在缓冲区溢出中,新的返回地址如何完美对齐?

问题描述

我很困惑,在基于堆栈的缓冲区溢出中,如果我用该堆栈元素中的新目标地址覆盖程序的返回地址,这些地址如何对齐或如何确保发生这种情况?例如:假设在堆栈帧中,我在地址 0x1 处有一个变量元素(尽管 0x1 太低而无法发生),但我在地址 0x6 处有一个返回地址元素。现在,如果架构在我的处理器中是 32 位,那么地址将为 4 个字节。因此,如果程序要求我提供一个参数,它将放入该缓冲区,并且我重复提供该地址两次,那么显然该地址元素将具有新地址的第二个字节。因此地址未对齐,程序将崩溃。我将如何确保地址始终对齐?

let new address = 0x bf ff ff 3c

address from buffer to return address element:  | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | ....
                                               |     |     |     |     |     |     |     
newly written return address layout in memory: | 3c  | ff   | ff   | bf  | 3c  | ff   | ....

解决方法

您选择要溢出的内容。使用正确的长度将返回地址与其相对于您正在利用的二进制文件堆栈帧中的缓冲区的位置对齐。

那个距离是一个编译时常数(或者至少 n%16 是常数,如果它是 VLA 或 alloca...)所以你可以用反汇编器找到它。编译器甚至会选择将本地数组对齐 161。或者在不需要 16 字节堆栈对齐的 32 位 ABI 上至少增加 4。因此,即使您没有要利用的二进制文件的副本,您仍然可以按照您的建议在相对于开头的 4 对齐的位置重复返回地址 缓冲。当然,如果没有二进制文件,知道要使用的返回地址(用于 ROP 攻击)可能会很困难,除非有些库没有经过 ASLR。

(例如,x86-64 System V ABI 要求所有数组 >=16 字节和所有 VLA,尽管私有本地数组在单个函数之外不可见。)