基本 ROP
ROP 简介
最开始,只需将函数返回地址覆盖为 jmp esp 指令的地址,然后在后面添加 shellcode 就可以执行。后来引入了 NX 机制,数据所在内存页被标记为不可执行,此时再执行 shellcode 就会抛出异常。既然注入的代码不行,那就复用程序中已有的代码。
有以下一些概念:
/1. rop:返回导向编程(Return Oriented Programming),在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
/2. gadgets:在程序中的指令片段,有时我们为了达到我们执行命令的目的,需要多个gadget来完成我们的功能。gadget最后一般都有ret,因为我们需要将程序控制权(EIP)给下一个gadget。即让程序自动持续的选择堆栈中的指令依次执行。
/3. ropgadgets:一个pwntools的一个命令行工具,用来具体寻找gadgets的。例如:我们从pop、ret序列当中寻找其中的eaxROPgadget --binary ./7.exe --only "pop|ret" | grep "eax"
/4. 在linux系统中,函数的调用是有一个系统调用号的。例如execve("/bin/sh",null,null)函数其系统调用号是59,即十六进制0x3b。
之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件
- 程序存在溢出,并且可以控制返回地址。
- 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
如果 gadgets 每次的地址是不固定的,那我们就需要想办法动态获取对应的地址了。
寻找 gadgets
/1. 在程序中寻找所有的 c3(ret) 字节
/2. 向前搜索,看前面的字节是否包含一个有效指令,这里可以指定最大搜索字节数,以获得不同长度的 gadgets
/3. 记录下我们找到的所有有效指令序列
理论上我们是可以这样寻找 gadgets 的,但实际上有很多工具可以完成这个工作,如 ROPgadget,Ropper 等。更完整的搜索可以使用 http://ropshell.com/。
常用的 gadgets
对于 gadgets 能做的事情,基本上只要你敢想,它就敢执行。下面简单介绍几种用法:
- 保存栈数据到寄存器
- 将栈顶的数据抛出并保存到寄存器中,然后跳转到新的栈顶地址。所以当返回地址被一个 gadgets 的地址覆盖,程序将在返回后执行该指令序列。
- 如:
pop eax; ret
- 保存内存数据到寄存器
- 将内存地址处的数据加载到内存器中。
- 如:
mov ecx,[eax]; ret
- 保存寄存器数据到内存
- 将寄存器的值保存到内存地址处。
- 如:
mov [eax],ecx; ret
- 算数和逻辑运算
- add, sub, mul, xor 等。
- 如:
add eax,ebx; ret,xor edx,edx; ret
- 系统调用
- 执行内核中断
- 如:
int 0x80; ret,call gs:[0x10]; ret
- 会影响栈帧的 gadgets
- 这些 gadgets 会改变 ebp 的值,从而影响栈帧,在一些操作如 stack pivot 时我们需要这样的指令来转移栈帧。
- 如:
leave; ret,pop ebp; ret
ret2text
原理
ret2text 即控制程序执行程序本身已有的的代码 (.text)。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP。
这时,我们需要知道对应返回的代码的位置。当然程序也可能会开启某些保护,我们需要想办法去绕过这些保护。
例子
ret2shellcode
原理
ret2shellcode,即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。这其实是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码。
在栈溢出的基础上,要想执行 shellcode,需要对应的 binary 在运行时,shellcode 所在的区域具有可执行权限。
例子
ret2syscall
原理
ret2syscall,即控制程序执行系统调用,获取 shell。
例子
ret2libc
原理
ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh"),故而此时我们需要知道 system 函数的地址。
