ROP 技术

基本 ROP

ret2text

ret2text 即控制程序执行程序本身已有的的代码 (.text)。例子

ret2shellcode

ret2shellcode,即控制程序执行 shellcode 代码。一般来说,shellcode 需要我们自己填充例子

ret2syscall

ret2syscall,即控制程序执行系统调用,获取 shell。例子

此时就需要用到 ROP 链了,得到 ROP 链可以使用 ROPgadget 这个工具,见 TIPS

ret2libc

ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。

一般情况下,我们会选择执行 system("/bin/sh"),故而此时我们需要知道 system 函数的地址。

中级 ROP

ret2csu

在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。 这时候,我们可以利用 x64 下的 __libc_csu_init 中的 gadgets

这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在。

BROP

黑盒测试的办法。见:http://www.scs.stanford.edu/brop/bittau-brop.pdf

高级 ROP

ret2dlresolve

要想弄懂这个 ROP 利用技巧,需要首先理解 ELF 文件的基本结构以及动态链接的基本过程:ElfFormat

在程序执行延迟绑定的函数时实际上会跳到 PLT 中保存的地址执行,这个地址上包含了三行函数。我们以 write@plt 举例:

1
2
3
4
5
6
$ objdump --disassemble-all ret2dlresolve | grep -A 3 "write@plt" 
00000490 <write@plt>:
 490:   ff a3 1c 00 00 00       jmp    *0x1c(%ebx)
 496:   68 20 00 00 00          push   $0x20
 49b:   e9 a0 ff ff ff          jmp    440 <.plt>
....
  1. 第一行是直接跳转到 GOT 表中,write 函数的真实地址。程序刚加载时,GOT 表中的地址都是指向 PLT 表的下一个位置,即上图中的 496
  2. 第二行将 0x20 入栈,准备将其作为参数调用函数;
  3. 第三行调用 .plt 表、第 440 行的指令。这个位置的指令把 link_map=*(GOT+4)(即链接器的标识信息)作为参数推入栈中,然后调用 *(GOT+8)(保存的是_dl_runtime_resolve函数的地址)。

上面的操作实际调用的是 _dl_runtime_resolve(link_map, reloc_arg),该函数会完成符号的解析,即将真实的 write 函数地址写入其 GOT 条目中,随后把控制权交给 write 函数。

_dl_runtime_resolve 是在 glibc-2.23/sysdeps/i386/dl-trampoline.S 中用汇编实现的。0xf7fededb 处即调用 _dl_fixup,并且通过寄存器传参。

SROP