NX 是怎么绕过的
绕过 NX 的基本思路
NX 阻止了攻击者在数据段上执行自己的代码。那么,绕过 NX 的基本思路就是:不注入代码,而是利用程序本身已有的、具有执行权限的代码
这个思路催生了多种绕过技术,其中最主要、最著名、最通用的就是 ROP
ROP
ROP 是目前最主流的 NX 绕过技术。它的原理是利用程序中已有的、以 ret
指令结尾的短小代码片段,这些片段被称为 “gadgets”
ROP 的工作原理
- 寻找 Gadgets: 攻击者首先在目标程序或其依赖的共享库(如
libc
)中寻找一系列以ret
指令结尾的“gadgets”。一个 gadget 可能是一条或几条汇编指令,例如:pop edi; ret;
或mov eax, [ebx]; ret;
- 构建 ROP 链: 攻击者利用漏洞(如缓冲区溢出),用一系列精心挑选的 gadget 地址来覆盖栈上的返回地址这些地址按顺序排列,形成一个 “ROP 链”
- 劫持控制流: 当函数返回时,它不再返回到正常调用的地方,而是返回到 ROP 链中的第一个 gadget
- 链式执行:
- 第一个 gadget 执行完后,其末尾的
ret
指令会从栈上弹出下一个地址,也就是 ROP 链中的第二个 gadget - 这样,一个 gadget 接一个 gadget 地执行,每个
ret
指令都将控制流转移到下一个 gadget - 通过这种方式,攻击者可以利用程序中已有的代码,间接地执行任意恶意逻辑
- 第一个 gadget 执行完后,其末尾的
ROP 攻击的最终目标
一个典型的 ROP 攻击,其最终目标通常是调用某个函数,比如 system()
,并将一个指向 Shell 命令字符串(例如 "/bin/sh"
)的指针作为参数传递给它
一个完整的 ROP 链通常包含:
pop
gadget: 用于将栈上的参数值弹出到寄存器中,为函数调用做准备system()
的地址: ROP 链的末尾,用于最终调用system()
- 字符串
/bin/sh
的地址: 作为system()
的参数
绕过 NX 的其他方法
除了 ROP,还有其他一些不那么常见,但同样能绕过 NX 的技术:
1. JIT Spraying(JIT 喷射)
这种方法主要用于绕过浏览器中的 NX 保护
- 原理: JIT(Just-In-Time)编译器会动态地生成可执行代码。攻击者可以利用 JavaScript 等语言的 JIT 特性,构造大量的
nop
指令(无操作指令),然后在其末尾附上 Shellcode - 工作方式: JIT 引擎会将这些指令编译成原生机器码并存放在一个可执行的内存区域。攻击者只需找到这个可执行区域的地址,并跳转过去即可
2. Return-to-libc(返回到 libc)
Return-to-libc 是 ROP 的前身和简化版。它不需要复杂的 gadget 链,而是直接劫持程序流,跳转到已加载的 libc
库中的一个函数
- 原理: 攻击者利用漏洞,用
libc
中system()
函数的地址覆盖栈上的返回地址 - 工作方式: 当函数返回时,程序流会直接跳转到
system()
函数。攻击者在栈上预先放置好system()
函数所需的参数(如"/bin/sh"
的地址),即可实现代码执行 - 局限性: 这种方法非常简单,但它只能调用
libc
中已有的函数,而不能像 ROP 那样组合出更复杂的逻辑