Stack Overflows - 击败 Canaries、ASLR、DEP、NX

信息安全 开发 已知漏洞 缓冲区溢出
2021-08-26 03:46:25

为了防止缓冲区溢出,可以使用多种保护措施,例如使用 Canary 值、ASLR、DEP、NX。但是,有志者事竟成。我正在研究攻击者可能绕过这些保护方案的各种方法。似乎没有一个地方可以提供明确的信息。这些是我的一些想法。

金丝雀- 攻击者可以找出金丝雀的值并在他的缓冲区注入中使用它来欺骗堆栈守卫检测到漏洞

DEP, NX - 如果调用VirtualAlloc(), VirtualProtect(), 攻击者可以尝试将代码重定向到这些函数并在他想要注入任意代码的页面上禁用 DEP, NX。

ASLR-没有线索。ASLR 和 DEP 是如何工作的?

3个回答

Canary
Stack canaries 通过修改每个函数的序言和结尾区域来分别放置和检查堆栈上的值。因此,如果在内存复制操作期间覆盖了堆栈缓冲区,则会在执行从复制函数返回之前注意到错误。发生这种情况时,会引发一个异常,该异常会向上传递回异常处理程序层次结构,直到它最终到达操作系统的默认异常处理程序。如果您可以覆盖堆栈中现有的异常处理程序结构,则可以使其指向您自己的代码。这是一个结构化异常处理 (SEH) 漏洞利用,它允许您完全跳过金丝雀检查。

DEP / NX
DEP 和 NX 本质上将内存中的重要结构标记为不可执行,并在您尝试执行这些内存区域时强制硬件级异常。这使得您设置eip的正常堆栈缓冲区溢出esp+offset并立即运行您的 shellcode 是不可能的,因为堆栈是不可执行的。绕过 DEP 和 NX 需要一个很酷的技巧,称为Return-Oriented Programming

ROP 本质上涉及从程序中找到现有的代码片段(称为小工具)并跳转到它们,以便您产生所需的结果。由于代码是合法可执行内存的一部分,因此 DEP 和 NX 无关紧要。这些小工具通过堆栈链接在一起,其中包含您的漏洞利用有效负载。堆栈中的每个条目对应于下一个 ROP 小工具的地址。每个 gadget 都是 的形式instr1; instr2; instr3; ... instrN; ret,因此ret在执行完指令后会跳转到栈上的下一个地址,从而将 gadgets 链接在一起。通常必须在堆栈上放置额外的值才能成功完成一个链,因为否则会妨碍指令。

诀窍是将这些 ROP 链接在一起以调用内存保护函数,例如VirtualProtect,然后使用该函数使堆栈可执行,因此您的 shellcode 可以通过jmp esp或等效的小工具运行。诸如此类的工具mona.py可用于生成这些 ROP 小工具链,或查找一般的 ROP 小工具。

ASLR
有几种方法可以绕过 ASLR:

  • 直接 RET 覆盖 - 通常使用 ASLR 的进程仍将加载非 ASLR 模块,允许您通过jmp esp.
  • 部分 EIP 覆盖 - 仅覆盖部分 EIP,或者使用堆栈中的可靠信息披露来找到真正的 EIP 应该是什么,然后用它来计算你的目标。尽管如此,我们仍然需要一个非 ASLR 模块。
  • NOP 喷雾 - 创建一大块 NOP 以增加跳跃着陆在合法记忆上的机会。困难,但即使所有模块都启用了 ASLR 也是可能的。如果打开 DEP 将不起作用。
  • Bruteforce - 如果您可以尝试利用不会导致程序崩溃的漏洞,您可以暴力破解 256 个不同的目标地址,直到它起作用。

推荐阅读:

金丝雀和其他挥发物不能防止溢出他们只是试图应对已经发生的溢出的后果。金丝雀尝试检测覆盖堆栈帧中的返回地址的溢出情况。DEP 更进一步,它假设返回地址已被覆盖和跟踪,它限制了执行可以跳转的区域。ASLR 更进了一步:它“洗牌”允许执行的区域。

历史上,缓冲区溢出被利用来覆盖堆栈中的返回地址,从而使执行跳转到已经用于溢出缓冲区的数据。金丝雀尝试在跳转之前检测到这一点,并使用 DEP 使堆栈空间不可执行。DEP 还可以在堆中的缓冲区溢出时工作(金丝雀仅用于堆栈缓冲区溢出,但堆也可以包含缓冲区,以及要覆盖的敏感数据,例如指向函数的指针——尤其是在 OOP 的上下文中C++ 等语言)。为了绕过 DEP 和金丝雀,攻击者已经开始寻找允许覆盖函数指针的溢出,从而使执行跳转到标准库代码这必然是“那里”,也必然是可执行的。这就是发明 ASLR 的原因:让此类游戏变得更难。ASLR 仍然可以被幸运打败:因为 ASLR 必须保持页面对齐(x86 上为 4 kB),在不太大的地址空间内(通常在 32 位 x86 上小于 2 GB),没有那么多地方目标代码可能在哪里(最多一百万)。根据攻击上下文和攻击者的脚本可以尝试的频率,这可能太低而无法舒适。

这里的重要主题是金丝雀、DEP 和 ASLR 本身不会阻止溢出,而是针对传统上使用的通用溢出利用方法。在任何应用程序中,覆盖非指针数据的溢出都可能与远程shell 漏洞一样致命(例如,想象修改名为“ authenticated_user_name”的字符串字段的溢出)。攻击者和防御者之间的武器竞赛变得过于专业化,在我看来,越来越没有抓住重点。一般来说,最好不要让溢出发生,即在目标缓冲区之外写入字节之前阻塞/杀死有问题的进程/线程。几乎所有体面的编程语言(Java、C#、VB.NET、Python、Ruby、Node.js、OCaml、PHP……选择范围都很大)都会发生这种情况。

基本的保护级别是 ASLR + DEP。

如果您不同时使用这两种方法,那么有许多强大的技术可以利用缓冲区溢出(例如,面向返回的计算、堆喷射、重复猜测)。例如,仅使用面向回报的计算就可以击败 DEP;并且可以使用堆喷射和重复尝试单独击败 ASLR。

但是,如果目标同时使用 ASLR + DEP,则利用变得更加困难。上述技术不足以击败 ASLR + DEP。ASLR + DEP 就像一拳,让攻击者的生活变得更加艰难。击败 ASLR + DEP 的组合并非不可能,但它需要更多的聪明才智。

我最喜欢的击败 ASLR + DEP 方法的示例在幻灯片中解释,解释器利用:指针推理和 JIT 喷射. 在那里,作者描述了他如何利用 Flash 中的内存安全错误。尽管存在 ASLR + DEP,但他利用 Flash JIT 的特性来安排内存,使他能够发起代码注入攻击。回想一下,JIT 是一种即时编译器;它将 Flash 字节码编译为本机代码。本机代码将存储在内存中的某个位置,Flash JIT 将其标记为可执行(尽管有 DEP)。作者找到了一种生成 Flash 字节码的方法,该字节码在编译时会生成嵌入他的恶意 shellcode 的字节序列(偏移一个字节)。然后,他使用堆喷射技术来确保内存中有很多副本。最后,他利用内存安全漏洞导致程序跳转到另一个地址;由于 ASLR,这就像跳转到一个随机地址,但是许多副本确保这很有可能会跳入他的shellcode。通过这种方式,他绕过了 ASLR 和 DEP——这是一个绝妙的壮举。

最后一点:值得一提的是,ASLR在 64 位架构上更有效在 32 位架构上,ASLR 通常可以通过简单的多次尝试而失败。只是在 32 位平台上没有足够的自由度来引入足够的随机性,因此在 32 位平台上,攻击者靠运气成功的机会仍然很高。要获得最强的防御,请使用 64 位平台。