缓冲区溢出漏洞 - 为什么 shellcode 放在返回地址之前

逆向工程 C 调用栈 缓冲区溢出 开发
2021-07-04 07:48:54

我所指的代码可以在这里找到:Link to code

我读到缓冲区溢出漏洞使用的缓冲区看起来像这样:

| NOP SLED | SHELLCODE | REPEATED RETURN ADDRESS |

据我了解,当缓冲区作为函数参数放入堆栈并覆盖函数的返回地址时,就会发生漏洞利用。我也明白重复的返回地址指向堆栈上同一缓冲区中的 NOP sled。

我不明白的是以下内容:

  1. 为什么返回地址必须指向同一个缓冲区中的shellcode?有人告诉我,我不能让返回地址指向 shellcode 数组(而不是同一缓冲区中 NOP sled 中的地址),因为不同的进程不能访问彼此的内存或类似的东西。如果有人能向我解释这一点,我会很高兴。

  2. 例如,缓冲区上的返回地址如何与原始地址完全对齐,以便ret命令读取正确的地址而不是从中间读取它。我所指的是缓冲区上的重复返回命令覆盖了堆栈上的原始返回命令,该命令是用call指令推送到那里的为什么覆盖返回地址与原始地址完全对齐?

3个回答

为什么返回地址必须指向同一个缓冲区中的shellcode?

它不会,但通常情况下,shellcode 和返回地址是同时传递的,因此它们被粘在一起。如果您的漏洞利用允许您单独交付它们,则可以将它们分开。然而,它们对于被利用的进程来说都是本地的,因为返回地址对于进程来说是本地的。当然,一旦 shellcode 获得控制权,它就可以跨越进程边界。

将 shellcode 放在首位的原因是因为那是被利用的缓冲区所在的位置。堆栈图像如下所示:缓冲区变量、其他变量、返回地址。shellcode 被放入缓冲区,如果需要,其他变量被 nop sled 覆盖,然后修改返回地址。

缓冲区上的返回地址如何与原始地址完全对齐,因此 ret 命令将读取正确的地址而不是从中间读取它

堆栈在32位模式下是4字节对齐的,变量也放在4字节对齐的地址上,并且它们的大小被填充对齐到4字节的倍数,所以一个1字节的char在堆栈上占用了4个字节, 121 字节的缓冲区将占用堆栈上的 124 字节,等等。漏洞利用编写者所要做的就是填充 shellcode+nop sled 直到它对齐到 4 字节的倍数,然后放置返回地址。

如果我正确理解你的第一个问题,我认为你和告诉你这个的人之间存在脱节。没有基于缓冲区在同一进程中的限制,至少在返回值方面没有限制。你绝对可以让返回地址指向 shellcode 的开头。这完全取决于你。

回到 NOP 雪橇的更好理由既是惯例又是一个好习惯。在您不知道您的代码最终将在哪里结束的确切示例中,NOP 雪橇有助于“捕获”您重定向的 EIP。由于 NOP 不做任何事情,因此它为您提供了更大的缓冲区和更大的捕获代码的可能性。正如您链接的讨论中的评论之一所述,这不是一种非常强大的利用形式。

第二个问题更笼统一些。我手头没有这本书的副本,但是确定将返回地址写入缓冲区的距离基本上是反复试验。假设崩溃是可靠的,您会将缓冲区从 120 A 更改为类似 AAAABBBBCCCCDDDDEEEE...等。当程序崩溃时,您将看到 EIP 中有什么垃圾,您可以使用该值来计算数据被覆盖的偏移量。

例如,如果程序终止时您的 EIP 包含 44444444,这意味着 RET 指令将 DDDD 段从堆栈中拉出。因此,当您制作最终的漏洞利用时,您会知道它是 12 个字节,直到保存的 EIP 在堆栈上被覆盖。

值得一提的是,如果您要做任何比这个更简单的示例,Metasploit 有一对很棒的脚本可以使这更容易。pattern_create 将创建一个你的长度的独特模式,pattern_offset 将获取一个长度和模式的一些子字符串,并告诉你它有多远。

关于第一个问题,没有什么能阻止您将 shellcode 放在该进程的可执行内存中的任何位置。例如,一种常见的做法是(老实说)将 shellcode 放在某个环境变量中,因为(在没有 ASLR 的情况下)可以完美地计算它们的地址。在这种情况下,您不必猜测返回地址,而是计算 shellcode 在环境中的地址并将该地址用作返回地址。

这个技巧具有不受缓冲区大小限制的额外好处。但是,另一方面,它显然仅适用于本地漏洞利用。

shellcode 之所以经常在覆盖原始返回地址的同一个缓冲区中的原因是,它只是在开始时方便且易于解释。