突破SafeSEH之理论篇
工作原理:编译器启动SafeSEH选项后,在编译时将所有异常处理函数地址编入一张表,俗称“良民册”。当程序调用异常处理函数时,会检查该函数是否位于该册①。
①处再具体一些:
- 检查异常处理链是否位于当前程序的栈中,若不在则停止调用异常处理函数
- 检查异常处理函数指针是否位于当前程序的栈中,若在则停止调用异常处理函数
- 前两个通过则调用
RtlIsValidHandler()
来验证异常处理函数的有效性
(我觉得原图有点问题,所以把其中一条路径改了)
伪代码如下:
BOOL RtlIsValidHandler( handler )
{
if (handler is in the loaded image) // 在加载模块的内存空间内
{
if (image has set the IMAGE_DLLCHARACTERISTICS_NO_SEH flag)
return FALSE; // 程序设置了忽略异常处理
if (image has a SafeSEH table) // 含有 SafeSEH 表说明程序启用了 SafeSEH
if (handler found in the table) // 异常处理函数地址在表中
return TRUE;
else
return FALSE;
if (image is a .NET assembly with the ILonly flag set)
return FALSE; // 包含 IL 标志的 .NET 中间语言程序
}
if (handler is on non-executable page) // 在不可执行页上
{
if (ExecuteDispatchEnable bit set in the process flags)
return TRUE; // DEP 关闭
else
raise ACCESS_VIOLATION; // 访问违例异常
}
if (handler is not in an image) // 在可执行页上,但在加载模块之外
{
if (ImageDispatchEnable bit set in the process flags)
return TRUE; // 允许加载模块内存空间外执行
else
return FALSE;
}
return TRUE; // 允许执行异常处理函数
}
可知有四条路径允许异常处理函数执行,分别对应上图通向验证通过的四个箭头。(《0day》和网上所查资料都只说了三条路径,暂不知为何。所以图中最下方的那条路径本文暂时不涉及。)
所以突破SafeSEH的方法有:
- 不攻击S.E.H,直接考虑ret攻击或者覆盖虚表等
- 在DEP关闭的前提下,在加载模块内存范围外找跳板指令跳入shellcode
程序加载到内存后,在它所占的内存空间里,除了PE模块、dll模块,还有一些映射文件:
当异常处理函数指针位于这些地址时,SafeSEH会无视掉。 - 攻击没有启用SafeSEH的模块
- 清空模块的安全S.E.H表,营造未开启SafeSEH的假象;(将我们的指令注册到安全S.E.H表中是不太可能的,因为其加密存储)
- 终极杀器:用堆区shellcode地址覆盖S.E.H中的异常处理函数地址,当发生异常时,就可以直接跳转执行!(就算该地址不在“良民册”上也无所谓)
Those who seek some sort of a higher purpose or 'universal goal', who don't know what to live for, who moan that they must 'find themselves'. You hear it all around us. That seems to be theofficial bromide of our century. Every book you open. Every drooling self-confession. It seems to be the noble thing to confess. I'd think it would be the most shameful one.