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_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_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: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x6130
Start of program headers: 64 (bytes into file)
Start of section headers: 137000 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 29
Section 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_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_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_Word sh_name; /* section的名字 (string tbl index) */
Elf32_Word sh_type; /*section类别 */
Elf32_Word sh_flags; /* section在进程中执行的特性(读、写) */
Elf32_Addr sh_addr; /* 在内存中开始的虚地址 */
Elf32_Off sh_offset; /* 此section在文件中的偏移 */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_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] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 0002a8 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00000000000002c4 0002c4 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 00000000000002e4 0002e4 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 000308 0000c0 00 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000003c8 0003c8 000c90 18 A 6 1 8
[ 6] .dynstr STRTAB 0000000000001058 001058 0005d8 00 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000001630 001630 00010c 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000001740 001740 000070 00 A 6 1 8
[ 9] .rela.dyn RELA 00000000000017b0 0017b0 001350 18 A 5 0 8
[10] .rela.plt RELA 0000000000002b00 002b00 0009f0 18 AI 5 24 8
[11] .init PROGBITS 0000000000004000 004000 000017 00 AX 0 0 4
[12] .plt PROGBITS 0000000000004020 004020 0006b0 10 AX 0 0 16
[13] .plt.got PROGBITS 00000000000046d0 0046d0 000018 08 AX 0 0 8
[14] .text PROGBITS 00000000000046f0 0046f0 01253e 00 AX 0 0 16
[15] .fini PROGBITS 0000000000016c30 016c30 000009 00 AX 0 0 4
[16] .rodata PROGBITS 0000000000017000 017000 005129 00 A 0 0 32
[17] .eh_frame_hdr PROGBITS 000000000001c12c 01c12c 0008fc 00 A 0 0 4
[18] .eh_frame PROGBITS 000000000001ca28 01ca28 002ed0 00 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000021390 020390 000008 08 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000021398 020398 000008 08 WA 0 0 8
[21] .data.rel.ro PROGBITS 00000000000213a0 0203a0 000a38 00 WA 0 0 32
[22] .dynamic DYNAMIC 0000000000021dd8 020dd8 0001f0 10 WA 6 0 8
[23] .got PROGBITS 0000000000021fc8 020fc8 000038 08 WA 0 0 8
[24] .got.plt PROGBITS 0000000000022000 021000 000368 08 WA 0 0 8
[25] .data PROGBITS 0000000000022380 021380 000268 00 WA 0 0 32
[26] .bss NOBITS 0000000000022600 0215e8 0012d8 00 WA 0 0 32
[27] .gnu_debuglink PROGBITS 0000000000000000 0215e8 000034 00 0 0 4
[28] .shstrtab STRTAB 0000000000000000 02161c 00010a 00 0 0 1
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题目来试试吧。
二进制文件下载地址:https://ufile.io/blvpm
国内下载:https://www.lanzous.com/i34qg6f
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: s
0x00002010 63726970 742e6578 65203c6b 65793e00 cript.exe <key>.
0x00002020 4c656e67 7468206f 66206172 67765b31 Length of argv[1
0x00002030 5d20746f 6f206c6f 6e672e00 5b2b5d20 ] too long..[+]
0x00002040 54686520 666c6167 2069733a 20534159 The flag is: SAY
0x00002050 43555245 7b25737d 205b2b5d 0a000a5b CURE{%s} [+]...[
0x00002060 2b5d204e 6f20666c 61672066 6f722079 +] No flag for y
0x00002070 6f752e20 5b2b5d00 25632563 25632563 ou. [+].%c%c%c%c
0x00002080 25632563 25632563 25632563 25632563 %c%c%c%c%c%c%c%c
0x00002090 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 xor ebp,ebp
10b2: 5e pop esi
10b3: 89 e1 mov ecx,esp
10b5: 83 e4 f0 and esp,0xfffffff0
10b8: 50 push eax
10b9: 54 push esp
10ba: 52 push edx
10bb: e8 22 00 00 00 call 10e2 <_start+0x32>
10c0: 81 c3 40 2f 00 00 add ebx,0x2f40
10c6: 8d 83 60 d4 ff ff lea eax,[ebx-0x2ba0]
10cc: 50 push eax
10cd: 8d 83 00 d4 ff ff lea eax,[ebx-0x2c00]
10d3: 50 push eax
10d4: 51 push ecx
10d5: 56 push esi
10d6: 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 hlt
10e2: 8b 1c 24 mov ebx,DWORD PTR [esp]
10e5: c3 ret
10e6: 66 90 xchg ax,ax
10e8: 66 90 xchg ax,ax
10ea: 66 90 xchg ax,ax
10ec: 66 90 xchg ax,ax
10ee: 66 90 xchg ax,ax
... Output Omitted ...
000011e9 <main>:
11e9: 8d 4c 24 04 lea ecx,[esp+0x4]
11ed: 83 e4 f0 and esp,0xfffffff0
11f0: ff 71 fc push DWORD PTR [ecx-0x4]
11f3: 55 push ebp
11f4: 89 e5 mov ebp,esp
11f6: 56 push esi
11f7: 53 push ebx
11f8: 51 push ecx
11f9: 83 ec 1c sub esp,0x1c
11fc: e8 ef fe ff ff call 10f0 <__x86.get_pc_thunk.bx>
1201: 81 c3 ff 2d 00 00 add ebx,0x2dff
1207: 89 ce mov esi,ecx
1209: c7 45 e4 00 00 00 00 mov DWORD PTR [ebp-0x1c],0x0
1210: c7 45 dc 07 00 00 00 mov DWORD PTR [ebp-0x24],0x7
1217: 83 3e 02 cmp DWORD PTR [esi],0x2
121a: 74 1c je 1238 <main+0x4f>
121c: 83 ec 0c sub esp,0xc
121f: 8d 83 08 e0 ff ff lea eax,[ebx-0x1ff8]
1225: 50 push eax
1226: e8 15 fe ff ff call 1040 <printf@plt>
122b: 83 c4 10 add esp,0x10
122e: 83 ec 0c sub esp,0xc
1231: 6a 01 push 0x1
1233: e8 28 fe ff ff call 1060 <exit@plt>
1238: 8b 46 04 mov eax,DWORD PTR [esi+0x4]
123b: 83 c0 04 add eax,0x4
123e: 8b 00 mov eax,DWORD PTR [eax]
1240: 83 ec 0c sub esp,0xc
1243: 50 push eax
1244: e8 27 fe ff ff call 1070 <strlen@plt>
1249: 83 c4 10 add esp,0x10
124c: 83 f8 0f cmp eax,0xf
124f: 76 1c jbe 126d <main+0x84>
1251: 83 ec 0c sub esp,0xc
1254: 8d 83 20 e0 ff ff lea eax,[ebx-0x1fe0]
125a: 50 push eax
125b: e8 f0 fd ff ff call 1050 <puts@plt>
1260: 83 c4 10 add esp,0x10
1263: 83 ec 0c sub esp,0xc
1266: 6a 01 push 0x1
1268: e8 f3 fd ff ff call 1060 <exit@plt>
126d: c7 45 e0 00 00 00 00 mov DWORD PTR [ebp-0x20],0x0
1274: eb 1a jmp 1290 <main+0xa7>
1276: 8b 46 04 mov eax,DWORD PTR [esi+0x4]
1279: 83 c0 04 add eax,0x4
127c: 8b 10 mov edx,DWORD PTR [eax]
127e: 8b 45 e0 mov eax,DWORD PTR [ebp-0x20]
1281: 01 d0 add eax,edx
1283: 0f b6 00 movzx eax,BYTE PTR [eax]
1286: 0f be c0 movsx eax,al
1289: 01 45 e4 add DWORD PTR [ebp-0x1c],eax
128c: 83 45 e0 01 add DWORD PTR [ebp-0x20],0x1
1290: 8b 45 e0 mov eax,DWORD PTR [ebp-0x20]
1293: 3b 45 dc cmp eax,DWORD PTR [ebp-0x24]
1296: 7c de jl 1276 <main+0x8d>
1298: 81 7d e4 21 03 00 00 cmp DWORD PTR [ebp-0x1c],0x321
129f: 75 1a jne 12bb <main+0xd2>
12a1: e8 33 00 00 00 call 12d9 <comp_key>
12a6: 83 ec 08 sub esp,0x8
12a9: 50 push eax
12aa: 8d 83 3c e0 ff ff lea eax,[ebx-0x1fc4]
12b0: 50 push eax
12b1: e8 8a fd ff ff call 1040 <printf@plt>
12b6: 83 c4 10 add esp,0x10
12b9: eb 12 jmp 12cd <main+0xe4>
12bb: 83 ec 0c sub esp,0xc
12be: 8d 83 5e e0 ff ff lea eax,[ebx-0x1fa2]
12c4: 50 push eax
12c5: e8 86 fd ff ff call 1050 <puts@plt>
12ca: 83 c4 10 add esp,0x10
12cd: 90 nop
12ce: 8d 65 f4 lea esp,[ebp-0xc]
12d1: 59 pop ecx
12d2: 5b pop ebx
12d3: 5e pop esi
12d4: 5d pop ebp
12d5: 8d 61 fc lea esp,[ecx-0x4]
12d8: c3 ret
000012d9 <comp_key>:
12d9: 55 push ebp
12da: 89 e5 mov ebp,esp
12dc: 57 push edi
12dd: 56 push esi
12de: 53 push ebx
12df: 83 ec 7c sub esp,0x7c
12e2: e8 09 fe ff ff call 10f0 <__x86.get_pc_thunk.bx>
12e7: 81 c3 19 2d 00 00 add ebx,0x2d19
12ed: c7 45 e4 00 00 00 00 mov DWORD PTR [ebp-0x1c],0x0
12f4: c7 45 a8 4c 00 00 00 mov DWORD PTR [ebp-0x58],0x4c
12fb: c7 45 ac 33 00 00 00 mov DWORD PTR [ebp-0x54],0x33
1302: c7 45 b0 74 00 00 00 mov DWORD PTR [ebp-0x50],0x74
1309: c7 45 b4 73 00 00 00 mov DWORD PTR [ebp-0x4c],0x73
1310: c7 45 b8 5f 00 00 00 mov DWORD PTR [ebp-0x48],0x5f
1317: c7 45 bc 67 00 00 00 mov DWORD PTR [ebp-0x44],0x67
131e: c7 45 c0 33 00 00 00 mov DWORD PTR [ebp-0x40],0x33
1325: c7 45 c4 74 00 00 00 mov DWORD PTR [ebp-0x3c],0x74
132c: c7 45 c8 5f 00 00 00 mov DWORD PTR [ebp-0x38],0x5f
1333: c7 45 cc 69 00 00 00 mov DWORD PTR [ebp-0x34],0x69
133a: c7 45 d0 6e 00 00 00 mov DWORD PTR [ebp-0x30],0x6e
1341: c7 45 d4 32 00 00 00 mov DWORD PTR [ebp-0x2c],0x32
1348: c7 45 d8 5f 00 00 00 mov DWORD PTR [ebp-0x28],0x5f
134f: c7 45 dc 52 00 00 00 mov DWORD PTR [ebp-0x24],0x52
1356: c7 45 e0 33 00 00 00 mov DWORD PTR [ebp-0x20],0x33
135d: 8b 55 e0 mov edx,DWORD PTR [ebp-0x20]
1360: 8b 75 dc mov esi,DWORD PTR [ebp-0x24]
1363: 8b 45 d8 mov eax,DWORD PTR [ebp-0x28]
1366: 89 45 a4 mov DWORD PTR [ebp-0x5c],eax
1369: 8b 4d d4 mov ecx,DWORD PTR [ebp-0x2c]
136c: 89 4d a0 mov DWORD PTR [ebp-0x60],ecx
136f: 8b 7d d0 mov edi,DWORD PTR [ebp-0x30]
1372: 89 7d 9c mov DWORD PTR [ebp-0x64],edi
1375: 8b 45 cc mov eax,DWORD PTR [ebp-0x34]
1378: 89 45 98 mov DWORD PTR [ebp-0x68],eax
137b: 8b 4d c8 mov ecx,DWORD PTR [ebp-0x38]
137e: 89 4d 94 mov DWORD PTR [ebp-0x6c],ecx
1381: 8b 7d c4 mov edi,DWORD PTR [ebp-0x3c]
1384: 89 7d 90 mov DWORD PTR [ebp-0x70],edi
1387: 8b 45 c0 mov eax,DWORD PTR [ebp-0x40]
138a: 89 45 8c mov DWORD PTR [ebp-0x74],eax
138d: 8b 4d bc mov ecx,DWORD PTR [ebp-0x44]
1390: 89 4d 88 mov DWORD PTR [ebp-0x78],ecx
1393: 8b 7d b8 mov edi,DWORD PTR [ebp-0x48]
1396: 89 7d 84 mov DWORD PTR [ebp-0x7c],edi
1399: 8b 45 b4 mov eax,DWORD PTR [ebp-0x4c]
139c: 89 45 80 mov DWORD PTR [ebp-0x80],eax
139f: 8b 7d b0 mov edi,DWORD PTR [ebp-0x50]
13a2: 8b 4d ac mov ecx,DWORD PTR [ebp-0x54]
13a5: 8b 45 a8 mov eax,DWORD PTR [ebp-0x58]
13a8: 83 ec 0c sub esp,0xc
13ab: 52 push edx
13ac: 56 push esi
13ad: 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 edi
13cc: 51 push ecx
13cd: 50 push eax
13ce: 8d 83 78 e0 ff ff lea eax,[ebx-0x1f88]
13d4: 50 push eax
13d5: 8d 83 30 00 00 00 lea eax,[ebx+0x30]
13db: 50 push eax
13dc: e8 af fc ff ff call 1090 <sprintf@plt>
13e1: 83 c4 50 add esp,0x50
13e4: 8d 83 30 00 00 00 lea eax,[ebx+0x30]
13ea: 8d 65 f4 lea esp,[ebp-0xc]
13ed: 5b pop ebx
13ee: 5e pop esi
13ef: 5f pop edi
13f0: 5d pop ebp
13f1: c3 ret
13f2: 66 90 xchg ax,ax
13f4: 66 90 xchg ax,ax
13f6: 66 90 xchg ax,ax
13f8: 66 90 xchg ax,ax
13fa: 66 90 xchg ax,ax
13fc: 66 90 xchg ax,ax
13fe: 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
0x00001040 printf@plt
0x00001050 puts@plt
0x00001060 exit@plt
0x00001070 strlen@plt
0x00001080 __libc_start_main@plt
0x00001090 sprintf@plt
0x000010a0 __cxa_finalize@plt
0x000010a8 __gmon_start__@plt
0x000010b0 _start
0x000010f0 __x86.get_pc_thunk.bx
0x00001100 deregister_tm_clones
0x00001140 register_tm_clones
0x00001190 __do_global_dtors_aux
0x000011e0 frame_dummy
0x000011e5 __x86.get_pc_thunk.dx
0x000011e9 main
0x000012d9 comp_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>: sub esp,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>: sub esp,0x1c
0x565561fc <main+19>: call 0x565560f0 <__x86.get_pc_thunk.bx>
0x56556201 <main+24>: add ebx,0x2dff
0x56556207 <main+30>: mov esi,ecx
0x56556209 <main+32>: mov DWORD 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>: add esp,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>: mov esi,ecx
0x56556209 <+32>: mov DWORD PTR [ebp-0x1c],0x0
0x56556210 <+39>: mov DWORD PTR [ebp-0x24],0x7
0x56556217 <+46>: cmp DWORD PTR [esi],0x2
0x5655621a <+49>: je 0x56556238 <main+79>
0x5655621c <+51>: sub esp,0xc
0x5655621f <+54>: lea eax,[ebx-0x1ff8]
0x56556225 <+60>: push eax
0x56556226 <+61>: call 0x56556040 <printf@plt>
0x5655622b <+66>: add esp,0x10
0x5655622e <+69>: sub esp,0xc
0x56556231 <+72>: push 0x1
0x56556233 <+74>: call 0x56556060 <exit@plt>
其伪代码看起来是这样的:
if(argc != 2) {
printf("Usage: script.exe <key>");
exit(1);
}
0x56556238 <+79>: mov eax,DWORD PTR [esi+0x4]
0x5655623b <+82>: add eax,0x4
0x5655623e <+85>: mov eax,DWORD PTR [eax]
0x56556240 <+87>: sub esp,0xc
0x56556243 <+90>: push eax
0x56556244 <+91>: call 0x56556070 <strlen@plt>
0x56556249 <+96>: add esp,0x10
0x5655624c <+99>: cmp eax,0xf
0x5655624f <+102>: jbe 0x5655626d <main+132>
0x56556251 <+104>: sub esp,0xc
0x56556254 <+107>: lea eax,[ebx-0x1fe0]
0x5655625a <+113>: push eax
0x5655625b <+114>: call 0x56556050 <puts@plt>
0x56556260 <+119>: add esp,0x10
0x56556263 <+122>: sub esp,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>: mov DWORD PTR [ebp-0x20],0x0
0x56556274 <+139>: jmp 0x56556290 <main+167>
0x56556276 <+141>: mov eax,DWORD PTR [esi+0x4]
0x56556279 <+144>: add eax,0x4
0x5655627c <+147>: mov edx,DWORD PTR [eax]
0x5655627e <+149>: mov eax,DWORD PTR [ebp-0x20]
0x56556281 <+152>: add eax,edx
0x56556283 <+154>: movzx eax,BYTE PTR [eax]
0x56556286 <+157>: movsx eax,al
0x56556289 <+160>: add DWORD PTR [ebp-0x1c],eax
0x5655628c <+163>: add DWORD PTR [ebp-0x20],0x1
0x56556290 <+167>: mov eax,DWORD PTR [ebp-0x20]
0x56556293 <+170>: cmp eax,DWORD PTR [ebp-0x24]
0x56556296 <+173>: jl 0x56556276 <main+141>
0x56556298 <+175>: cmp DWORD PTR [ebp-0x1c],0x321
0x5655629f <+182>: jne 0x565562bb <main+210>
0x565562a1 <+184>: call 0x565562d9 <comp_key>
0x565562a6 <+189>: sub esp,0x8
0x565562a9 <+192>: push eax
0x565562aa <+193>: lea eax,[ebx-0x1fc4]
0x565562b0 <+199>: push eax
0x565562b1 <+200>: call 0x56556040 <printf@plt>
0x565562b6 <+205>: add esp,0x10
0x565562b9 <+208>: jmp 0x565562cd <main+228>
0x565562bb <+210>: sub esp,0xc
0x565562be <+213>: lea eax,[ebx-0x1fa2]
0x565562c4 <+219>: push eax
0x565562c5 <+220>: call 0x56556050 <puts@plt>
0x565562ca <+225>: add esp,0x10
0x565562cd <+228>: nop
0x565562ce <+229>: lea esp,[ebp-0xc]
0x565562d1 <+232>: pop ecx
0x565562d2 <+233>: pop ebx
0x565562d3 <+234>: pop esi
0x565562d4 <+235>: pop ebp
0x565562d5 <+236>: lea esp,[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>: cmp eax,DWORD PTR [ebp-0x24])
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x56556289 <main+160>: add DWORD PTR [ebp-0x1c],eax
0x5655628c <main+163>: add DWORD PTR [ebp-0x20],0x1
0x56556290 <main+167>: mov eax,DWORD PTR [ebp-0x20]
=> 0x56556293 <main+170>: cmp eax,DWORD PTR [ebp-0x24]
0x56556296 <main+173>: jl 0x56556276 <main+141>
0x56556298 <main+175>: cmp DWORD PTR [ebp-0x1c],0x321
0x5655629f <main+182>: jne 0x565562bb <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 Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex
0 00 NUL 16 10 DLE 32 20 48 30 0 64 40 @ 80 50 P 96 60 ` 112 70 p
1 01 SOH 17 11 DC1 33 21 ! 49 31 1 65 41 A 81 51 Q 97 61 a 113 71 q
2 02 STX 18 12 DC2 34 22 " 50 32 2 66 42 B 82 52 R 98 62 b 114 72 r
3 03 ETX 19 13 DC3 35 23 # 51 33 3 67 43 C 83 53 S 99 63 c 115 73 s
4 04 EOT 20 14 DC4 36 24 $ 52 34 4 68 44 D 84 54 T 100 64 d 116 74 t
5 05 ENQ 21 15 NAK 37 25 % 53 35 5 69 45 E 85 55 U 101 65 e 117 75 u
6 06 ACK 22 16 SYN 38 26 & 54 36 6 70 46 F 86 56 V 102 66 f 118 76 v
7 07 BEL 23 17 ETB 39 27 ' 55 37 7 71 47 G 87 57 W 103 67 g 119 77 w
8 08 BS 24 18 CAN 40 28 ( 56 38 8 72 48 H 88 58 X 104 68 h 120 78 x
9 09 HT 25 19 EM 41 29 ) 57 39 9 73 49 I 89 59 Y 105 69 i 121 79 y
10 0A LF 26 1A SUB 42 2A * 58 3A : 74 4A J 90 5A Z 106 6A j 122 7A z
11 0B VT 27 1B ESC 43 2B + 59 3B ; 75 4B K 91 5B [ 107 6B k 123 7B {
12 0C FF 28 1C FS 44 2C , 60 3C < 76 4C L 92 5C \ 108 6C l 124 7C |
13 0D CR 29 1D GS 45 2D - 61 3D = 77 4D M 93 5D ] 109 6D m 125 7D }
14 0E SO 30 1E RS 46 2E . 62 3E > 78 4E N 94 5E ^ 110 6E n 126 7E ~
15 0F SI 31 1F US 47 2F / 63 3F ? 79 4F O 95 5F _ 111 6F o 127 7F DEL
然后我们将字符作为输入,执行程序即可得到FLAG
# ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 rrrrrru
[+] The flag is: SAYCURE{L3ts_g3t_in2_R3} [+]
# 7、说明
本文由笔者在原文基础上编译,转载请注明来源 原文:
Linux Reverse Engineering CTFs for Beginners | 🔐Blog of Osanda https://osandamalith.com/2019/02/11/linux-reverse-engineering-ctfs-for-beginners/