SEH stack 结构探索(5)--- __exception_handler4() 探秘3之 TryLevel
现在我们探索一下 SEH stack 里的 TryLevel
到底 TryLevel 是什么东西,来看看下面的代码:
__try { /* 0-level */ __try { /* 1-level */ __try { /* 2-level */ } __except(1) { } } __except(1) { } } __except(1) { } |
上面的代码嵌套了三层 __try{} 结构,那么最外层被称作 0-level,依次往里最里一层是 2-level,这些层并不是我凭空编出来的,看看 VC++ 的编译结果:
__try { 00FB3428 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 __try { 00FB342F C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 __try { 00FB3436 C7 45 FC 02 00 00 00 mov dword ptr [ebp-4],2 } __except(1) { 00FB343D C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 00FB3444 EB 10 jmp $LN16+0Ah (0FB3456h) $LN15: 00FB3446 B8 01 00 00 00 mov eax,1 $LN17: 00FB344B C3 ret $LN16: 00FB344C 8B 65 E8 mov esp,dword ptr [ebp-18h] } 00FB344F C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 } __except(1) { 00FB3456 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 00FB345D EB 10 jmp $LN12+0Ah (0FB346Fh) $LN11: 00FB345F B8 01 00 00 00 mov eax,1 $LN13: 00FB3464 C3 ret $LN12: 00FB3465 8B 65 E8 mov esp,dword ptr [ebp-18h] } 00FB3468 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 } __except(1) { 00FB346F C7 45 FC FE FF FF FF mov dword ptr [ebp-4],0FFFFFFFEh 00FB3476 EB 10 jmp $LN8+0Ah (0FB3488h) $LN7: 00FB3478 B8 01 00 00 00 mov eax,1 $LN9: 00FB347D C3 ret $LN8: 00FB347E 8B 65 E8 mov esp,dword ptr [ebp-18h] } 00FB3481 C7 45 FC FE FF FF FF mov dword ptr [ebp-4],0FFFFFFFEh |
在 __try {} 块头编译器生成设置 [ebp-4] 值的代码,每个 __try{} 就是一层,红色粗体部分代码显示正如前面所说的:最外层被设为 0 层,最内层被设为 2 层。
蓝色粗体部分代码显示每退出一层,就设回上一层,最后粉红色粗体代码部分显示:退出所有的层后,被设回初始值 0xFFFFFFFF
好啦,这个 [ebp-4] 位置就是我想要说的 TryLevel 值所在。
那么,我们对 SEH stack 的布局了解又增进了一分:
![](http://www.mouseos.com/images/EH4_stack.png)
看完上图,我们会有许多疑问:这个 TryLevel 是怎样作用的? ScopeTable 后面的结构是怎样的?下面我会作出解答。
下面我们还是先看一段代码:
BOOL SafeDiv(INT32 dividend, INT32 divisor, INT32 *pResult) { __try { *pResult = dividend / divisor; } __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { return FALSE; } return TRUE; } |
在 __try {} 里对这个除法进行保护,当除 0 时就会产生除 0 异常,异常码(ExceptionCode)是 EXCEPTION_INT_DIVIDE_BY_ZERO,那么系统应该去处理除 0 的Exception Handler(异常处理程序)。
现在我们重点放在 __except() 块里,括号里的表达式被称为 Exception Filter(异常过滤)表达式。这个表达式结果是什么在这里不是重点,重点是看看 VC++ 编译出来的代码:
__except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) 00D63444 8B 45 EC mov eax,dword ptr [ebp-14h] 00D63447 8B 08 mov ecx,dword ptr [eax] 00D63449 8B 11 mov edx,dword ptr [ecx] 00D6344B 89 95 20 FF FF FF mov dword ptr [ebp-0E0h],edx 00D63451 8B 85 20 FF FF FF mov eax,dword ptr [ebp-0E0h] 00D63457 33 C9 xor ecx,ecx 00D63459 3D 94 00 00 C0 cmp eax,0C0000094h 00D6345E 0F 94 C1 sete cl 00D63461 8B C1 mov eax,ecx $LN7: 00D63463 C3 ret $LN6: 00D63464 8B 65 E8 mov esp,dword ptr [ebp-18h] { return FALSE; 00D63467 C7 85 14 FF FF FF 00 00 00 00 mov dword ptr [ebp-0ECh],0 00D63471 C7 45 FC FE FF FF FF mov dword ptr [ebp-4],0FFFFFFFEh 00D63478 8B 85 14 FF FF FF mov eax,dword ptr [ebp-0ECh] 00D6347E EB 0C jmp $LN6+28h (0D6348Ch) } |
上面这段就是 VC++ 编译出来的代码。在 __except() 的括号里未进入 { } 块里进行了许多处理,我关心的就是括号里的这段代码。
为什么我会这么关注这段代码?当然很关键,它关系到 TryLevel 是如何运作的。
这段代码被称为 FilterFunc(过滤器函数),TryLevel 就决定是怎样调用这段 FilterFunc 函数:
00D63444 8B 45 EC mov eax,dword ptr [ebp-14h] 00D63447 8B 08 mov ecx,dword ptr [eax] 00D63449 8B 11 mov edx,dword ptr [ecx] 00D6344B 89 95 20 FF FF FF mov dword ptr [ebp-0E0h],edx 00D63451 8B 85 20 FF FF FF mov eax,dword ptr [ebp-0E0h] 00D63457 33 C9 xor ecx,ecx 00D63459 3D 94 00 00 C0 cmp eax,0C0000094h 00D6345E 0F 94 C1 sete cl 00D63461 8B C1 mov eax,ecx $LN7: 00D63463 C3 ret |
这段 FilterFunc 会返回给调用它的 caller,这一切答案就在 __except_handler4_common() 里,我们继续接着上一篇来看 __except_handler4_common() 的代码:
01152487 8b5510 mov edx,dword ptr [ebp+10h] ; ExceptionRecord 0115248a 8b4204 mov eax,dword ptr [edx+4] ; ExceptionRecord->ExceptionFlags 0115248d 83e066 and eax,66h 01152490 0f8523010000 jne MSVCR100D!_except_handler4_common+0x179 (011525b9) MSVCR100D!_except_handler4_common+0x56: 01152496 8b4d10 mov ecx,dword ptr [ebp+10h] ; ExceptionRecord 01152499 894df8 mov dword ptr [ebp-8],ecx ; [ExceptionRecord] 0115249c 8b5518 mov edx,dword ptr [ebp+18h] ; ContextRecord 0115249f 8955fc mov dword ptr [ebp-4],edx ; [ContextRecord] 011524a2 8b45e0 mov eax,dword ptr [ebp-20h] ; [RegistrationNode] 011524a5 8d4df8 lea ecx,[ebp-8] ; &ExceptonRecord 011524a8 894804 mov dword ptr [eax+4],ecx ; ExceptionPointers = &ExceptionRecord 011524ab 8b55e0 mov edx,dword ptr [ebp-20h] ; [RegistrationNode] 011524ae 8b4214 mov eax,dword ptr [edx+14h] ; get TryLevel 011524b1 8945d8 mov dword ptr [ebp-28h],eax 011524b4 eb06 jmp MSVCR100D!_except_handler4_common+0x7c (011524bc) MSVCR100D!_except_handler4_common+0x76: 011524b6 8b4dec mov ecx,dword ptr [ebp-14h] 011524b9 894dd8 mov dword ptr [ebp-28h],ecx MSVCR100D!_except_handler4_common+0x7c: 011524bc 837dd8fe cmp dword ptr [ebp-28h],0FFFFFFFEh 011524c0 0f84f1000000 je MSVCR100D!_except_handler4_common+0x177 (011525b7) MSVCR100D!_except_handler4_common+0x86: 011524c6 8b55d8 mov edx,dword ptr [ebp-28h] 011524c9 6bd20c imul edx,edx,0Ch ; TryLevel * 12 011524cc 8b45d4 mov eax,dword ptr [ebp-2Ch] ; ScopeTable 011524cf 8d4c1010 lea ecx,[eax+edx+10h] ; ScopeRecord 011524d3 894de4 mov dword ptr [ebp-1Ch],ecx 011524d6 8b55e4 mov edx,dword ptr [ebp-1Ch] 011524d9 8b4204 mov eax,dword ptr [edx+4] ; get FilterFunc 011524dc 8945d0 mov dword ptr [ebp-30h],eax 011524df 8b4de4 mov ecx,dword ptr [ebp-1Ch] 011524e2 8b11 mov edx,dword ptr [ecx] ; EnclosingLevel 011524e4 8955ec mov dword ptr [ebp-14h],edx 011524e7 837dd000 cmp dword ptr [ebp-30h],0 ; FilterFunc == NULL ? 011524eb 0f84c1000000 je MSVCR100D!_except_handler4_common+0x172 (011525b2) MSVCR100D!_except_handler4_common+0xb1: 011524f1 8b55e8 mov edx,dword ptr [ebp-18h] 011524f4 8b4dd0 mov ecx,dword ptr [ebp-30h] ; 传 FilterFunc 给 EH4_CallFilterFunc() 执行 011524f7 e806b4ffff call MSVCR100D!_EH4_CallFilterFunc (0114d902) |
1. 获取 TryLevel
011524ab 8b55e0 mov edx,dword ptr [ebp-20h] ; [RegistrationNode] 011524ae 8b4214 mov eax,dword ptr [edx+14h] ; get TryLevel |
请对照上面的 EH4 stack 结构图来看,[ebp-20h] 是上一篇分析得来的 RegistrationNode(局部变量),这个变量装的就是 EH4 stack 里保存 esp 的位置,那么[edx+14h] 也就是 esp 的位置加上 14h 得出 TryLevel 的位置。
再一次提醒:参照上面的 EH4 stack 进行理解
2. 检查 TryLevel 值
011524bc 837dd8fe cmp dword ptr [ebp-28h],0FFFFFFFEh 011524c0 0f84f1000000 je MSVCR100D!_except_handler4_common+0x177 (011525b7) |
[ebp-28h] 是获得 TryLevel 保存在局部 stack 的位置,代码中检查 TryLevel 是否为 0xFFFFFFFE 值,如果是 0xFFFFFFFE 值的话说明它是初始的 TryLevel 值,也就是没有可以处理的 FilterFunc,上面我们已经看到,当所有的 __try{} 退出后,会重新将 TryLevel 设为 0xFFFFFFFE 值
3. 获得 FilterFunc 函数
011524c6 8b55d8 mov edx,dword ptr [ebp-28h] 011524c9 6bd20c imul edx,edx,0Ch ; TryLevel * 12 011524cc 8b45d4 mov eax,dword ptr [ebp-2Ch] ; ScopeTable 011524cf 8d4c1010 lea ecx,[eax+edx+10h] ; ScopeRecord 011524d3 894de4 mov dword ptr [ebp-1Ch],ecx 011524d6 8b55e4 mov edx,dword ptr [ebp-1Ch] 011524d9 8b4204 mov eax,dword ptr [edx+4] ; get FilterFunc 011524dc 8945d0 mov dword ptr [ebp-30h],eax 011524df 8b4de4 mov ecx,dword ptr [ebp-1Ch] 011524e2 8b11 mov edx,dword ptr [ecx] ; EnclosingLevel 011524e4 8955ec mov dword ptr [ebp-14h],edx 011524e7 837dd000 cmp dword ptr [ebp-30h],0 ; FilterFunc == NULL ? 011524eb 0f84c1000000 je MSVCR100D!_except_handler4_common+0x172 (011525b2) |
首先计算得出 EH4_SCOPETABLE_RECORD 结构,这个 EH4_SCOPETABLE_RECORD 是 3 个成共 12 bytes
0:000:x86> dt _EH4_SCOPETABLE_RECORD MSVCR100D!_EH4_SCOPETABLE_RECORD +0x000 EnclosingLevel : Uint4B +0x004 FilterFunc : Ptr32 long +0x008 u : <unnamed-tag> |
TryLevel * 12 得出来的结果就是获得哪一层的 ScopeRecord,假如 TryLevel 为 0,代表它正处于 0-level,此时 TryLevel * 12 = 0;
接下来 [eax+edx+10h] 也就是:ScopeTable 的基址加上 10h 偏移量,再加上 (TryLevel * 12) 的值,那就得到哪一层的 EH4_SCOPETABLE_RECORD
[edx+4] 结果就是 FilterFunc 函数的地址,最后测试 FilterFunc == NULL
因此:得到 FilterFunc 的方法是:
计算方法是: FilterFunc = ScapeTable + TryLevel * sizeof(struct _EH4_SCOPETABLE_RECORD) + 0x10; |
4. 将 FilterFunc 交由 EH4_CallFilterFun() 执行
011524f1 8b55e8 mov edx,dword ptr [ebp-18h] 011524f4 8b4dd0 mov ecx,dword ptr [ebp-30h] ; 传 FilterFunc 给 EH4_CallFilterFunc() 执行 011524f7 e806b4ffff call MSVCR100D!_EH4_CallFilterFunc (0114d902) |
[ebp-18h] 的值是 FramePointer 也就是 ebp,[ebp-30h] 的值是 FilterFunc 函数地址。
5. EH4_CallFilterFun() 函数
MSVCR100D!_EH4_CallFilterFunc: 6b78d902 55 push ebp 6b78d903 56 push esi 6b78d904 57 push edi 6b78d905 53 push ebx 6b78d906 8bea mov ebp,edx 6b78d908 33c0 xor eax,eax 6b78d90a 33db xor ebx,ebx 6b78d90c 33d2 xor edx,edx 6b78d90e 33f6 xor esi,esi 6b78d910 33ff xor edi,edi 6b78d912 ffd1 call ecx 6b78d914 5b pop ebx 6b78d915 5f pop edi 6b78d916 5e pop esi 6b78d917 5d pop ebp 6b78d918 c3 ret |
其中这句 call ecx 跳转到 FilterFunc 去执行,而 FileterFunc 就是我们上面所看到的 __excpt() 块括号内的代码。
现在我们明白了 TryLevel 是怎样运作了吧。也明白了嵌套 __try{} 的结构了吧。还明白了怎样调用 FilterFunc 了吧。
今天的收获还不错,:)
上一页 目录 下一页
版权 mik 所有,转载请注明出处!