You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

picoCTF Buffer Overflow 2:栈覆盖传递win函数参数原理咨询

picoCTF Buffer Overflow 2:栈传参的底层原理

要搞懂这个机制,得先吃透x86平台cdecl调用约定和栈帧切换的核心逻辑,这是整个溢出操作的基础。

1. 先明确两个关键规则

  • cdecl调用约定:调用函数时,参数要从右往左压入栈;被调用函数用ebp(栈帧基址寄存器)作为基准取参数,第一个参数在ebp+8,第二个在ebp+12(因为ebp本身占4字节,加上返回地址的4字节,所以第一个参数偏移是8)。
  • 栈帧结构:每个函数执行时会创建自己的栈帧,结构大概是(地址从高到低,栈向下生长):
    [高地址]
    → 函数参数
    → 返回地址(当前函数执行完要跳回的位置)
    → 保存的旧ebp(上一个栈帧的基址)
    → 局部变量(比如题目里的缓冲区buf)
    [低地址]
    

2. 漏洞函数的栈布局与溢出过程

假设题目里的漏洞函数是vuln,它有一个缓冲区buf,存在ebp-0x40的位置(偏移0x40)。当我们输入超长数据时:

  1. 先填满buf(用0x40字节的垃圾数据);
  2. 接着覆盖ebp位置的旧ebp值(再填4字节,内容无所谓);
  3. 然后覆盖ebp+4位置的返回地址,把它改成win函数的地址;
  4. 继续输入的内容,就会存在返回地址的上方(更高地址),也就是vuln栈帧里的ebp+8、ebp+12位置。

3. 跳转到win函数后的参数读取逻辑

当vuln执行到ret指令时,会做两件事:

  • 把栈顶的win地址弹出到EIP(程序计数器),让程序跳到win函数执行;
  • esp(栈顶寄存器)加4,指向我们填在返回地址后面的第一个数据。

接下来win函数启动,执行栈帧初始化(prologue):

  1. push ebp:把当前ebp(也就是我们刚才覆盖的旧ebp值)压入栈,esp减4;
  2. mov ebp, esp:把ebp设置成当前esp,作为自己栈帧的基址。

此时win的栈帧结构就和正常调用时完全一致了:

  • ebp+4:win的返回地址(就是我们填在返回地址后面的第一个垃圾数据,无所谓是什么);
  • ebp+8:第一个参数(我们填在vuln的ebp+8位置的数据);
  • ebp+12:第二个参数(我们填在vuln的ebp+12位置的数据)。

这样win函数就能通过ebp+8和ebp+12正确读取到我们传入的参数,完成执行。

简单说,我们就是通过溢出,在栈上伪造了一个win函数被正常调用时的参数布局,让win以为自己是被合法调用的,从而读取我们塞进去的参数。

内容的提问来源于stack exchange,提问作者יאיר צפוני

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 21:13:26