如何确定栈中Canary与返回地址的位置?基于给定代码的技术咨询
栈结构分析与疑问解答
实验场景与代码
我有一份教授提供的代码,他通过取消注释部分代码演示栈的工作原理,但我不太理解。代码如下:
#include <stdio.h> int main(int argc, char** argv); void loop() { int buf[1]; printf("%08X %08X %08X\n", buf, loop, main); printf("%08X %08X %08X %08X\n", buf[1], buf[2], buf[3], buf[4]); printf("%08X %08X %08X %08X\n", buf[5], buf[6], buf[7], buf[8]); printf("loop\n"); //buf[1] = loop;//canary alert //buf[2] = loop;// messes up the exit of main //buf[3] = 0XF0F0F0F0; //buf[4] = &(buf[0]); //buf[4] = loop; //buf[5] = &(buf[0]); //buf[5] = loop; printf("%08X %08X %08X %08X\n", buf[1], buf[2], buf[3], buf[4]); printf("%08X %08X %08X %08X\n", buf[5], buf[6], buf[7], buf[8]); printf("loop returns\n"); } int main(int argc, char **argv) { printf("Main\n"); loop(); printf("main terminates\n"); }
我在32位Ubuntu 16.04 LTS虚拟机中运行该程序,测试结果:
- 取消注释
buf[1] = loop;时,触发stack smashing(栈溢出),因为修改了canary; - 取消注释
buf[2] = loop;时,程序执行完printf("main terminates\n")后触发segmentation fault(段错误); - 取消注释
buf[3] = 0XF0F0F0F0;时,main执行结束前就触发段错误; - 推测取消注释
buf[4]或buf[5]相关代码会修改返回地址。
疑问解答
1. 如何确定canary的位置?是否可默认它位于已声明变量之后的首个栈位置?
在32位Linux默认栈布局(开启栈保护-fstack-protector时),栈帧结构大致为:
高地址 → ... 函数参数 返回地址 EBP(基址指针) Canary(栈保护值) 局部变量(如buf[1]) 低地址 → ...
栈是向下生长的,所以局部变量的地址比Canary更低(数值更小)。你代码中的buf是局部数组,buf[1]属于越界访问,刚好命中Canary的位置,这也是修改它会触发栈溢出检测的原因。
但不能绝对默认Canary是局部变量后的首个栈位置:
- 编译器可能对局部变量做对齐优化,如果局部变量大小不是栈对齐单位(32位下通常是4字节)的整数倍,会插入填充字节;
- 若函数有多个局部变量,Canary会放在所有局部变量的高地址侧(靠近EBP的方向),而非单个变量之后。
不过在这个实验代码中,buf是单个4字节的int数组,刚好对齐,所以Canary就紧邻buf的高地址侧,也就是buf[1]的位置。
2. 仅通过代码如何确定返回地址的位置?
仅通过代码推导需要结合32位x86栈帧的通用结构和编译器行为:
- 栈帧基础结构:32位下函数调用时,栈帧会依次压入返回地址、旧EBP,然后是Canary、局部变量。
- 从实验现象反推:
- 修改
buf[2]导致main结束后段错误:buf[2]修改的是旧EBP的值(Canary之后是旧EBP)。当loop返回时,旧EBP会被恢复到EBP寄存器,若EBP被篡改,main函数结束时清理栈帧会访问错误地址,触发段错误; - 修改
buf[3]直接导致main结束前段错误:buf[3]对应返回地址的位置——当loop执行ret指令时,会跳转到被篡改的无效地址(0xF0F0F0F0),直接触发段错误; - 修改
buf[4]/buf[5]为loop会让程序跳转到loop执行,形成循环,这也符合返回地址被篡改的行为。
- 修改
仅通过代码推导的核心逻辑:
- 先明确局部变量的大小(这里
buf[1]是4字节); - 按照栈向下生长的顺序,依次推算:局部变量 → Canary(4字节) → 旧EBP(4字节) → 返回地址(4字节);
- 每个
buf[N]对应偏移N*4字节的栈空间(int是4字节),所以buf[0]是局部变量,buf[1]是Canary,buf[2]是旧EBP,buf[3]是返回地址,以此类推。
内容的提问来源于stack exchange,提问作者Ludovico Latini
相关产品推荐
相关产品推荐

