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

32位MSVC Debug环境下b覆盖返回地址为&f后为何b与f被调用两次?

问题分析:栈溢出修改返回地址后的执行流程

首先明确核心逻辑:这段代码通过栈越界写入修改了函数b的返回地址,让b执行完毕后跳转到f;而f执行完毕后回到main并再次调用b,本质是栈上的另一个关键地址被意外修改,形成了循环执行链。

1. 32位函数调用的栈基础

在32位x86架构的Debug编译模式下(从汇编中的__RTC_CheckStackVars、0CCCCCCCCh填充栈空间可判断),函数调用的栈帧布局如下:

  • 当main调用b时,call指令会将main中call b的下一条指令地址(00472507)压入栈,作为b的返回地址。
  • 进入b后,首先执行push ebp(将main的ebp值压栈),再mov ebp, esp,此时ebp指向当前栈帧的基址。
  • 后续sub esp, 0CCh分配局部变量空间,push ebx/esi/edi保存寄存器值,局部数组m就位于ebp下方的栈空间中。

2. m[3] = &f的实际作用

代码中int* m[1]定义了一个仅含1个元素的指针数组,但m[3]属于越界访问:

  • 32位下每个指针占4字节,m[3]的地址为m的起始地址 + 3*4 = m起始地址 + 12字节。
  • 结合汇编代码mov dword ptr m[ecx], offset f(ecx=12),这个地址最终指向ebp+4——也就是b的返回地址存储位置。

所以这行代码的实际效果是:将b的返回地址从main的00472507替换为f的入口地址。

3. f执行完毕后为何回到main并再次调用b

当b执行ret指令时,会弹出栈顶的f地址到eip,跳转到f执行:

  • f的栈帧创建和销毁流程与普通函数一致,执行到ret时,会弹出当前栈顶的地址到eip。
  • 这里的栈顶地址,正是main调用b时最初压入的00472507吗?不——因为在Debug模式下,b的栈越界写入还意外修改了main栈帧中的某个关键地址:
    当m[3]写入f的地址时,由于栈空间在Debug模式下有额外的填充和对齐,实际覆盖了main栈帧中ret指令的返回地址,将其修改为main中call b的地址(00472502)。
  • 因此f执行ret后,eip指向00472502,也就是main中调用b的位置,从而再次触发b的调用,形成main→b→f→main→b...的循环。

验证关键点

从汇编代码可佐证:

  • main中call b的地址是00472502,b的返回地址原本是00472507;
  • f的ret指令如果跳转到00472502,就会重复执行call b,符合你观察到的现象。

内容的提问来源于stack exchange,提问作者Oliver

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 19:25:53