ROP

ROP,Return Oriented Programming,核心操作就是——修改返回地址,让其指向内存中已有的一段指令,可用于绕过NX的保护机制

ROP的思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓 gadgets 就是以ret结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。

之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件:

  • 程序存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

ROP要完成的任务包括:在内存中确定某段指令的地址,并用其覆盖返回地址。

下面看下单个Gadget和多个Gadgets进行溢出的原理示例。

单个Gadget溢出示例

其payload是如下形式,使用padding溢出到返回地址前,再将gadget地址传到返回地址处:

payload : padding + address of gadget

多个Gadgets溢出示例

如果想连续执行若干段指令,就需要每个 gadget 执行完毕可以将控制权交给下一个 gadget。所以 gadget 的最后一步应该是 RET 指令,这样程序的控制权(EIP)才能得到切换,所以这种技术被称为返回导向编程( Return Oriented Programming )。要执行多个 gadget,溢出数据应该以下面的方式构造:

payload : padding + address of gadget 1 + address of gadget 2 + …… + address of gadget n

在这样的构造下,被调用函数返回时会跳转执行 gadget 1,执行完毕时 gadget 1 的 RET 指令会将此时的栈顶数据(也就是 gadget 2 的地址)弹出至 EIP,程序继续跳转执行 gadget 2,以此类推。

……(待补充)

ret2text

ret2text即控制程序执行程序本身已有的的代码 (.text)。也就是说,在该ELF的.text代码段中是存在可以利用的代码的,如存在system(“/bin/sh”)的代码。

下面按照一般的做题步骤做下吧。

一、运行程序,了解执行流程

运行程序,输入内容后即返回:

二、file查看elf文件是否动态链接

可用看到是32位ELF文件,且是动态链接的,即可能与libc相关:

三、checksec查看保护机制

查看一下程序的保护机制,可以看到是32位的程序且仅仅开启了栈不可执行保护NX:

四、使用IDA进行静态汇编代码分析

IDA打开查看,可以看出程序在主函数中使用了gets函数,显然存在栈溢出漏洞,且可看到变量v4相对于ESP和EBP寄存器的偏移:

由于NX启动了即栈不可执行,我们可以到其他的地方查看,打开shift+F12打开string窗口,可用看到“/bin/sh/”字符串:

双击点击进去,发现是在secure函数中调用的:

点击后面的secure,进入secure函数发现了存在直接调用system(“/bin/sh”)的代码,那么如果我们直接控制程序返回至0x0804863A,那么就可以得到shell了:

现在已经明确了可利用的Gadget地址,即反弹shell的地址:0x0804863A。

五、计算偏移地址

构造payload之前,需要计算能够控制的内存的起始地址距离main()函数的返回地址的字节数。

Method 1——GDB断点调试获取

先查看一下gets()函数的汇编代码,得知该字符串是通过相对于ESP的索引(具体如何判断可看后面的调试):

可知gets()函数地址为0x080486AE,在该处设置断点进行调试:

ESP为ffffcfa0,其中存放的内容为ffffcfbc,即输入的内容s的地址为ESP+1c= ffffcfbc,而EBP为ffffd028,则s到EBP的偏移为|ffffd028- ffffcfbc|=6c,所以s相对与返回地址的偏移为0x6c+4=0x70。

确认一遍,在gets()函数的下一条汇编指令处打断点,继续运行并输入一大串1:

可以发现EAX地址处确实保存的是s(数据换成1),且|EAX-ESP|=|0xffffcfbc-0xffffcfa0|=1c,即说明s是相对于ESP偏移的;但|EAX-EBP|=|0xffffcfbc-0xffffd028|=6c,和IDA给出的64h有差别,由此可知IDA给出的相对于ESP的偏移是正确的、但相对于EBP的偏移是有误差的,这就是之前说的IDA分析给出的偏移量并不那么可信。

Method2——使用GDB pattern字符串溢出计算偏移量

GDB的pattern_create创建计算溢出偏移量的字符串,在输入内容时输入即可:

pattern_offset算出偏移量:

得出s与函数返回地址的偏移为112即0x70。

六、编写payload

由前面分析知,可以利用.text代码段区域中的system(“/bin/sh”)代码,该代码段即为可被ROP利用的Gadget,地址为0x0804863a,将其覆盖到函数返回地址处,前面再padding 70h个字节码即可:

1
2
3
4
5
6
7
from pwn import *

sh = process("./ret2text")
binsh_addr = 0x0804863a
payload = flat(["A" * 0x70, binsh_addr])
sh.sendline(payload)
sh.interactive()

运行后,成功getshell:

参考

基本 ROP

手把手教你栈溢出从入门到放弃(下)