C程序中函数返回后跳过代码行,10字节增量的技术意义是什么?
为什么修改返回地址时要加10?底层原理是什么?
先看你给出的示例代码及运行结果:
#include <stdio.h> void f() { unsigned int *array = (unsigned int *) &array; array[4] = array[4] + 10; } void main() { f(); printf("I am skipped\n"); }
编译运行后无输出,说明printf确实被成功跳过,下面拆解底层逻辑和数字10的意义:
核心逻辑:篡改函数返回地址
这段代码的本质是利用栈布局,定位到函数f()的返回地址(即main函数中f()调用完成后,CPU要执行的下一条指令地址),通过修改该地址,让CPU直接跳过printf的执行流程。
为什么用索引4定位返回地址?
当执行unsigned int *array = (unsigned int *) &array;时,array指针指向了自身在栈上的存储地址。在x86架构、gcc 7.5.0默认编译选项下,函数f()的栈帧布局大致为:
- 高地址区域:保存的EBP寄存器值(占4字节)
- 局部变量
array的存储空间(占4字节) - 中间栈对齐或隐含填充数据
- 低地址区域:函数调用时压入的返回地址(占4字节)
从array的位置到返回地址,正好间隔了4个unsigned int的长度(每个4字节,共16字节),因此array[4]能精准定位到返回地址的位置。
数字10的来源:指令字节总长度
10是main函数中,从f()返回后的第一条指令,到printf执行完毕后下一条指令的总字节数。我们可以通过编译后的汇编代码验证:
main函数关键部分的汇编(简化版):
main: push ebp mov ebp, esp call f ; 调用f,此时返回地址是下一条指令的地址 push OFFSET FLAT:.LC0 ; 压入字符串地址,指令占4字节 call printf ; 调用printf,指令占5字节 add esp, 4 ; 清理栈,指令占1字节 ; 后续是main函数返回的指令
从返回地址(push OFFSET FLAT:.LC0的地址)开始,到跳过printf需要跳到add esp,4之后的指令,这段指令的总长度是4+5+1=10字节。把返回地址加10后,CPU会直接跳到这段指令的末尾,完全跳过printf的执行。
注意:这个数字是环境依赖的
10这个值仅在x86架构、gcc 7.5.0、默认无优化编译选项下有效。如果切换到x64架构、开启编译优化(比如-O2)、更换编译器版本,指令序列的长度会发生变化,对应的增量值也会不同。
内容的提问来源于stack exchange,提问作者Jason Ivey
相关产品推荐
相关产品推荐

