每个线程都有自己的堆栈来存储局部变量。但是栈也习惯了store return addresses调用函数时。
在 x86 汇编中,esp
指向最近分配的堆栈端。今天,大多数 CPU 的堆栈都呈负增长。此行为通过溢出缓冲区并覆盖保存的返回地址来启用任意代码执行。如果堆栈正向增长,则此类攻击将不可行。
让调用堆栈向上增长是否更安全?为什么 Intel 设计 8086 时堆栈向下增长?他们能否在任何后来的 CPU 中改变一些东西,让现代 x86 拥有向上增长的堆栈?
最佳答案
有趣的一点;大多数缓冲区溢出确实会超过结尾,而不是在开头之前,因此这几乎肯定会有所帮助。编译器可以将局部数组放在堆栈帧中的最高地址,因此不会有任何标量局部变量要覆盖位于数组之后的位置。
但是,如果将本地数组的地址传递给另一个函数,仍然存在危险。因为被调用函数的返回地址将位于数组末尾之后。
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 这样的现有架构的人来说可能还不够,但是 新架构的有趣想法 .不过,堆栈增长几乎是 CPU 中的通用标准。有什么东西使用向上增长的调用堆栈吗?有多少软件实际上取决于该假设?希望不多...
传统的布局为堆和/或栈的增长留出了空间,只有当它们在中间相遇时才会出现问题。
可预测的代码/数据地址比可预测的堆栈地址更重要,因此具有更多 RAM 的计算机可以将堆栈与数据/代码放置得更远,同时仍然在恒定地址加载代码/数据。 (这是非常手动的。我认为自己很幸运没有写过实际的 16 位程序,并且只学习了但没有使用过分段。也许还记得 DOS 的人可以在这里阐明为什么它可以很好地使用堆栈在高地址,而不是段底部的向上增长堆栈和顶部的数据/代码。例如,使用“小”代码模型,其中所有内容都在一个段中)。
改变这种行为的唯一真正机会是使用 AMD64 ,这是 x86 第一次真正打破向后兼容性。现代 Intel CPU 仍然支持 8086 未记录的操作码,例如
D6
: SALC
(Set AL from Carry Flag) , 限制了 ISA 扩展的编码空间。 (例如 SSSE3 and SSE4 instructions would be 1 byte shorter 如果英特尔放弃了对未记录的操作码的支持。即便如此,它也只适用于新模式; AMD64 CPU 仍然必须支持传统模式,当处于 64 位模式时,它们必须将长模式与兼容模式混合使用(通常从 32 位二进制文件运行 32 位用户空间进程)。
AMD64 可能会添加一个堆栈方向标志,但这会使硬件更加复杂。正如我上面所说,我认为这不会对安全有很大好处。否则,也许 AMD 架构师会考虑它,但仍然不太可能。他们肯定的目标是将侵入性降至最低,并且不确定它会流行起来。如果世界上大多只是继续运行 32 位操作系统和 32 位代码,他们不想被额外的负担困住以保持其 CPU 中的 AMD64 兼容性。
真可惜,因为他们本来可以做很多小事,而执行单元中可能不需要太多额外的晶体管。 (例如,在长模式下,将
setcc r/m8
替换为 setcc r/m32
)。
关于assembly - 让调用堆栈向上增长会使缓冲区溢出更安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39187276/