Stack Smashing 报错意味着GCC的栈溢出防护机制(SSP)成功拦截了缓冲区溢出,程序被主动终止而非崩溃;其核心是通过在栈帧中插入随机canary值并校验,防止返回地址被篡改。

Stack Smashing 报错意味着什么,不是崩溃而是拦截成功
终端打出 *** stack smashing detected ***: terminated 时,程序没“出 bug”,恰恰说明防护机制生效了。这不是运行时异常,而是 GCC 内置的 Stack Smashing Protector(SSP)在函数返回前校验失败后主动终止进程。它不等你覆盖完返回地址、不等 shellcode 执行,就在控制流劫持发生前一步掐断执行——所以看到这句提示,第一反应不该是“怎么修崩溃”,而是“哪段代码越界写了栈内存”。
Canary 值插在哪、怎么生成、谁来验证
Canary 不是编译期写死的常量,而是在程序启动时由内核或 libc 随机生成的一个值(通常存于 gs:0x14 或 fs:0x28 等 TLS 区域),每次运行都不同。编译器启用 -fstack-protector 后,会在每个**有局部数组或可能溢出风险的函数**栈帧中,把该值拷贝一份,放在局部变量和保存的返回地址之间。
函数返回前,编译器自动插入校验逻辑:cmp %canary_in_stack, %canary_from_tls;若不等,跳转到 __stack_chk_fail 终止进程。关键点在于:
- Canary 不保护所有函数——
-fstack-protector只对含数组的函数插桩;-fstack-protector-strong扩展到含地址取址(&)、alloca 调用等更多场景 - Canary 无法防住覆盖局部变量本身(比如改掉某个 flag 变量),只防覆盖返回地址或
longjmp缓冲区 - 如果攻击者能先泄露 Canary 值(如通过格式化字符串漏洞),后续溢出就可绕过校验
哪些 C/C++ 写法会直接触发 Canary 失败
不是所有越界都会触发报错,但以下写法几乎必中:
立即学习“C++免费学习笔记(深入)”;
-
gets(buffer):已废弃,无长度参数,输入任意长都写进栈 -
strcpy(buffer, src):不检查src长度,只要src比buffer长就溢出 -
sprintf(buffer, "%s", src):同strcpy,且易被格式化字符串利用 -
scanf("%s", buffer):未指定宽度,等价于无界读取 - 手动循环写入时漏判
i 边界
注意:strncpy 和 snprintf 并非绝对安全——strncpy(dst, src, n) 若 src 长度 ≥ n,不会补 \0,后续 strlen 或 strcpy 可能再次越界;snprintf 虽安全,但若误用 snprintf(buf, sizeof(buf), ...) 却传入错误的 sizeof(比如对指针用 sizeof),照样溢出。
验证 Canary 是否启用及调试溢出点的实操方法
别靠猜,用工具确认:
- 检查编译选项:
readelf -s ./a.out | grep __stack_chk—— 若有__stack_chk_fail符号,说明启用了 SSP - 反汇编看关键函数:
objdump -d ./a.out | grep -A20 vulnerable_function—— 查找mov %gs:0x14,%reg和结尾处的call __stack_chk_fail - GDB 中定位溢出位置:
gdb ./a.out→run $(python3 -c "print('A'*72)")→ 崩溃后info registers看 Canary 寄存器值是否被改写,再x/20x $rsp观察栈上布局 - 临时关闭防护验证逻辑:
gcc -fno-stack-protector -z execstack编译(仅测试环境!),此时溢出会直接 segfault 或跳转,便于对比分析
真正难的不是加防护,而是当 Canary 触发时,你得快速区分:是输入恶意构造导致的攻击尝试,还是开发阶段遗留的边界遗漏?后者往往藏在多层函数调用、动态长度计算、或跨平台 size_t 与 int 混用里——这些地方,连 -fstack-protector-strong 也救不了你。


















