如果调用堆栈向上增长会使缓冲区溢出更安全吗?

Kel*_*ang 5 x86 assembly stack intel buffer-overflow

每个线程都有自己的堆栈来存储局部变量.但是堆栈也用于在调用函数时存储返回地址.

在x86程序esp集中,指向最近分配的堆栈末尾.今天,大多数CPU的堆栈增长都是负面的.此行为通过溢出缓冲区并覆盖保存的返回地址来启用任意代码执行.如果堆栈要积极增长,这种攻击是不可行的.

使调用堆栈向上增长更安全吗?为什么英特尔设计8086随着堆栈的增长而下降?他们可以在任何后来的CPU中改变一些东西,让现代x86的堆栈向上增长吗?

Pet*_*des 6

有趣的一点;大多数缓冲区溢出确实会超过结束,而不是开始之前,所以这几乎肯定会有所帮助。编译器可以将本地数组放在堆栈帧中的最高地址,这样就不会覆盖位于数组后面的任何标量本地变量。

但是,如果将本地数组的地址传递给另一个函数,仍然存在危险。因为被调用函数的返回地址将位于数组末尾的后面。

unsafe() {
    char buf[128];
    gets(buf);      // stack grows upward: exploit happens when gets executes `ret`
    // stack grows down: exploit happens when the `ret` at the end of *this* function executes.
}
Run Code Online (Sandbox Code Playgroud)

因此,可能仍然可能发生大量缓冲区溢出。这个想法仅在不安全的数组写入代码被内联时才能避免缓冲区溢出,因此溢出发生时数组上方没有任何重要的内容。

但是,缓冲区溢出的其他一些常见原因可以轻松内联,例如strcat. 向上增长的堆栈有时会有所帮助。

安全措施不一定要万无一失才有效,所以这有时肯定会有所帮助。对于想要改变 x86 等现有架构的人来说,这可能还不够,但对于新架构来说,这是一个有趣的想法。不过,堆栈向下增长几乎是 CPU 的通用标准。有什么使用向上增长的调用堆栈吗?有多少软件实际上依赖于这个假设?希望不要太多...


传统的布局为堆和/或堆栈的增长留下了空间,只有当它们在中间相遇时才会出现问题。

可预测的代码/数据地址比可预测的堆栈地址更重要,因此具有更多 RAM 的计算机可以将堆栈放置得离数据/代码更远,同时仍然在恒定地址加载代码/数据。(这是非常手工的。我认为自己很幸运没有编写过实际的 16 位程序,并且只了解但没有使用过分段。也许仍然记得 DOS 的人可以在这里阐明为什么它可以很好地使用堆栈位于高地址,而不是在段底部向上增长的堆栈和顶部的数据/代码。例如,使用“微小”代码模型,其中所有内容都在一个段中)。


改变这种行为的唯一真正机会是 AMD64,这是 x86 第一次真正破坏了向后兼容性。现代 Intel CPU 仍然支持 8086 个未记录的操作码,例如D6SALC(Set AL from Carry Flag),限制了 ISA 扩展的编码空间。(例如,如果英特尔放弃对未记录操作码的支持,SSSE3 和 SSE4 指令将缩短 1 个字节。

即便如此,也只是针对新模式;AMD64 CPU 仍然必须支持传统模式,并且在 64 位模式下,它们必须将长模式与兼容模式混合在一起(通常是从 32 位二进制文​​件运行 32 位用户空间进程)。

AMD64 或许可以添加堆栈方向标志,但这会使硬件更加复杂。正如我上面所说,我认为这不会给安全带来很大的好处。否则,也许 AMD 架构师会考虑这一点,但可能性仍然不大。他们的目标肯定是尽量减少侵入性,但不确定它会流行起来。如果世界大多只是继续运行 32 位操作系统和 32 位代码,他们不想承受额外的负担来维持 CPU 中的 AMD64 兼容性。

这是一种耻辱,因为他们可以做很多小事情,而这些事情可能不需要执行单元中太多额外的晶体管。(例如,在长模式下,替换setcc r/m8setcc r/m32)。