初识栈溢出
认识栈结构
栈这种结构学过数据结构的都知道,是一种先进后出的结构,类似于子弹放进弹夹一样,先放进的子弹最后打出。
函数调用过程
这个知识是整个栈方面的关键知识,我在大一的时候学pwn怎么也学不会,就是忽略了对基础知识的学习,直接学漏洞,一直搞不清怎么回事,非常郁闷。现在详细总结一下函数调用过程,以X86系统为例
-
压入参数
根据调用约定压入参数,main函数作为调用者,首先将fun的参数a,b...压栈。栈是向下生长的,先压入的参数靠近栈顶esp,后压入的靠近栈底edp -
返回地址压入
fun函数调用完成,程序需要返回源地址继续执行程序,那么我们就去要保存调用函数下一句的地址,把它压入栈中,以便我们恢复原程序继续执行 -
fun函数运行
fun函数运行会给自己再开辟一个栈,这个栈的栈底就是上个栈的栈顶,因为当调用函数时,执行call命令,会执行这几条汇编‘push edp;mov edp ,esp;sub esp,0x ’这句话可以画图来体会一下。push edp;是为了保存调用函数的栈帧,调用函数结束后要恢复原函数的edp,esp。mov edp ,esp;把esp的值赋值给edp,这样就把新栈帧的栈底确定了。sub esp ,0x,这句话为栈开辟空间,esp就确定了,完成一个新栈开辟
-
fun函数返回
当函数运行完成之后,函数这么返回呢?函数一般会执行‘leave;ret;’这句话什么含义,就是‘mov esp ,edp;pop edp;pop eip’ 我们来解读一下,首先把栈顶移动到栈底,相当于恢复栈顶,仔细想一想,是不是fun调用的时候,把edp移动到esp。然后pop edp 把栈中压入的main_edp弹出赋值给edp,这样我们就恢复了edp,然后再pop eip,把压入的main_addr弹出赋值给eip程序控制流就有回到了call的下一句
函数调用过程中涉及操作指令
- 压栈(push):栈顶指针esp减小4字节;以字节为单位将寄存器数据(4字节,不足补0)压入堆栈,从高到低依次将数据存人esp-1、esp-2、esp-3、esp-4指向的地址单元。
- 出栈(pop):栈顶指针esp指向的栈中数据被取回寄存器;栈顶指针esp增加4字节。push和pop指令在不同系统上运行时稍有不同,在64位系统中变化的大小是8字节,在32位系统中变化的大小是4字节。
- 调用(ca11):将当前的指令指针eip(该指针指向ca11指令后的下条指令)压入堆栈,以返回时能恢复执行下条指令。然后,设置eip指向被调函数的开始处,以跳转到被调函数的入口地址处执行。
- 离开(1eave):恢复主调函数的栈帧以准备返回,它等价于以下指令序列:
mov esp,ebp(恢复原esp值,指向被调函数栈帧开始处);
pop ebp(恢复原 ebp 的值,即主调函数帧基指针) - 返回(ret):与ca11指令配合,用于从函数或过程返回。从栈顶弹出返回地址(之前 ca11指令保存的下条指令地址)到eip寄存器中,程序转到该地址处继续执行(此时 esp指向进人函数时的第一个参数)。若带有立即数,esp要加上立即数(丢弃一些在执行ca11指令前入栈的参数)。使用该指令前,应使当前栈顶指针所指向位置的内容正好是先前ca11指令保存的返回地址。
初识栈溢出
当我们调用一个函数的时候,在函数内部存在,栈溢出漏洞,get函数或者开辟空间大于变量距离栈底的位置,那么就可能造成栈溢出,溢出后如果溢出值覆盖了main_addr,函数的返回地址就会变化,我们可以根据这个特点,篡改返回地址到我们想返回的。看下图,距离栈底0x80个字节,但是允许读取0x200个字节,就会造成栈溢出,接下来我们来小试牛刀
例题:https://gitee.com/tky5216/CTF/raw/master/PWN/stack/ret2text
查看保护
打开IDA反编译看看代码逻辑
这里有两种判断偏移的方式
- 第一种就是看图片上红框部分,显示s距离ebp为0x108个字节所以需要填充0x108+0x4个字节的数据才能覆盖返回地址
- 使用pwngdb中cyclic判断偏移,在call gets处下断点,用gdb调试
用cyclic生成有规律的字符串,输入c把字符串输入运行
得到一个异常返回地址,程序停止
用命令cyclic -l 加返回地址算出偏移
这里的到的偏移就不用算edp的大小了,因为已经包括edp了
有了偏移我们找一下后门函数
这题也算很仁慈,直接给出了后门函数,我们随着学习的深入,一般不会直接给出后门函数,需要使用各种技巧来进入shell
接下来开始编写脚本
from pwn import * p = process("./ret2text") target = 0x0804850B p.recvuntil("ret2text\n") payload = b"a" * 0x108 + p32(1) + p32(target) p.sendline(payload) p.interactive()