GCC编译C语言缓冲区溢出测试代码未触发段错误原因咨询
问题
在Numberphile频道的缓冲区溢出相关视频中了解到缓冲区溢出的实现方式后,我尝试自行复现。编写的测试代码如下,除buffer数组大小外和视频示例完全一致,但输入长度大于buffer大小的字符串时,并没有像视频演示那样触发段错误,想知道具体原因。已知编译器是影响结果的关键因素,我当前使用的编译器为GCC。
#include <stdio.h> #include <string.h> int main(int argc, char** argv){ char buffer[50]; strcpy(buffer, argv[1]); return 0; }
回答
复现失败的核心原因是现代GCC默认开启了多层栈安全防护,和视频里用的无任何防护的老旧32位编译环境行为差异极大,具体影响点如下:
- 栈空间对齐预留:GCC为了适配SIMD指令的内存对齐要求,不会严格按你声明的
char buffer[50]刚好分配50字节栈空间,会默认向上对齐到16字节整数倍,实际给buffer预留的栈空间有64字节。只要输入的字符串长度没超过预留空间+栈帧保存的寄存器值长度,根本碰不到栈上的返回地址,自然不会触发内存访问违规。 - 栈金丝雀(Stack Canary)保护:GCC默认编译时会在栈帧返回地址前插一个随机生成的校验值,缓冲区溢出要覆盖返回地址必然先篡改这个校验值,函数返回前校验失败会直接终止程序,根本走不到跳转非法地址触发段错误的流程。
- FORTIFY_SOURCE安全检查:默认编译优化等级下,GCC会联动glibc对
strcpy这类危险字符串函数做边界检查,如果检测到拷贝长度超过编译期可确定的缓冲区大小,会直接终止程序,不会执行无边界的内存拷贝。 - 架构差异:视频演示用的是32位系统,栈帧结构简单,返回地址只占4字节,溢出少量字节就能覆盖;现在主流是64位系统,栈帧有更多预留空间,需要输入更长的字符串才能碰到返回地址。
复现视频效果的编译方法
要得到和视频完全一致的运行结果,需要手动关闭GCC所有默认安全防护,指定32位编译模式,编译命令如下:
gcc -m32 -fno-stack-protector -D_FORTIFY_SOURCE=0 -O0 test.c -o vuln
各参数作用:
-m32:生成32位x86程序,栈帧布局和视频中的老旧环境一致-fno-stack-protector:关闭栈金丝雀校验机制-D_FORTIFY_SOURCE=0:关闭glibc对字符串操作函数的运行时边界检查,让strcpy执行无边界拷贝-O0:关闭所有编译优化,避免编译器调整栈布局或者直接优化掉buffer变量
注意:就算关完所有保护,也不是输入超过50字节就立刻报错。32位栈帧里,buffer往上先存着4字节的旧ebp寄存器值,再往上才是4字节的返回地址,至少要输入58字节以上的字符串才能覆盖到返回地址;输入长度不够的话只会覆盖未使用的栈预留空间,程序会正常退出,没有任何异常提示。
内容的提问来源于stack exchange,提问作者Mattia Suffredini
相关产品推荐
相关产品推荐

