NX 位:它保护堆栈吗?

信息安全 应用安全 攻击 开发 缓冲区溢出 外壳代码
2021-08-27 07:41:55

我曾经听说 NX 钻头是灵丹妙药,然后它不是。不过,我想知道一个细节:NX(不执行)位是否可以防止代码插入堆栈并在那里执行?在我看来,堆栈,因为它不是堆,通常可能不受 NX 保护。谢谢。

2个回答

NX 位是某些 CPU(包括足够近的 x86)的内存管理单元一个特性。它允许将每个内存页面标记为“允许”或“禁止”代码执行。MMU受内核控制;内核代码决定哪些页面获得执行权限,哪些没有。因此,堆栈空间是否受到保护以防止执行取决于操作系统。它还取决于许多其他事情:

  • 尽管内核可能会为页面属性应用默认值,但应用程序可以请求更改。例如,在 Linux 系统上,mprotect()系统调用可用于启用或禁用在调用进程控制下的任何页面上的执行。特别是,任何包含JIT 编译器的应用程序(例如,Web 浏览器,用于有效的 Javascript 支持)都必须使用mprotect().,因为它必须生成代码(即将字节写入某些内存)然后执行所述代码。

  • 单线程应用程序在内核知道的专用区域中获取其堆栈;特别是,堆栈页在第一次使用时自动分配。在多线程应用程序中,情况发生了变化:每个线程都有自己的堆栈,从内核的角度来看,堆栈分配。根据操作系统,内核可能会或可能不会对线程堆栈提供特殊支持。

  • GCC 尤其支持称为嵌套函数的 C 语言扩展。由于这些函数的语义,嵌套函数的编译代码必须在堆栈上动态生成一些可执行代码;这叫做蹦床有关详细信息,请参阅此页面底线是,要使蹦床真正工作,堆栈(或至少蹦床所在的页面)必须标记为可执行。

碰巧在ELF 文件格式(Linux 上的可执行文件和 DLL 的格式)中,有一个字段允许指定堆栈是否应该是可执行的。当 GCC 编译一些包含嵌套函数的代码时,它会在生成的二进制文件中设置这个标志。

我在最近的 Linux 上进行了一些测试(Ubuntu 13.10,在 64 位 x86 上,NX 位已激活)。可以从进程 ID/proc/$$/maps所在的特殊文件的内容中推断给定页面是否可执行。$$这些测试表明:

  • 默认情况下,主堆栈是不可执行的。为堆栈页设置 NX 位。
  • 如果代码包含嵌套函数,则可执行文件被标记为“可执行堆栈”标志,实际上,主堆栈现在是可执行的。
  • 线程堆栈的创建与主堆栈具有相同的“可执行状态”。
  • 如果一个“普通”可执行文件,其主堆栈不可执行,动态加载一个dlopen()包含嵌套函数代码的 DLL(带有 ),那么主堆栈会自动切换到可执行状态。
  • 如果一个普通的多线程可执行文件,其主堆栈和所有线程堆栈都是不可执行的,动态加载包含嵌套函数代码的 DLL,然后主堆栈和所有线程堆栈被整体提升为可执行状态。请注意,这意味着在这个 Linux 系统上,“某物”(可能是内核,我没有检查过)知道所有当前的线程堆栈,并且可以动态地更改它们的映射权限。

从所有这些我们得出的结论是,至少在一些最新版本的 Linux 上,堆栈(主线程和线程)通常会被标记为不可执行(即 NX 位集)。但是,如果应用程序的可执行代码或应用程序加载的 DLL 中某处的某些可执行代码包含嵌套函数或以其他方式宣传需要可执行堆栈,则应用程序中的所有堆栈都将被标记为可执行(即NX 位未设置)。换句话说,一个应用程序的单个可加载插件或扩展可能会为应用程序的所有线程停用 NX 堆栈保护,只需使用 GCC 的一个很少使用但合法且记录在案的功能。

不过,不必惊慌,因为正如@tylerl 指出的那样,NX 位提供的保护并不是那么好。对于最不称职的攻击者来说,这会使一些漏洞利用变得更加尴尬;但优秀的攻击者不会受到阻碍。此外,所有关于 NX 的讨论都是关于在发生缓冲区溢出或释放后使用时试图控制损坏,这可以说是有点迟了反应。

通过将堆栈标记为非执行,您可以有效地防止插入堆栈的代码运行。您没有保护堆栈不被修改;相反,当代码尝试跳转到 NX 标记堆栈中的某个位置时,您会导致严重崩溃。

解决方法是不要尝试在堆栈上执行代码。不是将返回位置设置为堆栈中的某个位置,而是将其设置为系统函数的位置,例如exec(),将您在堆栈上覆盖的位置设置为包含exec()系统调用的参数而不是可执行的机器代码。在这种情况下,攻击者可以使用他选择的参数运行一个系统函数,这足以例如执行另一个程序。

因此,NX 提供了一定程度的保护,但不是吨。它在一定程度上限制了攻击者对初始漏洞利用的灵活性,但考虑到他可以利用该漏洞利用来执行 shell,那里的安全性并不高。

防御的下一层是 ASLR,它将系统功能的位置随机化,例如exec(). 这样,当他尝试执行他的 shell 时,他将不知道将哪个地址加载到返回位置。

针对 ALSR 的攻击通常是蛮力攻击;多次运行您的漏洞利用,每次尝试不同的返回地址。这意味着 ALSR 在 32 位系统上的效果明显低于 64 位系统,因为随机化范围有限,允许相对快速的蛮力攻击。

ASLR 与 NX 相结合提供了一定程度的保护,但没有什么是绝对的。