利用ROP链突破DEP保护机制

来源:互联网 发布:汽车网络事件营销 编辑:程序博客网 时间:2024/05/21 11:03
@Puzzor         

本文旨在介绍如何利用ROP绕过DEP保护机制,也是学习过程中的总结

DEP保护机制即Data Execution Prevention,其分为软件DEP以及硬件DEP,下面所讨论的都是基于硬件DEP下的绕过方式。相关术语请自行学习。

所用实例为一存在溢出漏洞的程序,当处理某些文件时未能检查文件长度的限制,导致栈区溢出,若不开启DEP保护,则溢出后EIP可以跳转至栈区执行指令,而当添加了DEP保护后,由于栈区被标记为不可执行,故抛出Access violation (0xC00000005)错误,错误提示如下:


前面准备工作就不详细叙述了,假设我们现在已经找到了溢出点并且能够控制EIP指向任意地址,正如我所说的,我暂时将EIP控制为0x43434343



此时挂载调试器,停到中断点上。如下图所示,正如我们所设计的,EIP成功变为0x43434343



此时栈区情况如下图

基本情况就是这样,接下来正式讨论如何绕过DEP机制。我们都知道在PE文件内存在好多的Section,.Text Section,也就是指令区,存放了CPU所要执行的指令,而其它Section一般不会存放指令的。DEP的保护机制就是给.Text赋予Executable的属性,而其它区域是不具有这种属性的,这样一来,当我们在程序运行时想把我们的shellcode拷入堆栈区执行是不可能的。

正如我们的实例所展示的那样当EIP变为0x43434343时,CPU会判断出当前指令的位置已经不处于可执行的内存范围内,便会抛出访问异常的错误提示。

但是当利用ROP链之后便能够巧妙地突破这一限制,真正达到EIP为我所用。

在接下来演示之前,我们先介绍几个基础的汇编指令:

  1. POP 指令

将ESP所指向的内容弹出,并且ESP+4

        2.  PUSH指令

ESP-4,并且将源操作数所指向的内容压入ESP所指向的地址

        3.  RET

将ESP所指向的内容赋给EIP后ESP+4

接下来我们来进行一个小的演示:

这次我们将EIP的值改掉,改为一个RET命令的地址,在此实例中我们对msvcrt.dll中进行查找,找到后如下图



这条指令所在地址为0x77c05443,将这个地址替换掉我们之前的0x43434343,替换之后在0x77c05443处增加断点,断点触发后观察EIP ,ESP的值正如我们所设计的那样:


单步执行后,首先ESP(0x0010F730)所指向的内容0x42424242会被弹入EIP,弹入后ESP+4,下面是单步执行后的效果:

可以看到,指令终于能够继续了,接下来讨论如何利用。

如图所示,初始状态下ESP指向0x0010f730,EIP为0x77c05443,接下来跳转的此处执行RETN,Retn指令的动作为0x0010f730中的内容被弹入EIP,ESP+4,指向了0x0010f734,就是基于这样一种机制我们便可以控制EIP继续执行。不要着急,我们会慢慢完成上面这个表,直到全部内容都被展现出来。别忘了,我们最终的目的是要执行我们的shellcode。我们必须想办法把shellcode所在的内存空间标记为可执行的,而这一工作则需要用到VirtualProtect()函数。所以我们如此不断跳转的目的也是为了能够实现调用VirtualProtect()函数并更改shellcode所在空间为可执行。

Retn之后,需要将当前ESP指针保存起来,故需要类似于PUSH ESP POP [寄存器]这样的命令,于是在0x5ADC9227处找到PUSH ESP # MOV EAX,EDX # POP EDI # RETN,中间的 MOV EAX,EDX并不会影响我们的ESP和EDI,所以可以忽略。



于是,将0x0010F730填充为0x5ADC9227


接下来,将EDI的内容传入EAX,为此,需要PUSH EDI # POP EAX这样的指令,于是我们在0x77BEE842处找到PUSH EDI # POP EAX # POP EBP # RETN 指令,可以看到,这里有两次POP,而POP EBP 对于我们来说是没有意义的操作,为了进行堆栈平衡,还需要向栈中填入一些junk。填充完成之后应该是下面这个样子的

执行完这个ROP gadget后,寄存器状态如下

可以看到,EAX,EDI中同时保存了初始时的栈地址,EBP中便是我们填入的Junk。

接下来我们需要将ESP向前移动,以免覆盖掉VirtualProtect的参数。所以在0x10018F88处找到 ADD ESP,20 # RETN指令,接下来填入栈区


当EIP返回到0x10018F88后,ESP会加20,这样一来,ESP 从原来的0x0010F740增加到0x0010F760,而0x0010F740到0x0010F760这一段区间内我们要放的正是VirtualProtect()函数的地址以及相应的参数,virtualprotect函数共需要5个参数放在栈帧顶部:返回地址,shellcode 在的位置的指针,Size,新的保护标志,接收旧的保护标志值的指针。

故填充之后如下所示:


接下来我们要将几个参数填充,当前EIP指向RETNESP0x0010F760,下面需要复写第一个参数,也就是0x0010F744地址的值。这个参数需要的是shellcode的地址,而shellcode正处于esp往后的一段空间内。可以看到当前寄存器的状态为:


(不要关注ESP的值,这是已经单步后的状态)EAX和EDI中都保存有最开始的栈帧地址,

所以我们的思路为:

  1. 将EDI与ESI进行交换,使得ESI中保存有初始的栈帧地址。
  2. 将EAX自增一定量的字节数后(本次试验中,增加了0x100),使其跳转到shellcode附近
  3. 利用MOV DWORD PTR DS:[ESI+10],EAX将EAX的值写入到ESI+10所指向的地址中,覆盖参数一

覆盖后,继续进行第二个参数的处理,第二个参数和第一个参数是一样的,因此大致思路一样,在第一次覆盖后,寄存器状态如下:


只关注EAX,因为其仍然保存了0x0010F734这个数值,所以要先将其赋给ESI,以便后续继续使用。

  1. 保存EAX到ESI
  2. EAX自增(本次自增100)
  3. ESI+4(跳过第一个参数的偏移)
  4. 利用MOV DWORD PTR DS:[ESI+10],EAX将EAX的值写入到ESI+10所指向的地址中,覆盖参数二

覆盖后:


参数三:

  1. 保存栈帧
  2. XOR EAX,EAX
  3. EAX增加300
  4. ESI+4(跳过第二个参数的偏移)
  5. 存入参数三位置

参数四:

  1. 保存栈帧
  2. XOR EAX,EAX
  3. EAX增加40
  4. ESI+4(跳过第三个参数的偏移)
  5. 存入参数四位置

参数五:

事先预定好的一个Writable地址,全部完成后,内存区域如下

寄存器情况如下,

可以看到,EAX指向了VirtualProtect函数的地址,接下来可以利用XCHG EAX,ESP这样的命令使得ESP退回到0x0010F740,之后再RETN就可以顺利的调用VirtualProtect函数,函数返回后便会返回到参数一所设置的shellcode的起始地址了,而shellcode所在地址也顺利被标记为可执行的了!

如图,EIP顺利落入Slide区域执行指令








原创粉丝点击