本篇为个人对 CTFWiki 的学习笔记。
0x00 What’s ROP
随着 NX (Non-eXecutable) 保护的开启,传统的直接向栈或者堆上直接注入代码的方式难以继续发挥效果,由此攻击者们也提出来相应的方法来绕过保护。
目前被广泛使用的攻击手法是 返回导向编程 (Return Oriented Programming),其主要思想是在 栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
gadgets 通常是以
ret结尾的指令序列,通过这样的指令序列,我们可以多次劫持程序控制流,从而运行特定的指令序列,以完成攻击的目的。返回导向编程这一名称的由来是因为其核心在于利用了指令集中的 ret 指令,从而改变了指令流的执行顺序,并通过数条 gadget “执行” 了一个新的程序。
使用 ROP 攻击一般得满足如下条件:
- 程序漏洞允许我们劫持控制流,并控制后续的返回地址。
- 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
作为一项基本的攻击手段,ROP 攻击并不局限于栈溢出漏洞,也被广泛应用在堆溢出等各类漏洞的利用当中。
0x01 ret2text
ret2text 是指利用程序已有的 .text 段代码进行攻击的 ROP 类型。


简单的原理如上图,即覆盖栈内的 Return Address 达到劫持程序流的作用。
这里用 CTFWiki 上的 ret2text 作示例,首先用 checksec 命令查看安全信息。

可以看到打开了 NX (Non-Excutable) 保护,作用是使数据,堆栈和堆段不可执行,而代码段不可写。
使用 Binja 反编译发现使用了可能导致栈溢出的函数 gets,同时发现 secure 函数中有着能够获取 Shell 的代码:


这时我们的主要目标就明朗了:覆盖 main 函数栈的 retaddr 为 0x804863A 拿到 Shell,开始构造 Payload。
我们会遇到一个问题:如何确定栈的深度?即如何确认我们需要在栈内覆盖多少大小的数据?
首先我们给出简单的方法:覆盖栈直到程序产生栈溢出错误。方法可以参考这篇文章:gdb调试之超长字符生成与定位_gdb cyclic-CSDN博客。
我们用 pwngdb 来调试,首先用命令 cyclic 200 生成长度为 200 的字符串,输入 r 运行程序,粘贴上面生成的字符串回车,可以看到程序产生了栈溢出错误。

此时 EIP 的值为 daab 。我们再用 cyclic -l daab 命令获取偏移量。

这说明我们需要填充 112 个字符,所以我们可以得到以下的 Exploit:
from pwn import *
sh = process('./ret2text')
addr = 0x0804863a
payload = b'a' * 112 + p32(addr)
sh.sendline(payload)
sh.interactive()

成功得到 Shell,但是仍然存在着一些问题需要深究:
- 为什么用的是 EIP 的值去计算偏移值?
- 能否不用这种方法,直接计算偏移值?
首先来看第一个问题,我们首先要知道 EIP 寄存器的作用:存储即将执行的程序命令的地址。
