No-exec / NX

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

基本信息

The No-Execute (NX) bit, also known as Execute Disable (XD) in Intel terminology, 是一个基于硬件的安全特性,旨在缓解****buffer overflow攻击的影响。启用并正确实现后,它会区分用于可执行代码的内存区域和用于数据的区域,例如 stackheap。核心思想是阻止攻击者通过将恶意代码(例如放在 stack)并将执行流指向它,从而利用 buffer overflow 漏洞来执行恶意代码。

现代操作系统通过支撑 ELF program headers 的页表属性来强制实施 NX。例如,PT_GNU_STACK header 与 GNU_PROPERTY_X86_FEATURE_1_SHSTKGNU_PROPERTY_X86_FEATURE_1_IBT 属性结合,告知 loader stack 应为 RW 还是 RWX。当 NX 启用且二进制使用非可执行 stack 链接(-z noexecstack)时,任何将执行流转入攻击者控制的数据页(stack、heap、mmap’ed buffers 等)的尝试都会产生故障,除非这些页面被显式标记为可执行。

快速检测 NX

  • checksec --file ./vuln 将根据 GNU_STACK program header 显示 NX enabledNX disabled
  • readelf -W -l ./vuln | grep GNU_STACK 会显示 stack 权限;出现 E 标志表示 stack 为可执行。示例:
$ readelf -W -l ./vuln | grep GNU_STACK
GNU_STACK      0x000000 0x000000 0x000000 0x000000 0x000000 RW  0x10
  • execstack -q ./vuln (from prelink) 在审计大量二进制文件时很有用,因为它会为仍有可执行栈的二进制打印 X
  • 在运行时,/proc/<pid>/maps 会显示某个分配是否为 rwxrw-r-x 等,这对验证 JIT 引擎或自定义分配器很有帮助。

绕过

代码重用原语

可以使用诸如 ROP 来绕过 此保护,通过执行二进制中已存在的可执行代码片段。典型的链包括:

  • Ret2libc
  • Ret2syscall
  • Ret2dlresolve 当二进制没有导入 system/execve
  • Ret2csuRet2vdso 用于合成 syscalls
  • Ret2… — 任何允许你将受控寄存器状态与现有可执行代码拼接以调用 syscalls 或库 gadgets 的调度器。

通常的工作流程是:(1) 通过 info leak 来 leak 代码或 libc 指针,(2) 解析函数基址,和 (3) 构造一个永远不需要攻击者可控可执行字节的链。

Sigreturn Oriented Programming (SROP)

SROP 在可写页上构造一个伪 sigframe,并将执行枢转到 sys_rt_sigreturn(或相关 ABI 的等价项)。内核随后“恢复”该构造的上下文,立即给予对所有通用寄存器、ripeflags 的完全控制。近期的 CTF 挑战(例如 n00bzCTF 2023 中的 Hostel 任务)展示了 SROP 链如何先调用 mprotect 将栈切换为 RWX,然后重用该栈来放置 shellcode,从而在即便只有单个 syscall; ret gadget 可用的情况下也能有效绕过 NX。更多架构相关的技巧请参见专门的 SROP page

Ret2mprotect / ret2syscall 来翻转权限

如果你能调用 mprotectpkey_mprotect,甚至 dlopen,你就可以在运行 shellcode 之前合法地请求可执行映射。一个小的 pwntools 骨架如下:

from pwn import *
elf = ELF("./vuln")
rop = ROP(elf)
rop.mprotect(elf.bss(), 0x1000, 7)
payload = flat({offset: rop.chain(), offset+len(rop.chain()): asm(shellcraft.sh())})

同样的思路也适用于通过 ret2syscall 链设置 rax=__NR_mprotect、将 rdi 指向 mmap/.bss 页面、在 rsi 中保存所需长度并将 rdx=7PROT_RWX)。一旦存在 RWX 区域,执行就可以安全地跳入攻击者控制的字节。

来自 JIT 引擎和内核的 RWX 原语

动态生成代码的 JIT 引擎、解释器、GPU 驱动和内核子系统,通常是在严格 NX 策略下重新获得可执行内存的常见途径。2024 年的 Linux 内核漏洞 CVE-2024-42067 表明 set_memory_rox() 的失败会导致 eBPF JIT 页面同时可写 可执行,使攻击者能够在内核中喷洒 gadgets 或整个 shellcode blobs,尽管存在 NX/W^X 的预期。获得对 JIT 编译器(BPF、JavaScript、Lua 等)的控制的利用链因此可以将其载荷放在这些 RWX 区域,并只需一次函数指针覆盖即可跳入。

非返回式代码重用 (JOP/COP)

如果 ret 指令被加固(例如 CET/IBT),或者二进制文件缺乏足够的 ret gadgets,就转向 Jump-Oriented Programming (JOP)Call-Oriented Programming (COP)。这些技术构建使用二进制或已加载库中 jmp [reg]call [reg] 序列的调度器。它们仍然符合 NX,因为复用的是现有的可执行代码,但可以规避那些专门检测大量 ret 链的缓解措施。

ROP & JOP

参考资料

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks