C 代码的 x86 反汇编生成:orq $0x0, %(rsp)

Rwi*_*ami 1 c assembly gcc callstack x86-64

我编写了以下 C 代码: 在此处输入图片说明

它只是分配一个包含 1000000 个整数的数组和另一个整数,并将数组的第一个整数设置为 0

我编译这个使用 gcc -g test.c -o test -fno-stack-protector

它给出了一个非常奇怪的反汇编:在此处输入图片说明

显然,它在循环中不断在堆栈上分配 4096 个字节,并且每 4096 个字节与 0“或”,然后一旦达到 3997696 个字节,它就会进一步分配 2184 个字节。然后继续将第 4000000 个字节(从未分配过)设置为 5。

为什么不分配请求的全部 4000004 字节?为什么它在每 4096 个字节“或”一个 0,这是一条无用的指令?

我在这里理解错了吗?

注意:这是用 gcc 9.3 版编译的。gcc 7.4 版不执行循环和“或”每 4096 个字节为 0,但它只分配了 3997696+2184=3999880 个字节,但仍将第 4000000 个字节设置为 5

R..*_*R.. 9

这是Stack Clash类漏洞的缓解措施,自 90 年代或更早以来就已为人所知,但仅在 2017 年才广泛宣传。(请参阅stack-clash.txt此博客条目。)

如果攻击者可以安排一个具有攻击者控制大小的 VLA 的函数来执行,或者可以安排一个具有大型固定大小数组的函数在攻击者以其他方式控制已经使用的堆栈数量时执行,他们会导致栈指针被调整到指向其他内存的中间,从而导致函数破坏该内存,通常会导致任意代码执行。

GCC 在这里发出的机器代码是堆栈冲突保护功能的一部分。它通过(粗略地)在将堆栈指针调整超过最小页面大小时,一次以一个最小页面大小的单位增量移动它并在每次调整后访问内存来降低风险。这确保,如果至少存在一个保护页(页映射PROT_NONE),则访问将出错并在调整到无关内存之前生成一个信号。主线程总是有保护页,默认情况下新创建的也有保护页(大小可以在 pthread 线程创建属性中配置)。

  • [Linux 进程堆栈因局部变量溢出(堆栈保护)](/sf/ask/4204121141/) 有一些有关 Linux 堆栈分配的更多信息。和这个检查。看起来这段代码是用“gcc -fstack-check”编译的,显然在 OP 的 GCC 发行版中默认是打开的。 (3认同)

Ant*_*ala 6

这里有两件事:

  • “无操作”OR读取写入堆栈上的每个页面。这些是必需的,因为堆栈通常被映射,以便堆栈下方有一个保护页。当触摸保护页时,堆栈向下展开。但是,如果您触摸保护页下方的内存,则会发生 SIGSEGV。

  • x86-64 System-V ABI 指定堆栈指针下方的128 字节红色区域。 编译器也可以自由使用该区域来存储局部变量。如果将 128 添加到 3997696,您将得到 4000008。请注意,堆栈始终必须至少对齐到 8,而不是 4,以便任何 int64_t 或 double 都可以对齐(正如 Peter Cordes 所指出的,较大的数组需要对齐)是 16 字节对齐的,因此要求整个堆栈也必须是 16 字节对齐的),所以 40000004 显然是错误的!