1.ELF格式
我们先来看看 ELF 文件头,如果想详细了解,可以查看ELF的man page文档。

关于ELF更详细的说明: e_shoff:节头表的文件偏移量(字节)。如果文件没有节头表,则此成员值为零。 sh_offset:表示了该section(节)离开文件头部位置的距离
+-------------------+| ELF header|---+
+---------> +-------------------+ | e_shoff
| | |<--+
| Section | Section header 0|
| | |---+ sh_offset
| Header+-------------------+ |
| | Section header 1|---|--+ sh_offset
| Table +-------------------+ ||
| | Section header 2|---|--|--+
+---------> +-------------------+ |||| Section 0 |<--+||+-------------------+|| sh_offset| Section 1 |<-----+|+-------------------+ || Section 2 |<--------++-------------------+
2.可执行头部(Executable Header)
ELF文件的第一部分是可执行文件头部(Executable Header),其中包含有关ELF文件类型的信息。 ELF文件在各种平台下都通用,ELF文件有32位版本和64位版本,其文件头内容是一样的,只不过有些成员的大小不一样。它的文件图也有两种版本:分别叫"Elf32_Ehdr"和"Elf64_Ehdr"。 这里以32位版本为例:
#define EI_NIDENT (16)
typedef struct {unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */Elf32_Halfe_type; /* Object file type */Elf32_Halfe_machine;/* Architecture */Elf32_Worde_version;/* Object file version */Elf32_Addre_entry;/* Entry point virtual address */Elf32_Off e_phoff;/* Program header table file offset */Elf32_Off e_shoff;/* Section header table file offset */Elf32_Worde_flags;/* Processor-specific flags */Elf32_Halfe_ehsize; /* ELF header size in bytes */Elf32_Halfe_phentsize;/* Program header table entry size */Elf32_Halfe_phnum;/* Program header table entry count */Elf32_Halfe_shentsize;/* Section header table entry size */Elf32_Halfe_shnum;/* Section header table entry count */Elf32_Halfe_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
使用readelf
对ELF文件格式进行分析
# readelf -h /bin/ls
ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data:2's complement, little endianVersion: 1 (current)OS/ABI:UNIX - System VABI Version: 0Type:DYN (Shared object file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x6130Start of program headers:64 (bytes into file)Start of section headers:137000 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 11Size of section headers: 64 (bytes)Number of section headers: 29Section header string table index: 28
我们可以使用以下计算方法来计算整个二进制文件的大小:
size = e_shoff + (e_shnum * e_shentsize)
size = Start of section headers + (Number of section headers * Size of section headers)
size = 137000 + (29*64) = 138856
计算结果验证:
# ls -l /bin/ls
-rwxr-xr-x 1 root root 138856 Aug 29 21:20 /bin/ls
3、程序头部(Program Headers)
程序头部是描述文件中的各种segments(段),用来告诉系统如何创建进程映像的。
typedef struct {Elf32_Wordp_type; /* Segment type */Elf32_Off p_offset; /* Segment file offset */Elf32_Addrp_vaddr;/* Segment virtual address */Elf32_Addrp_paddr;/* Segment physical address */Elf32_Wordp_filesz; /* Segment size in file */Elf32_Wordp_memsz;/* Segment size in memory */Elf32_Wordp_flags;/* Segment flags */Elf32_Wordp_align;/* Segment alignment */
} Elf32_Phdr;
4、节表头部(Section Headers)
节表头部(Section Headers)包含了描述文件节区的信息,比如大小、偏移等,但这些对二进制文件的执行流程来说并不重要。
- sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出, segments与sections是包含的关系,一个segment包含若干个section。

typedef struct {Elf32_Wordsh_name;/* section的名字 (string tbl index) */Elf32_Wordsh_type;/*section类别 */Elf32_Wordsh_flags; /* section在进程中执行的特性(读、写) */Elf32_Addrsh_addr;/* 在内存中开始的虚地址 */Elf32_Off sh_offset;/* 此section在文件中的偏移 */Elf32_Wordsh_size;/* Section size in bytes */Elf32_Wordsh_link;/* Link to another section */Elf32_Wordsh_info;/* Additional section information */Elf32_Wordsh_addralign; /* Section alignment */Elf32_Wordsh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
5、表(Section)
5.1 .bss Section
保存未初始化的数据,比如那些未初始化的全局变量。
5.2 .data Section
保存已初始化的数据。
5.3 .rodata Section
保存程序中的只读数据。
5.4 .text Section
本节包含程序的实际代码,逻辑流程。 使用readelf
查看ELF文件表结构
# readelf -S --wide /bin/ls
There are 29 section headers, starting at offset 0x21728:
Section Headers:[Nr] NameTypeAddressOffSize ES Flg Lk Inf Al[ 0] NULL0000000000000000 000000 000000 000 00[ 1] .interp PROGBITS00000000000002a8 0002a8 00001c 00 A0 01[ 2] .note.ABI-tag NOTE00000000000002c4 0002c4 000020 00 A0 04[ 3] .note.gnu.build-id NOTE00000000000002e4 0002e4 000024 00 A0 04[ 4] .gnu.hash GNU_HASH0000000000000308 000308 0000c0 00 A5 08[ 5] .dynsym DYNSYM00000000000003c8 0003c8 000c90 18 A6 18[ 6] .dynstr STRTAB0000000000001058 001058 0005d8 00 A0 01[ 7] .gnu.versionVERSYM0000000000001630 001630 00010c 02 A5 02[ 8] .gnu.version_rVERNEED 0000000000001740 001740 000070 00 A6 18[ 9] .rela.dyn RELA00000000000017b0 0017b0 001350 18 A5 08[10] .rela.plt RELA0000000000002b00 002b00 0009f0 18AI5248[11] .init PROGBITS0000000000004000 004000 000017 00AX0 04[12] .pltPROGBITS0000000000004020 004020 0006b0 10AX0 0 16[13] .plt.gotPROGBITS00000000000046d0 0046d0 000018 08AX0 08[14] .text PROGBITS00000000000046f0 0046f0 01253e 00AX0 0 16[15] .fini PROGBITS0000000000016c30 016c30 000009 00AX0 04[16] .rodata PROGBITS0000000000017000 017000 005129 00 A0 0 32[17] .eh_frame_hdr PROGBITS000000000001c12c 01c12c 0008fc 00 A0 04[18] .eh_frame PROGBITS000000000001ca28 01ca28 002ed0 00 A0 08[19] .init_array INIT_ARRAY0000000000021390 020390 000008 08WA0 08[20] .fini_array FINI_ARRAY0000000000021398 020398 000008 08WA0 08[21] .data.rel.roPROGBITS00000000000213a0 0203a0 000a38 00WA0 0 32[22] .dynamicDYNAMIC 0000000000021dd8 020dd8 0001f0 10WA6 08[23] .gotPROGBITS0000000000021fc8 020fc8 000038 08WA0 08[24] .got.pltPROGBITS0000000000022000 021000 000368 08WA0 08[25] .data PROGBITS0000000000022380 021380 000268 00WA0 0 32[26] .bssNOBITS0000000000022600 0215e8 0012d8 00WA0 0 32[27] .gnu_debuglinkPROGBITS0000000000000000 0215e8 000034 000 04[28] .shstrtab STRTAB0000000000000000 02161c 00010a 000 01
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)
6、完成简单的CTF挑战
既然已经对ELF文件有所了解了,那找一个CTF题目来试试吧。
二进制文件下载地址:ufile.io/blvpm
1、运行这个程序,并传递一些随机字符给它,得到的结果如下:
# ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaa
[+] No flag for you. [+]
2、接着使用strings
查看一下程序的字符串,看是否能找到有用的信息
# strings nix_5744af788e6cbdb29bb41e8b0e5f3cd5
/lib/ld-linux.so.2
Mw1i#'0
libc.so.6
_IO_stdin_used
exit
sprintf
puts
strlen
__cxa_finalize
__libc_start_main
GLIBC_2.1.3
Y[^]
[^_]
UWVS
[^_]
Usage: script.exe <key>
Length of argv[1] too long.
[+] The flag is: SAYCURE{%s} [+]
[+] No flag for you. [+]
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c
;*2$"
GCC: (Debian 8.2.0-8) 8.2.0
crtstuff.c
我们可以看到 “%c” 是打印flag的字符串,数量是15个。
3、我们可以查看“.rodata ”部分的偏移量,可以更好的查看这些字符
# readelf-x .rodata nix_5744af788e6cbdb29bb41e8b0e5f3cd5
Hex dump of section '.rodata':0x00002000 03000000 01000200 55736167 653a2073 ........Usage: s0x00002010 63726970 742e6578 65203c6b 65793e00 cript.exe <key>.0x00002020 4c656e67 7468206f 66206172 67765b31 Length of argv[10x00002030 5d20746f 6f206c6f 6e672e00 5b2b5d20 ] too long..[+] 0x00002040 54686520 666c6167 2069733a 20534159 The flag is: SAY0x00002050 43555245 7b25737d 205b2b5d 0a000a5b CURE{%s} [+]...[0x00002060 2b5d204e 6f20666c 61672066 6f722079 +] No flag for y0x00002070 6f752e20 5b2b5d00 25632563 25632563 ou. [+].%c%c%c%c0x00002080 25632563 25632563 25632563 25632563 %c%c%c%c%c%c%c%c0x00002090 25632563 256300 %c%c%c.
4、检查符号表(Symbols) nm命令查看库文件的符号
# nm -D nix_5744af788e6cbdb29bb41e8b0e5f3cd5 w __cxa_finalize U exit w __gmon_start__
00002004 R _IO_stdin_used w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable U __libc_start_main U printf U puts U sprintf U strlen
说明: -D或–dynamic:显示动态符号。该任选项仅对于动态目标(例如特定类型的共享库)有意义 我们可以发现 printf, puts, sprintf, strlen functions.
这些函数未定义。 5、跟踪系统调用(System Calls) 我们可以使用strace
之类的工具去跟踪程序的系统调用
# strace ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaa
execve("./nix_5744af788e6cbdb29bb41e8b0e5f3cd5", ["./nix_5744af788e6cbdb29bb41e8b0e"..., "aaaa"], 0x7ffd5ff92d18 /* 46 vars */) = 0
strace: [ Process PID=59965 runs in 32 bit mode. ]
brk(NULL) = 0x56f14000
access("/etc/ld.so.nohwcap", F_OK)= -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7ef0000
access("/etc/ld.so.preload", R_OK)= -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=220471, ...}) = 0
mmap2(NULL, 220471, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf7eba000
close(3)= 0
access("/etc/ld.so.nohwcap", F_OK)= -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \233\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1930924, ...}) = 0
mmap2(NULL, 1940000, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf7ce0000
mprotect(0xf7cf9000, 1814528, PROT_NONE) = 0
mmap2(0xf7cf9000, 1359872, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0xf7cf9000
mmap2(0xf7e45000, 450560, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x165000) = 0xf7e45000
mmap2(0xf7eb4000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d3000) = 0xf7eb4000
mmap2(0xf7eb7000, 10784, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf7eb7000
close(3)= 0
set_thread_area({entry_number=-1, base_addr=0xf7ef10c0, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}) = 0 (entry_number=12)
mprotect(0xf7eb4000, 8192, PROT_READ) = 0
mprotect(0x5664d000, 4096, PROT_READ) = 0
mprotect(0xf7f1e000, 4096, PROT_READ) = 0
munmap(0xf7eba000, 220471)= 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
brk(NULL) = 0x56f14000
brk(0x56f35000) = 0x56f35000
brk(0x56f36000) = 0x56f36000
write(1, "\n", 1
) = 1
write(1, "[+] No flag for you. [+]\n", 25[+] No flag for you. [+]
) = 25
exit_group(26)= ?
+++ exited with 26 +++
为了更好地理解,我们可以使用ltrace
解码C++来跟踪函数名所做的库调用。 我们可以看到正在进行字符串长度检查。
# ltrace -i -C ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaaaaaa
[0x565570e1] __libc_start_main(0x565571e9, 2, 0xffe3a584, 0x56557400 <unfinished ...>
[0x56557249] strlen("aaaaaaaa")= 8
[0x565572ca] puts("\n[+] No flag for you. [+]"
[+] No flag for you. [+]
)= 26
[0xffffffffffffffff] +++ exited (status 26) +++
6、反编译 ".text"部分 让我们看一下.text部分的反汇编并尝试理解
# objdump -D -M intel -j .text nix_5744af788e6cbdb29bb41e8b0e5f3cd5
nix_5744af788e6cbdb29bb41e8b0e5f3cd5: file format elf32-i386
Disassembly of section .text:
000010b0 <_start>:10b0: 31 ed xorebp,ebp10b2: 5e popesi10b3: 89 e1 movecx,esp10b5: 83 e4 f0 andesp,0xfffffff010b8: 50 push eax10b9: 54 push esp10ba: 52 push edx10bb: e8 22 00 00 00 call 10e2 <_start+0x32>10c0: 81 c3 40 2f 00 00 addebx,0x2f4010c6: 8d 83 60 d4 ff ff leaeax,[ebx-0x2ba0]10cc: 50 push eax10cd: 8d 83 00 d4 ff ff leaeax,[ebx-0x2c00]10d3: 50 push eax10d4: 51 push ecx10d5: 56 push esi10d6: ff b3 f8 ff ff ff push DWORD PTR [ebx-0x8]10dc: e8 9f ff ff ff call 1080 <__libc_start_main@plt>10e1: f4 hlt10e2: 8b 1c 24 movebx,DWORD PTR [esp]10e5: c3 ret10e6: 66 90 xchg ax,ax10e8: 66 90 xchg ax,ax10ea: 66 90 xchg ax,ax10ec: 66 90 xchg ax,ax10ee: 66 90 xchg ax,ax
... Output Omitted ...
000011e9 <main>:11e9: 8d 4c 24 04 leaecx,[esp+0x4]11ed: 83 e4 f0 andesp,0xfffffff011f0: ff 71 fc push DWORD PTR [ecx-0x4]11f3: 55 push ebp11f4: 89 e5 movebp,esp11f6: 56 push esi11f7: 53 push ebx11f8: 51 push ecx11f9: 83 ec 1c subesp,0x1c11fc: e8 ef fe ff ff call 10f0 <__x86.get_pc_thunk.bx>1201: 81 c3 ff 2d 00 00 addebx,0x2dff1207: 89 ce movesi,ecx1209: c7 45 e4 00 00 00 00 movDWORD PTR [ebp-0x1c],0x01210: c7 45 dc 07 00 00 00 movDWORD PTR [ebp-0x24],0x71217: 83 3e 02 cmpDWORD PTR [esi],0x2121a: 74 1c je 1238 <main+0x4f>121c: 83 ec 0c subesp,0xc121f: 8d 83 08 e0 ff ff leaeax,[ebx-0x1ff8]1225: 50 push eax1226: e8 15 fe ff ff call 1040 <printf@plt>122b: 83 c4 10 addesp,0x10122e: 83 ec 0c subesp,0xc1231: 6a 01 push 0x11233: e8 28 fe ff ff call 1060 <exit@plt>1238: 8b 46 04 moveax,DWORD PTR [esi+0x4]123b: 83 c0 04 addeax,0x4123e: 8b 00 moveax,DWORD PTR [eax]1240: 83 ec 0c subesp,0xc1243: 50 push eax1244: e8 27 fe ff ff call 1070 <strlen@plt>1249: 83 c4 10 addesp,0x10124c: 83 f8 0f cmpeax,0xf124f: 76 1c jbe126d <main+0x84>1251: 83 ec 0c subesp,0xc1254: 8d 83 20 e0 ff ff leaeax,[ebx-0x1fe0]125a: 50 push eax125b: e8 f0 fd ff ff call 1050 <puts@plt>1260: 83 c4 10 addesp,0x101263: 83 ec 0c subesp,0xc1266: 6a 01 push 0x11268: e8 f3 fd ff ff call 1060 <exit@plt>126d: c7 45 e0 00 00 00 00 movDWORD PTR [ebp-0x20],0x01274: eb 1a jmp1290 <main+0xa7>1276: 8b 46 04 moveax,DWORD PTR [esi+0x4]1279: 83 c0 04 addeax,0x4127c: 8b 10 movedx,DWORD PTR [eax]127e: 8b 45 e0 moveax,DWORD PTR [ebp-0x20]1281: 01 d0 addeax,edx1283: 0f b6 00 movzxeax,BYTE PTR [eax]1286: 0f be c0 movsxeax,al1289: 01 45 e4 addDWORD PTR [ebp-0x1c],eax128c: 83 45 e0 01 addDWORD PTR [ebp-0x20],0x11290: 8b 45 e0 moveax,DWORD PTR [ebp-0x20]1293: 3b 45 dc cmpeax,DWORD PTR [ebp-0x24]1296: 7c de jl 1276 <main+0x8d>1298: 81 7d e4 21 03 00 00 cmpDWORD PTR [ebp-0x1c],0x321129f: 75 1a jne12bb <main+0xd2>12a1: e8 33 00 00 00 call 12d9 <comp_key>12a6: 83 ec 08 subesp,0x812a9: 50 push eax12aa: 8d 83 3c e0 ff ff leaeax,[ebx-0x1fc4]12b0: 50 push eax12b1: e8 8a fd ff ff call 1040 <printf@plt>12b6: 83 c4 10 addesp,0x1012b9: eb 12 jmp12cd <main+0xe4>12bb: 83 ec 0c subesp,0xc12be: 8d 83 5e e0 ff ff leaeax,[ebx-0x1fa2]12c4: 50 push eax12c5: e8 86 fd ff ff call 1050 <puts@plt>12ca: 83 c4 10 addesp,0x1012cd: 90 nop12ce: 8d 65 f4 leaesp,[ebp-0xc]12d1: 59 popecx12d2: 5b popebx12d3: 5e popesi12d4: 5d popebp12d5: 8d 61 fc leaesp,[ecx-0x4]12d8: c3 ret000012d9 <comp_key>:12d9: 55 push ebp12da: 89 e5 movebp,esp12dc: 57 push edi12dd: 56 push esi12de: 53 push ebx12df: 83 ec 7c subesp,0x7c12e2: e8 09 fe ff ff call 10f0 <__x86.get_pc_thunk.bx>12e7: 81 c3 19 2d 00 00 addebx,0x2d1912ed: c7 45 e4 00 00 00 00 movDWORD PTR [ebp-0x1c],0x012f4: c7 45 a8 4c 00 00 00 movDWORD PTR [ebp-0x58],0x4c12fb: c7 45 ac 33 00 00 00 movDWORD PTR [ebp-0x54],0x331302: c7 45 b0 74 00 00 00 movDWORD PTR [ebp-0x50],0x741309: c7 45 b4 73 00 00 00 movDWORD PTR [ebp-0x4c],0x731310: c7 45 b8 5f 00 00 00 movDWORD PTR [ebp-0x48],0x5f1317: c7 45 bc 67 00 00 00 movDWORD PTR [ebp-0x44],0x67131e: c7 45 c0 33 00 00 00 movDWORD PTR [ebp-0x40],0x331325: c7 45 c4 74 00 00 00 movDWORD PTR [ebp-0x3c],0x74132c: c7 45 c8 5f 00 00 00 movDWORD PTR [ebp-0x38],0x5f1333: c7 45 cc 69 00 00 00 movDWORD PTR [ebp-0x34],0x69133a: c7 45 d0 6e 00 00 00 movDWORD PTR [ebp-0x30],0x6e1341: c7 45 d4 32 00 00 00 movDWORD PTR [ebp-0x2c],0x321348: c7 45 d8 5f 00 00 00 movDWORD PTR [ebp-0x28],0x5f134f: c7 45 dc 52 00 00 00 movDWORD PTR [ebp-0x24],0x521356: c7 45 e0 33 00 00 00 movDWORD PTR [ebp-0x20],0x33135d: 8b 55 e0 movedx,DWORD PTR [ebp-0x20]1360: 8b 75 dc movesi,DWORD PTR [ebp-0x24]1363: 8b 45 d8 moveax,DWORD PTR [ebp-0x28]1366: 89 45 a4 movDWORD PTR [ebp-0x5c],eax1369: 8b 4d d4 movecx,DWORD PTR [ebp-0x2c]136c: 89 4d a0 movDWORD PTR [ebp-0x60],ecx136f: 8b 7d d0 movedi,DWORD PTR [ebp-0x30]1372: 89 7d 9c movDWORD PTR [ebp-0x64],edi1375: 8b 45 cc moveax,DWORD PTR [ebp-0x34]1378: 89 45 98 movDWORD PTR [ebp-0x68],eax137b: 8b 4d c8 movecx,DWORD PTR [ebp-0x38]137e: 89 4d 94 movDWORD PTR [ebp-0x6c],ecx1381: 8b 7d c4 movedi,DWORD PTR [ebp-0x3c]1384: 89 7d 90 movDWORD PTR [ebp-0x70],edi1387: 8b 45 c0 moveax,DWORD PTR [ebp-0x40]138a: 89 45 8c movDWORD PTR [ebp-0x74],eax138d: 8b 4d bc movecx,DWORD PTR [ebp-0x44]1390: 89 4d 88 movDWORD PTR [ebp-0x78],ecx1393: 8b 7d b8 movedi,DWORD PTR [ebp-0x48]1396: 89 7d 84 movDWORD PTR [ebp-0x7c],edi1399: 8b 45 b4 moveax,DWORD PTR [ebp-0x4c]139c: 89 45 80 movDWORD PTR [ebp-0x80],eax139f: 8b 7d b0 movedi,DWORD PTR [ebp-0x50]13a2: 8b 4d ac movecx,DWORD PTR [ebp-0x54]13a5: 8b 45 a8 moveax,DWORD PTR [ebp-0x58]13a8: 83 ec 0c subesp,0xc13ab: 52 push edx13ac: 56 push esi13ad: ff 75 a4 push DWORD PTR [ebp-0x5c]13b0: ff 75 a0 push DWORD PTR [ebp-0x60]13b3: ff 75 9c push DWORD PTR [ebp-0x64]13b6: ff 75 98 push DWORD PTR [ebp-0x68]13b9: ff 75 94 push DWORD PTR [ebp-0x6c]13bc: ff 75 90 push DWORD PTR [ebp-0x70]13bf: ff 75 8c push DWORD PTR [ebp-0x74]13c2: ff 75 88 push DWORD PTR [ebp-0x78]13c5: ff 75 84 push DWORD PTR [ebp-0x7c]13c8: ff 75 80 push DWORD PTR [ebp-0x80]13cb: 57 push edi13cc: 51 push ecx13cd: 50 push eax13ce: 8d 83 78 e0 ff ff leaeax,[ebx-0x1f88]13d4: 50 push eax13d5: 8d 83 30 00 00 00 leaeax,[ebx+0x30]13db: 50 push eax13dc: e8 af fc ff ff call 1090 <sprintf@plt>13e1: 83 c4 50 addesp,0x5013e4: 8d 83 30 00 00 00 leaeax,[ebx+0x30]13ea: 8d 65 f4 leaesp,[ebp-0xc]13ed: 5b popebx13ee: 5e popesi13ef: 5f popedi13f0: 5d popebp13f1: c3 ret13f2: 66 90 xchg ax,ax13f4: 66 90 xchg ax,ax13f6: 66 90 xchg ax,ax13f8: 66 90 xchg ax,ax13fa: 66 90 xchg ax,ax13fc: 66 90 xchg ax,ax13fe: 66 90 xchg ax,ax
... Output Omitted ...
在这个二进制文件中,符号没有被剥离,因此我们可以看到函数名称,这使得它更容易理解。 如果你可以阅读汇编代码,你可以很清楚的知道发生了什么。 如果不能阅读汇编代码,让我们做一些实时调试,并尝试更好地理解。 7、实时调试 这里我们使用GDB-Peda
进行实时调试 我们首先检查二进制文件中的函数。我们可以看到main,comp_key
等函数
gdb-peda$ info functions
All defined functions:
Non-debugging symbols:
0x00001000_init
0x00001040printf@plt
0x00001050puts@plt
0x00001060exit@plt
0x00001070strlen@plt
0x00001080__libc_start_main@plt
0x00001090sprintf@plt
0x000010a0__cxa_finalize@plt
0x000010a8__gmon_start__@plt
0x000010b0_start
0x000010f0__x86.get_pc_thunk.bx
0x00001100deregister_tm_clones
0x00001140register_tm_clones
0x00001190__do_global_dtors_aux
0x000011e0frame_dummy
0x000011e5__x86.get_pc_thunk.dx
0x000011e9main
0x000012d9comp_key
0x00001400__libc_csu_init
0x00001460__libc_csu_fini
0x00001464_fini
调试方法:首先使用 break main
跳到主函数,使用n
来step和ni
来执行每条指令
gdb-peda$ break main
Breakpoint 1 at 0x11f9
gdb-peda$ run aaaaaaaa
Starting program: /mnt/hgfs/shared/Linux RE/nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaaaaaa
[----------------------------------registers-----------------------------------]
EAX: 0xf7f95dd8 --> 0xffffd2f0 --> 0xffffd4d1 ("NVM_DIR=/root/.nvm")
EBX: 0x0
ECX: 0xffffd250 --> 0x2
EDX: 0xffffd274 --> 0x0
ESI: 0xf7f94000 --> 0x1d5d8c
EDI: 0x0
EBP: 0xffffd238 --> 0x0
ESP: 0xffffd22c --> 0xffffd250 --> 0x2
EIP: 0x565561f9 (<main+16>: subesp,0x1c)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------] 0x565561f6 <main+13>: push esi 0x565561f7 <main+14>: push ebx 0x565561f8 <main+15>: push ecx
=> 0x565561f9 <main+16>: subesp,0x1c 0x565561fc <main+19>: call 0x565560f0 <__x86.get_pc_thunk.bx> 0x56556201 <main+24>: addebx,0x2dff 0x56556207 <main+30>: movesi,ecx 0x56556209 <main+32>: movDWORD PTR [ebp-0x1c],0x0
[------------------------------------stack-------------------------------------]
0000| 0xffffd22c --> 0xffffd250 --> 0x2
0004| 0xffffd230 --> 0x0
0008| 0xffffd234 --> 0xf7f94000 --> 0x1d5d8c
0012| 0xffffd238 --> 0x0
0016| 0xffffd23c --> 0xf7dd79a1 (<__libc_start_main+241>: addesp,0x10)
0020| 0xffffd240 --> 0xf7f94000 --> 0x1d5d8c
0024| 0xffffd244 --> 0xf7f94000 --> 0x1d5d8c
0028| 0xffffd248 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x565561f9 in main ()
1: main = {<text variable, no debug info>} 0x565561e9 <main>
2: puts = {<text variable, no debug info>} 0xf7e25e40 <puts>
gdb-peda$
让我们来看看程序的逻辑,程序首先尝试比较参数的数量。它存储在ecx寄存器中并移动到esi,它用于将值与0x2进行比较
0x56556207 <+30>: movesi,ecx
0x56556209 <+32>: movDWORD PTR [ebp-0x1c],0x0
0x56556210 <+39>: movDWORD PTR [ebp-0x24],0x7
0x56556217 <+46>: cmpDWORD PTR [esi],0x2
0x5655621a <+49>: je 0x56556238 <main+79>
0x5655621c <+51>: subesp,0xc
0x5655621f <+54>: leaeax,[ebx-0x1ff8]
0x56556225 <+60>: push eax
0x56556226 <+61>: call 0x56556040 <printf@plt>
0x5655622b <+66>: addesp,0x10
0x5655622e <+69>: subesp,0xc
0x56556231 <+72>: push 0x1
0x56556233 <+74>: call 0x56556060 <exit@plt>
其伪代码看起来是这样的:
if(argc != 2) { printf("Usage: script.exe <key>"); exit(1);
}
0x56556238 <+79>: moveax,DWORD PTR [esi+0x4] 0x5655623b <+82>: addeax,0x4 0x5655623e <+85>: moveax,DWORD PTR [eax] 0x56556240 <+87>: subesp,0xc 0x56556243 <+90>: push eax 0x56556244 <+91>: call 0x56556070 <strlen@plt> 0x56556249 <+96>: addesp,0x10 0x5655624c <+99>: cmpeax,0xf 0x5655624f <+102>: jbe0x5655626d <main+132> 0x56556251 <+104>: subesp,0xc 0x56556254 <+107>: leaeax,[ebx-0x1fe0] 0x5655625a <+113>: push eax 0x5655625b <+114>: call 0x56556050 <puts@plt> 0x56556260 <+119>: addesp,0x10 0x56556263 <+122>: subesp,0xc 0x56556266 <+125>: push 0x1 0x56556268 <+127>: call 0x56556060 <exit@plt>
其代码是这样的:
if(strlen(argv[1]) > 15) {puts("Length of argv[1] too long.");exit(1);
}
如果你检查这个代码,可以看到有一个循环正在迭代我们输入字符串的每个字符。
0x5655626d <+132>: movDWORD PTR [ebp-0x20],0x0 0x56556274 <+139>: jmp0x56556290 <main+167> 0x56556276 <+141>: moveax,DWORD PTR [esi+0x4] 0x56556279 <+144>: addeax,0x4 0x5655627c <+147>: movedx,DWORD PTR [eax] 0x5655627e <+149>: moveax,DWORD PTR [ebp-0x20] 0x56556281 <+152>: addeax,edx 0x56556283 <+154>: movzxeax,BYTE PTR [eax] 0x56556286 <+157>: movsxeax,al 0x56556289 <+160>: addDWORD PTR [ebp-0x1c],eax 0x5655628c <+163>: addDWORD PTR [ebp-0x20],0x1 0x56556290 <+167>: moveax,DWORD PTR [ebp-0x20] 0x56556293 <+170>: cmpeax,DWORD PTR [ebp-0x24] 0x56556296 <+173>: jl 0x56556276 <main+141> 0x56556298 <+175>: cmpDWORD PTR [ebp-0x1c],0x321 0x5655629f <+182>: jne0x565562bb <main+210> 0x565562a1 <+184>: call 0x565562d9 <comp_key> 0x565562a6 <+189>: subesp,0x8 0x565562a9 <+192>: push eax 0x565562aa <+193>: leaeax,[ebx-0x1fc4] 0x565562b0 <+199>: push eax 0x565562b1 <+200>: call 0x56556040 <printf@plt> 0x565562b6 <+205>: addesp,0x10 0x565562b9 <+208>: jmp0x565562cd <main+228> 0x565562bb <+210>: subesp,0xc 0x565562be <+213>: leaeax,[ebx-0x1fa2] 0x565562c4 <+219>: push eax 0x565562c5 <+220>: call 0x56556050 <puts@plt> 0x565562ca <+225>: addesp,0x10 0x565562cd <+228>: nop 0x565562ce <+229>: leaesp,[ebp-0xc] 0x565562d1 <+232>: popecx 0x565562d2 <+233>: popebx 0x565562d3 <+234>: popesi 0x565562d4 <+235>: popebp 0x565562d5 <+236>: leaesp,[ecx-0x4] 0x565562d8 <+239>: ret
它到底循环了多少个字符?通常来说,我们的密码长度为7个字符。
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x56559000 --> 0x3efc
ECX: 0x6
EDX: 0xffffd4c6 ("1234567890")
ESI: 0xffffd250 --> 0x2
EDI: 0x0
EBP: 0xffffd238 --> 0x0
ESP: 0xffffd210 --> 0xf7f943fc --> 0xf7f95200 --> 0x0
EIP: 0x56556293 (<main+170>: cmpeax,DWORD PTR [ebp-0x24])
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------] 0x56556289 <main+160>: addDWORD PTR [ebp-0x1c],eax 0x5655628c <main+163>: addDWORD PTR [ebp-0x20],0x1 0x56556290 <main+167>: moveax,DWORD PTR [ebp-0x20]
=> 0x56556293 <main+170>: cmpeax,DWORD PTR [ebp-0x24] 0x56556296 <main+173>: jl 0x56556276 <main+141> 0x56556298 <main+175>: cmpDWORD PTR [ebp-0x1c],0x321 0x5655629f <main+182>: jne0x565562bb <main+210> 0x565562a1 <main+184>: call 0x565562d9 <comp_key>
[------------------------------------stack-------------------------------------]
0000| 0xffffd210 --> 0xf7f943fc --> 0xf7f95200 --> 0x0
0004| 0xffffd214 --> 0x7
0008| 0xffffd218 --> 0x6
0012| 0xffffd21c --> 0x135
0016| 0xffffd220 --> 0x2
0020| 0xffffd224 --> 0xffffd2e4 --> 0xffffd487 ("/mnt/hgfs/shared/Linux RE/nix_5744af788e6cbdb29bb41e8b0e5f3cd5")
0024| 0xffffd228 --> 0xffffd2f0 --> 0xffffd4d1 ("NVM_DIR=/root/.nvm")
0028| 0xffffd22c --> 0xffffd250 --> 0x2
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x56556293 in main ()
gdb-peda$ print $ebp-0x24
$24 = (void *) 0xffffd214
gdb-peda$ x/x 0xffffd214
0xffffd214: 0x00000007
代码看起来是这样的:
for (i = 0; i < 7; i++) value += argv[1][i];
if (value != 801) return puts("\n[+] No flag for you. [+]");
return printf("[+] The flag is: SAYCURE{%s} [+]\n", comp_key());
可以看出,如果7个字符总和等于801,即可得到flag。您可以使用任何字符,只要总和是801即可。检查完成后,调用comp_key
函数并打印出flag。 比如这样: 114 * 6 + 177 = 801
我们找到数字对应的ASCII字符 114是 ‘r’ 117 是 ‘u’。
Dec HexDec HexDec HexDec HexDec HexDec Hex Dec Hex Dec Hex0 00 NUL16 10 DLE32 2048 30 064 40 @80 50 P 96 60 `112 70 p1 01 SOH17 11 DC133 21 !49 31 165 41 A81 51 Q 97 61 a113 71 q2 02 STX18 12 DC234 22 "50 32 266 42 B82 52 R 98 62 b114 72 r3 03 ETX19 13 DC335 23 #51 33 367 43 C83 53 S 99 63 c115 73 s4 04 EOT20 14 DC436 24 $52 34 468 44 D84 54 T100 64 d116 74 t5 05 ENQ21 15 NAK37 25 %53 35 569 45 E85 55 U101 65 e117 75 u6 06 ACK22 16 SYN38 26 &54 36 670 46 F86 56 V102 66 f118 76 v7 07 BEL23 17 ETB39 27 '55 37 771 47 G87 57 W103 67 g119 77 w8 08 BS 24 18 CAN40 28 (56 38 872 48 H88 58 X104 68 h120 78 x9 09 HT 25 19 EM 41 29 )57 39 973 49 I89 59 Y105 69 i121 79 y
10 0A LF 26 1A SUB42 2A *58 3A :74 4A J90 5A Z106 6A j122 7A z
11 0B VT 27 1B ESC43 2B +59 3B ;75 4B K91 5B [107 6B k123 7B { 12 0C FF 28 1C FS 44 2C ,60 3C <76 4C L92 5C \108 6C l124 7C | 13 0D CR 29 1D GS 45 2D -61 3D =77 4D M93 5D ]109 6D m125 7D }
14 0E SO 30 1E RS 46 2E .62 3E >78 4E N94 5E ^110 6E n126 7E ~
15 0F SI 31 1F US 47 2F /63 3F ?79 4F O95 5F _111 6F o127 7F DEL
然后我们将字符作为输入,执行程序即可得到FLAG
# ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 rrrrrru
[+] The flag is: SAYCURE{L3ts_g3t_in2_R3} [+]
