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
相关产品推荐
相关产品推荐

