ROP攻击技术

ROP(Return-Oriented Programming,返回导向编程)是一种攻击技术,攻击者利用受害程序中已存在的代码片段(称为“gadget”)来执行任意代码。ROP 技术常用于绕过 DEP(Data Execution Prevention,数据执行保护)等安全机制。

ROP 攻击的基本原理

  1. 代码重用:攻击者不需要在堆栈中插入自己的代码(如传统的 shellcode),而是利用已有的、合法程序中的代码片段(gadget)执行恶意操作。这些 gadget 通常是程序中一些小的指令序列,通常以“ret”(返回指令)结束,返回会控制程序的执行流。

  2. 跳转到 gadget:攻击者通过堆栈溢出等方式控制程序的返回地址,劫持程序的控制流,让程序按攻击者的意愿依次跳转到预期的 gadget 处。

  3. 执行控制:攻击者通过选择合适的 gadget 实现与恶意代码相同的效果,如操作寄存器、内存,调用系统调用等。

ROP 的步骤

  1. 寻找 gadget:攻击者需要寻找程序或共享库中合适的 gadget。这些 gadget 通常是一些较短的指令序列,可能执行简单的操作,如将某个值加载到寄存器、对寄存器内容进行操作,或者跳转到另一个 gadget。

    例如:

pop eax; ret
xor eax, eax; ret
  1. 控制返回地址:利用栈溢出、格式化字符串漏洞等手段,将特定的 gadget 地址放入栈中,替代原本的返回地址。

  2. 构造 ROP 链:通过在栈中连续放入 gadget 的地址,使程序按攻击者设计的顺序依次执行这些 gadget,形成一条 ROP 链。通过这些 gadget,攻击者可以达到类似 shellcode 的效果。

绕过 DEP

DEP 是一种防止代码在不可执行区域(如栈或堆)运行的机制。ROP 通过复用程序中已有的代码来执行恶意操作,从而绕过了 DEP。

典型用法

  • 执行系统调用:攻击者可以通过 ROP 构造一系列系统调用(如 execve()),从而执行恶意代码。

  • 提升权限:通过 ROP 技术调用特定系统函数提升进程权限。

防御 ROP 的技术

  • ASLR(Address Space Layout Randomization):通过随机化程序的内存布局,使得攻击者无法轻易找到 gadget 的准确地址。

  • 栈金丝雀:在函数栈帧中插入特殊的值(金丝雀),如果在函数返回之前该值发生改变,则检测到溢出攻击。

  • Control Flow Integrity(控制流完整性):确保程序的执行流不会被任意劫持,攻击者无法随意跳转到 gadget。

ROP 是一种极具威胁的攻击方式,它利用了程序中现有的代码而非插入恶意代码,因此传统的执行保护机制对其无效。

cyclic

使用步骤

1. 生成一个循环模式

可以使用pwntools库生成一个不重复的字节模式,用于触发栈溢出并找出精确的覆盖位置。首先,确保已经安装pwntools库:

pip install pwntools

然后,可以使用cyclic函数生成一个特定长度的循环模式。假设你想生成300字节的模式:

from pwn import *
pattern = cyclic(300)
print(pattern)

这会生成一段如"aaaabaaacaaadaaaeaaafaaa..."的字符串,并且每个字符组的长度和顺序都是不重复的,便于后续调试。

2. 触发栈溢出并崩溃程序

接下来,将生成的模式传递给目标程序。你可以通过手动输入、管道传递或者通过修改代码直接传入这个模式,具体方式取决于程序的输入方式。

假设你的目标程序通过命令行参数接收输入:

./vulnerable_program $(cyclic 300)

这样运行程序,栈溢出时程序会崩溃,并且你可以通过gdb获取程序崩溃时的寄存器信息。

3. 通过gdb调试获取崩溃地址

使用gdb调试崩溃的程序。运行命令:

gdb ./vulnerable_program

在gdb中使用如下命令:

run $(cyclic 300)

程序崩溃时,gdb 会显示类似这样的输出:

Program received signal SIGSEGV, Segmentation fault.
0x61616161 in ?? ()

这里的0x61616161表示栈上的地址已经被'aaaa'覆盖了。

4. 找到偏移量

为了确定返回地址被覆盖的精确偏移,可以使用pwntoolscyclic_find函数:

from pwn import *
offset = cyclic_find(0x61616161)
print(offset)

这个命令会输出偏移量,假设结果是112,说明在输入的第112字节处覆盖了栈上的返回地址。

5. 替换返回地址并进一步调试

知道偏移量后,你可以尝试通过修改输入,使其在覆盖返回地址的位置插入一个新的有效地址。例如,覆盖栈返回地址并劫持程序流:

payload = fit({
    112: p32(0xdeadbeef)
})

然后再用gdb调试这个修改过的输入,继续分析程序行为。

gdb-peda

gdb-peda 是一个增强 gdb 调试体验的插件,它集成了许多常用的调试功能,尤其是在漏洞利用和安全研究中非常有用。在 gdb-peda 中,pattern 命令可以用来生成和查找特定模式,类似于 pwntools 中的 cyclic 功能。pattern create 是其中一个常用的子命令,用于生成独特的字节模式,通常用于检测栈溢出和找到返回地址的偏移。

gdb-pedapattern create 的使用

1. 安装 gdb-peda

如果还没有安装 gdb-peda,可以通过以下命令安装:

git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit

重新启动 gdb 后,peda 就会被自动加载。

2. pattern create 命令

pattern create 用来生成一个指定长度的不重复的字节模式,用于后续的栈溢出调试和分析。生成的模式可以被传递到目标程序,之后可以通过程序崩溃的地址来确定溢出发生的位置。

生成模式

使用 pattern create 来生成指定长度的模式:

gdb-peda$ pattern create 300

这将生成一个300字节长度的模式,类似于:

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3...

这些字节组不重复并按照特定的顺序排列。你可以把这个模式作为输入传递给目标程序,帮助你在栈溢出时确定覆盖了哪一部分栈内存。

3. 使用模式来定位崩溃点

当目标程序因为栈溢出崩溃时,你可以在 gdb-peda 中查看崩溃发生时的寄存器信息,比如 EIPRIP 的值。如果 EIP 被模式覆盖了,你会看到一些不常见的值。

假设程序崩溃时 EIP 显示为 0x41336241,这表明 EIP 被模式的一部分覆盖了。

4. pattern offset 命令

使用 pattern offset 来查找栈溢出覆盖的位置。假设我们看到 EIP0x41336241 覆盖了,我们可以通过以下命令找到它在模式中的偏移量:

gdb-peda$ pattern offset 0x41336241

gdb-peda 会返回一个类似这样的输出:

109

这表示在模式生成的字节流中的第 109 个字节处覆盖了 EIP,所以可以确定我们需要在输入的第109个字节开始构造覆盖 EIP 的数据。

5. 替换返回地址

知道了偏移量之后,你可以通过构造输入来覆盖 EIP 并劫持控制流。例如,你可以在偏移位置插入你想要的返回地址(比如 0xdeadbeef):

payload = "A" * 109 + "\xef\xbe\xad\xde"

这个 payload 将在偏移109字节的位置插入地址 0xdeadbeef,用于劫持程序流。

6. 验证漏洞利用

将构造的 payload 传递给目标程序,并通过 gdb-peda 继续调试,确认是否成功劫持了程序流。