通过蛮力克服堆栈随机化

overcome stack randomization by brute force

我正在读一本书,书中介绍了缓冲区溢出攻击的原理和一种阻止它的技术,称为 Stack Randomization,下面引用自该书:

顽固的攻击者可以通过暴力破解随机化,反复尝试使用不同的地址进行攻击。一个常见的技巧是在实际利用代码之前包含一长串 nop(发音为“no op”,“no operation”的缩写)指令。执行这条指令没有任何效果,除了将程序计数器递增到下一条指令。只要攻击者能猜出这个序列中某处的地址,程序就会运行遍历这个序列,然后命中漏洞利用代码。如果我们设置一个 256 字节(28)的 nop sled,那么 n = 223 上的随机化可以通过枚举 215 = 32,768个起始地址

我理解第一部分,但不明白关于枚举起始地址的第二部分。例如%rsp指向当前的起始地址如下图所示(为简单起见只显示8个字节而不是256个字节)

我认为作者的意思是,尝试猜测 %rsp 指向的堆栈内存的不同地址。而return地址和%rsp之间的padding都是nop,然后用猜测的地址覆盖return地址,极有可能指向部分padding(nop).但是由于 Stack Randomization 在程序开始时在堆栈上的 0 到 n 字节之间分配了一个随机数量的 space,所以我们只能说攻击成功的概率是 215/223 = 0.78%,怎么说试32768个(固定数)地址就能破解呢?和抛硬币一样,只能说正面的概率是0.5,不能说第二轮就正面,因为有可能反面

Stack Randomization allocats a random amount of space between 0 and n bytes on the stack at the start of a program

不,它不分配。它随机化虚拟地址 space 中堆栈映射的位置,而其他页面未映射。这是页面粒度。

猜测错误通常会导致受害程序出现段错误(或者 OS 在无效页面错误上所做的任何事情)。这对任何入侵检测来说都是嘈杂和明显的。 如果程序最终重新启动以便您可以重试,它的堆栈地址将按照您的建议重新随机化,

落入有效内存但未落入 NOP sled 的错误猜测通常也会因无效指令或解码为无效内存访问的内容而很快崩溃。

所以是的,你是对的,你不能只枚举地址space,这只是一个概率问题。尝试足够多的次数就意味着它很可能你成功了,没有保证。并且尝试枚举可能的 ASLR 熵并不是特别有用,或者比我每次都猜测同一个页面更有可能成功。

但是在单个页面中尝试不同的偏移量很有用,因为 OS 驱动的堆栈 ASLR 仅具有页面粒度。

环境变量和命令行参数使用了一定量的堆栈 space,这会因系统而异,但我认为在同一程序的调用中往往是恒定的。除非从不同的调用链到达易受攻击的函数,或者在父函数中有一个可变大小的堆栈分配,在这种情况下,页面内缓冲区的偏移量可能每个 运行.


当然,现在大多数进程 运行 都使用不可执行的堆栈,这使得堆栈缓冲区无法直接注入代码。如果存在任何已知的静态数据或代码地址,则可能会发生 ROP 攻击(将 return 地址注入现有的字节序列并解码为有趣的内容)。如果不是,您必须处理 code/data 的 ASLR,但您无法注入 NOP sled。