他们只是假设 32 位系统上的最大总共享内存 (kbytes) 为 8388608,这是他们提出 2^23 的地方
2^15 的计算公式如下:
(8388608 / 256) = 32768 == 2^15
换句话说:
total_memory_size / NOP_sled_length = total_iterations_to_find_NOP_sled_in_memory
他们基于以下假设计算得出:我们的 NOP 雪橇可以位于从 0x0 一直到 0x800000(即 8388608 或 2^23)的范围内的任何位置。因为我们的 NOP sled 是 256 字节长,所以我们不需要为每次猜测/迭代/蛮力增加 1,而是每次计算增加 256,从而为我们提供上述等式 0x800000 / 256 = 32768 == 2^15 的结果。所以我们只需要暴力破解 32768 个可能的地址,因为其中一个地址会让我们在 NOP sled 的开始处着陆,并一直向下滑动到我们的有效负载。
如果我们的 NOP sled 是 500 字节(假设我们的漏洞利用允许我们安装这么大的 NOP sled),则等式将是:
0x8000000 / 500 = 268435 迭代以找到我们的 NOP sled 的起始地址。
这种方法对于 64 位系统不太适用的原因是因为以下等式:
2^32 / 256 = 16,777,216(我们的 NOP sled 可以从超过 1600 万个可能的地址开始!即使您的 NOP sled 是 500 字节并且除以 500,您仍然有超过 850 万个地址可以开始您的 NOP sled!)
0000
0000
NNNN
NNNN
NNNN
PPPP
PPPP
PPPP
0000
0000
如果您想象上面是我的堆栈,我的总内存大小为 40。我有一个 12 字节的 NOP 雪橇(N)和一个 12 字节的有效负载(P)。所以我对这个可利用场景的等式是:
40 / 12 = 3 给了我 3 个可能的地址,我的 NOP 雪橇可以在(12、24、32 或十六进制 0x0c、0x18 和 0x20)找到,尽可能少的尝试。
因此,如果我的漏洞利用只是从堆栈的开头开始并以 12 为增量计数,那么它将在第一次尝试时找到我的 NOP sled(将 4 个字节放入我的 NOP sled)。
基于 cmets 的更新:
就地址随机化的绕过技术而言,NOP sled 背后的想法不是猜测 NOP sled 的开始 - 它是计算最少数量的地址 这将保证您将降落在您的 NOP 雪橇内,并且使用尽可能少的地址猜测/蛮力。当然,如果您想找到 NOP 雪橇的开头,您可以使用以下等式:
total_mem_size / NOP_size = least_amount_of_guesses_to_land_inside_payload + 1
但请记住,通过添加额外的地址来尝试,您不再计算 在执行有效负载之前要猜测的最少地址(这是我自己和您正在阅读的书正在计算,因为这是使用 NOP 雪橇背后的想法)。
如果我们重新访问我上面的小“堆栈”,确实可能有 4 个地址可以启动 NOP 雪橇,但该等式计算了 3 个地址,保证可以找到 NOP雪橇(尽可能少的猜测是关键)。为了更清楚,您可以说漏洞利用开发人员将尝试通过增加 NOP sled 的大小(在可能的情况下)使这个数字尽可能小,这样他们就不会担心找到 NOP sled 的开始 -他们只想降落在 NOP 雪橇内。
索引12 的猜测会将 4 个字节放入 NOP sled,从而在到达有效负载之前只执行 8 个 NOP。对索引24 的猜测将使您进入有效负载的几个字节,从而导致崩溃,而对索引32 的猜测将使您超过有效负载,从而导致崩溃。
让我们使用您的方法(使用总共 4 个地址)来说明为什么等式中没有考虑额外地址:
0000
0000
NNNN
NNNN
NNNN
PPPP
PPPP
PPPP
0000
0000
让我们在等式中加 1,得到 4 个可能的地址,它们的堆栈布局与之前相同:
40 / 12 = 3 + 1 = 4
所以现在我们有 4 个地址可以蛮力登陆我们的 NOP 雪橇 0, 12, 24, 32。因为我们有一个 12 字节的 NOP sled,所以仍然只有 1 个地址(索引 12 处的地址,其地址是原始方程找到的地址)将落在我们的 NOP sled 中,让我们在 shellcode 的开头开始执行 shellcode。索引 0 使我们在我们的 NOP 雪橇之前无法控制的数据进入堆栈。因此,在组合中再添加 1 个地址并不能帮助我们找到 NOP 雪橇——它只会增加尝试/蛮力/猜测的地址数量。
是的,你是对的 - 我只是增加更直观的地址以使示例更有意义,但在实际执行期间堆栈上的“计数”或“增加”地址将从高地址开始。