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

时间:2016-08-28 01:37:01

标签: assembly x86 stack intel buffer-overflow

每个线程都有自己的堆栈来存储局部变量。但是在调用函数时,堆栈也用于store return addresses

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

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

1 个答案:

答案 0 :(得分:3)

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

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

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.
}

因此可能仍然存在很多的缓冲区溢出。当不安全的数组写入代码被内联时,这个想法只会破坏缓冲区溢出,因此在数组上方没有任何重要的事情发生溢出。

但是,缓冲区溢出的其他一些常见原因很容易内联,如strcat向上增长的筹码有时会有所帮助。

安全措施不一定非常有用,所以这肯定有时会有所帮助。可能还不足以让任何人想要改变像x86这样的现有架构,但对于新架构来说是一个有趣的想法。然而,Stack-grow-down是CPU中几乎通用的标准。有什么东西使用向上增长的调用堆栈吗?多少软件实际上取决于这个假设?希望不多......

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

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

改变这种行为的唯一真正机会是使用AMD64 ,这是x86第一次真正破坏向后兼容性。现代英特尔CPU仍然支持8086未记录的操作码,如D6: SALC (Set AL from Carry Flag),限制了ISA扩展的编码空间。 (例如SSSE3 and SSE4 instructions would be 1 byte shorter如果英特尔放弃对无证件操作码的支持。

即使这样,它也只适用于新模式; AMD64 CPU仍然必须支持传统模式,而在64位模式下,它们必须将长模式与compat模式混合(通常是从32位二进制文​​件运行32位用户空间进程)。

AMD64可能已经添加了堆栈方向标志,但这会使硬件变得更加复杂。正如我上面所论述的那样,我认为这不会对安全产生重大影响。否则,也许AMD架构师会考虑它,但仍然不太可能。他们肯定是针对极少侵入性的,并且不确定它会流行起来。如果全世界大多只是运行32位操作系统和32位代码,那么他们不想再忍受额外的行李来维持AMD在CPU中的兼容性。

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