You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何确定栈中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栈帧的通用结构和编译器行为:

  1. 栈帧基础结构:32位下函数调用时,栈帧会依次压入返回地址、旧EBP,然后是Canary、局部变量。
  2. 从实验现象反推:
    • 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 06:20:40