[原创]SEH和SafeSEH
昨天写了个驱动,用vs2010编译不了,但是wdk可以编译。vs2010提示是

查了很多资料也不知道什么原因,有的说打开/Gs选项,然后链接上一个bufferoverflowU.lib文件,但是出错提示更多符号找不到,仔细看了下问题是链接ntoskrnl.lib的时候有个loadcfg.obj找不到__security_cookie的符号,其实这个符号很常见,用于检测堆栈是否被溢出的,/Gs开关就是控制是否做这个检测的。问题是我链接了那个提供这个符号lib还是不行。试了下xp却可以编译。在vs的目录搜索loadcfg,发现了loadcfg.c的文件,看了下里面用到了__security_cookie,用winhex打开ntoskrnl.lib也能搜到loadcfg.obj,所以问题出在这个.c文件,它也参加了编译和链接。用loadcfg.c里面的其他内容搜索到了原因,应该SafeSEH需要关闭,链接命令加上SafeSEH:NO就行了(还不懂求解释)。结合代码和资料复习了一下SEH,并学习了一下SafeSEH。不过后来搜其他结构的时候发现原来早就有这个资料了,但是就当笔记记录一下吧。
随便写个函数,然后Release编译下
1,SecurityCheck,这是普通的打开/Gs需要进行检查的

函数会用__security_cookie xor加密一下ebp,存放起来,也会加密一下scopetable的地址。ebp-10就是异常框架的起点了。在函数结尾,会将ebp ^ cookie的值异或上正确的ebp,用_security_check_cookie检查结果和cookie是否相等,不相等说明ebp-1c的位置被修改了,所以能简单地阻止被溢出。溢出我没研究过。。。还有xxxx的位置,是异常分发下来填写的,用GetExceptionInformation获取这个xxxx,xxxx,xxxx+4就是Context和ExceptionRecord的结构指针, r0分发的时候把2个结构打包到用户堆栈。
2,SafeSEH
异常分发到r3之后的顺序是KiUserExceptionDispatcher -> RtlDispatchException,不考虑VEH的话,会先判断这2个条件:异常处理的框架在堆栈,异常处理的Handler不在堆栈上,不满足就RaiseException。调用ZwQueryInformationProcess查看一下ProcessExecuteFlags属性,得到ExecuteOption。之后就是RtlIsValidHandler,来检测是否Handler是否正常, ExecuteOption作为参数参入RtlIsValidHandler。
RtlIsValidHandler-> RtlLookupFunctionTable,查看一下信息。
.text:77EE8B68 mov esi, _LdrpInvertedFunctionTable ; 这个是需要检查的模块数目。 .text:77EE8B6E cmp esi, ebx .text:77EE8B70 jz loc_77EDD3F6 .text:77EE8B76 dec esi .text:77EE8B77 .text:77EE8B77 loc_77EE8B77: ; CODE XREF: RtlLookupFunctionTable(x,x,x)+69 j .text:77EE8B77 ; RtlLookupFunctionTable(x,x,x)+271 j .text:77EE8B77 cmp esi, ebx .text:77EE8B79 jl loc_77EDD3F6 ; 没找到,就跳走,之后会返回0 .text:77EE8B7F lea ecx, [esi+ebx] ; ebx=0,esi=count-1,这是二分查找,esi是high,ebx是low指针 .text:77EE8B82 sar ecx, 1 .text:77EE8B84 mov eax, ecx .text:77EE8B86 shl eax, 4 ; 每一项对应的是16byte大小的结构。off0用于解密 .text:77EE8B89 add eax, offset unk_77F9AC0C ; 全局变量,是这个模块表的起点 .text:77EE8B8E mov edi, [eax+4] ; off4是模块映像基地址,off8是大小,offc是SafeSeh的数量 .text:77EE8B91 mov edx, [eax+8] .text:77EE8B94 add edx, edi .text:77EE8B96 cmp [ebp+Handler], edi .text:77EE8B99 jb loc_77EE8DA4 ; 调整high的指针 .text:77EE8B9F cmp [ebp+Handler], edx .text:77EE8BA2 jb short loc_77EE8BA9 ; 找到了 .text:77EE8BA4 lea ebx, [ecx+1] ; 调整low指针 .text:77EE8BA7 jmp short loc_77EE8B77 .text:77EE8BA9 ; --------------------------------------------------------------------------- .text:77EE8BA9 .text:77EE8BA9 loc_77EE8BA9: ; CODE XREF: RtlLookupFunctionTable(x,x,x)+64 j .text:77EE8BA9 mov ecx, edi .text:77EE8BAB mov [ebp+var_1C], ecx .text:77EE8BAE mov ecx, [eax+0Ch] .text:77EE8BB1 mov edx, [ebp+arg_8] ; 第三个参数返回SafeSeh(handler)的个数 .text:77EE8BB4 mov [edx], ecx .text:77EE8BB6 push dword ptr [eax] ; off0用于解密 .text:77EE8BB8 call _RtlDecodeSystemPointer@4 ; RtlDecodeSystemPointer(x) .text:77EE8BBD mov esi, eax ; 得到的结果就是这个模块的SafeSeh表的地址,返回的也是这个值
得到了Handler所在模块的SafeSEH函数表,如果有这张表,Hander就必须在表中。也是按地址从低到高排列的,二分查找一下。
.text:77EE8ADD call _RtlLookupFunctionTable@12 ; RtlLookupFunctionTable(x,x,x) .text:77EE8AE2 xor edi, edi .text:77EE8AE4 mov [ebp+SafeSehTable], eax .text:77EE8AE7 cmp eax, edi .text:77EE8AE9 jz ModDontHaveSehTable ; 不在模块内部 .text:77EE8AEF mov esi, [ebp+nCount] .text:77EE8AF2 cmp esi, edi .text:77EE8AF4 jz ModDontHaveSehTable ; 或者在模块内部,但是模块本身没有sehtable .text:77EE8AFA cmp eax, 0FFFFFFFFh .text:77EE8AFD jz loc_77F4DE4A .text:77EE8B03 .text:77EE8B03 loc_77EE8B03: ; CODE XREF: RtlIsValidHandler(x,x)+65390 j .text:77EE8B03 sub ebx, [ebp+pMouduleBase] ; 得到Handler的Rva .text:77EE8B06 xor edx, edx .text:77EE8B08 cmp esi, edi ; esi是high索引,edx是low索引 .text:77EE8B0A jl loc_77F37DAE ; 没有找到 .text:77EE8B10 .text:77EE8B10 loc_77EE8B10: ; CODE XREF: RtlIsValidHandler(x,x)+198 j .text:77EE8B10 lea ecx, [edx+esi] .text:77EE8B13 sar ecx, 1 .text:77EE8B15 mov edi, [eax+ecx*4] ; SafeSehTable里面的项 .text:77EE8B18 cmp ebx, edi .text:77EE8B1A jb loc_77EE8D94 ; 调整一下索引 .text:77EE8B20 ja loc_77EE8C5F ; 调整一下索引 .text:77EE8B26 .text:77EE8B26 RetTrue: ; CODE XREF: RtlIsValidHandler(x,x)+4F5CF j .text:77EE8B26 ; RtlIsValidHandler(x,x)+653C2 j ... .text:77EE8B26 mov al, 1 ; Return True .text:77EE8B28 .text:77EE8B28 Ret: ; CODE XREF: RtlIsValidHandler(x,x)+65398 j .text:77EE8B28 ; RtlIsValidHandler(x,x)+65403 j .text:77EE8B28 mov ecx, [ebp+var_4] .text:77EE8B2B pop edi .text:77EE8B2C pop esi .text:77EE8B2D xor ecx, ebp .text:77EE8B2F pop ebx .text:77EE8B30 call @__security_check_cookie@4 ; __security_check_cookie(x) .text:77EE8B35 leave .text:77EE8B36 retn 8
没找到模块或者模块没有SafeSEH就继续判断。
.text:77F3807A ModDontHaveSehTable: ; CODE XREF: RtlIsValidHandler(x,x)+2D j .text:77F3807A ; RtlIsValidHandler(x,x)+38 j .text:77F3807A cmp [ebp+ExeOption], edi .text:77F3807D jz loc_77F37D91 .text:77F38083 .text:77F38083 CmpExecuteOption: ; CODE XREF: RtlIsValidHandler(x,x)+4F2E7 j .text:77F38083 ; RtlIsValidHandler(x,x)+653A8 j .text:77F38083 mov eax, [ebp+ExeOption] .text:77F38086 and eax, 30h .text:77F38089 cmp al, 30h ; ExecuteDispatchEnable | ImageDispatchEnable .text:77F3808B jz RetTrue ; Return True .text:77F38091 jmp loc_77F4DE69
当ExecuteOption的ExecuteDispatchEnable和ImageDispatchEnable位都为1,就直接返回TRUE,表示Handler可以执行。否则查询一下Handler所在内存的情况。
.text:77F4DE69 loc_77F4DE69: ; CODE XREF: RtlIsValidHandler(x,x)+4F5D5 j .text:77F4DE69 lea eax, [ebp+var_14] .text:77F4DE6C push eax .text:77F4DE6D push 1Ch .text:77F4DE6F lea eax, [ebp+var_30] .text:77F4DE72 push eax ; Buffer .text:77F4DE73 push edi ; 0 = MemoryBasicInformation .text:77F4DE74 push ebx ; Handler .text:77F4DE75 push 0FFFFFFFFh .text:77F4DE77 call _NtQueryVirtualMemory@24 ; NtQueryVirtualMemory(x,x,x,x,x,x) .text:77F4DE7C test eax, eax .text:77F4DE7E jl RetTrue ; Return True .text:77F4DE84 test [ebp+var_1C], 0F0h ; #define PAGE_EXECUTE 0x10 .text:77F4DE84 ; #define PAGE_EXECUTE_READ 0x20 .text:77F4DE84 ; #define PAGE_EXECUTE_READWRITE 0x40 .text:77F4DE84 ; #define PAGE_EXECUTE_WRITECOPY 0x80 .text:77F4DE88 jz short HandlerNotExecutive .text:77F4DE8A cmp [ebp+var_18], MEM_IMAGE ; #define SEC_IMAGE 0x1000000 .text:77F4DE91 jnz short loc_77F4DEB7 .text:77F4DE93 lea eax, [ebp+nCount] .text:77F4DE96 push eax .text:77F4DE97 lea eax, [ebp+SafeSehTable] .text:77F4DE9A push eax .text:77F4DE9B push [ebp+Base] .text:77F4DE9E call _RtlCaptureImageExceptionValues@12 ; RtlCaptureImageExceptionValues(x,x,x) .text:77F4DEA3 cmp [ebp+SafeSehTable], edi .text:77F4DEA6 jz RetTrue ; Return True .text:77F4DEAC cmp [ebp+nCount], edi .text:77F4DEAF jz RetTrue ; Return True .text:77F4DEB5 jmp short RetFalse ; Table和Count都=-1,返回0 .text:77F4DEB7 ; ---------------------------------------------------------------------------
当两个符号都不都为1,先看内存的Protect属性,如果不在可执行段,但是ExecuteDispatchEnable为1,就返回TRUE,Handler可以执行,否则RaiseException。如下。
.text:77F4DEC4 HandlerNotExecutive: ; CODE XREF: RtlIsValidHandler(x,x)+653CC j .text:77F4DEC4 test byte ptr [ebp+ExeOption], 10h .text:77F4DEC8 jnz RetTrue ; Return True .text:77F4DECE push 0FFFFFFFEh .text:77F4DED0 push 0FFFFFFFEh .text:77F4DED2 jmp short loc_77F4DE59
如果处于可执行段,再检查是否是映像文件,不属于映像文件,但是ImageDispatchEnable置为1也可以执行。
.text:77F4DEB7 loc_77F4DEB7: ; CODE XREF: RtlIsValidHandler(x,x)+653D5 j .text:77F4DEB7 mov al, byte ptr [ebp+ExeOption] .text:77F4DEBA shr al, 5 .text:77F4DEBD and al, 1 .text:77F4DEBF jmp Ret
最后如果Handler属于可执行内存且不是映像文件,从映像头部在获取一次SafeSEH。这次调用了RtlCaptureImageExceptionValues。这个函数不是分开的了,直接F5了。。
int __thiscall RtlCaptureImageExceptionValues(void *this, int a2, int a3, int a4)
{
int v4; // eax@2
int v5; // eax@4
int result; // eax@5
int v7; // ecx@8
void *v8; // [sp+0h] [bp-4h]@1
v8 = this;
if ( !(*(_WORD *)(RtlImageNtHeader(a2) + 94) & 0x400) )// IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400
{
v4 = RtlImageDirectoryEntryToData(a2, 1, 10, (int)&v8);
if ( v4 && v8 && (v8 == (void *)64 || v8 == *(void **)v4) )
{
if ( *(_DWORD *)v4 >= 0x48u )
{
v7 = *(_DWORD *)(v4 + 64);
if ( v7 )
{
if ( *(_DWORD *)(v4 + 68) )
{
*(_DWORD *)a3 = v7;
result = *(_DWORD *)(v4 + 68);
*(_DWORD *)a4 = result;
return result;
}
}
}
}
v5 = RtlImageDirectoryEntryToData(a2, 1, 14, (int)&a2);// 传说里的.net的某个标志?PeLord写的是com。
if ( !v5 || !(*(_BYTE *)(v5 + 16) & 1) )
{
*(_DWORD *)a3 = 0;
result = a4;
*(_DWORD *)a4 = 0;
return result;
}
}
*(_DWORD *)a3 = -1;
result = a4;
*(_DWORD *)a4 = -1;
return result;
}
RtlImageDirectoryEntryToData返回目录项的Va和大小(v8和a2),第一个结构IMAGE_LOAD_CONFIG_DIRECTORY32_2是那个loadcfg.c里面设置的,有点关联?查到了然后读出SafeSEH table和项数。#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14,第二次查的是这个。COM Runtime descriptor。不太清楚,看资料。可能跟.net有关。这里返回0的,只有两种情况,必须没有IMAGE_DLLCHARACTERISTICS_NO_SHE,同时LoadConfig目录读出来没找到SafeSEH table(项数为0),或者第15个目录某个标志位没有置位。返回之后会对比结果。只有返回0,才能认为Handler可以执行。
总结一下SafeSEH就是对比Handler是不是正常的Handler。如果找到了Table,就必须在Table中。如果没找到,就看ExecuteDispatchEnable和ImageDispatchEnable ,同时置1就直接让Handler执行。不是同时为1,就先看Handler所在内存是不是可执行,若不是可执行,如果ExecuteDispatchEnable = 1就 return TRUE,否则FALSE。如果内存可执行也是MEM_IMAGE类型的内存,得看ImageDispatchEnable,置位了就TRUE,否则FALSE。如果不是MEM_IMAGE类型的可执行内存,再给次机会看看Handler所在模块的基地址,如果还是没找到,也放行。
虽然能编译,不过问题的原因还是不知道,感觉问题是这里,搜loadcfg找到的不明字符串。
