Windows异常世界历险记(三)——VC6中结构化异常处理机制的反汇编分析(上)
2018-01-29 10:28
288 查看
在《Visual C++异常处理机制原理与应用》部分,我们讲解了Visual C++提供的结构化异常处理机制,并对其中比较简单的终止型异常处理程序在部分条件下(自然执行、提前越出、__leave关键字等)进行了反汇编分析。下面我们继续对整个Visual C++结构化异常处理机制进行分析。
本次分析的目标环境是VC++ 6.0,这是一款已经有些“美人迟暮”的集成开发环境。使用它进行分析,主要原因如下:
仍有不少程序使用VC++ 6.0开发和编译(特别是恶意代码、灰产代码等)
Visual Studio的发展是一脉相承的,VC++ 6.0所使用的异常处理机制也为后续Visual Studio所借鉴并发展,大框架并没变。VC++ 6.0中的结构化异常机制相对比较简单,却囊括了所有关键部分。
因此,下面先在VC++ 6.0环境下进行相关分析,完毕后再分析Visual Studio 2017中C/C++结构化异常处理机制。
在WinMain中设置了两个try块,其中:
内层的try块为try-finally终止异常处理
外层try块为try-except型异常处理,能处理任何异常程序
而在WinMain中被try块保护范围内,调用了一个名为RaiseExcept的函数,该函数被两层try块保护,其中执行了一段导致违规访问的代码:
内层try块为try-finally终止型异常处理
外层try块为try-except异常处理,但不处理违规访问类型的异常
按照之前的分析,执行的顺序是:
RaiseExcept中外层try-except中的过滤函数,而该异常处理块不处理此类型异常;
转而执行WinMain中外层try-except的过滤函数,并确定该异常处理块可以执行此类异常;
执行RaiseExcept内层finally块的代码
执行WinMain内层finally块的代码
执行WinMain中try-except的异常处理代码
而在函数返回前,将该节点从SEH链中摘除:
这里用到ebp-10进行寻址。那么这个地址存的是什么呢?画个栈帧图看一下:
从上图可以看出,ebp-0x10处存的正好是SEH链上下一节点的地址。
该值在函数入口处为0xFF:
在进入最外层try块时被置为0:
而在进入第二层try块时被置为1:
在出第二层try块时又被置回0:
在出最外层try块是被置为0xFFFFFFFF:
根据上述事实,我们推测tryLevel局部变量用于记录当前所处的try的层级。
经过分析,该函数中的相关代码与我们之前的推测相符,关键节点都用注释在代码中标出了。在接下来的文章中,我们将结合调试,揭开__except_handler3函数的神秘面纱以及它背后的数据结构支持。
本次分析的目标环境是VC++ 6.0,这是一款已经有些“美人迟暮”的集成开发环境。使用它进行分析,主要原因如下:
仍有不少程序使用VC++ 6.0开发和编译(特别是恶意代码、灰产代码等)
Visual Studio的发展是一脉相承的,VC++ 6.0所使用的异常处理机制也为后续Visual Studio所借鉴并发展,大框架并没变。VC++ 6.0中的结构化异常机制相对比较简单,却囊括了所有关键部分。
因此,下面先在VC++ 6.0环境下进行相关分析,完毕后再分析Visual Studio 2017中C/C++结构化异常处理机制。
测试代码与分析
下面是用于测试的代码:#include <windows.h> #include <tchar.h> int ExceptFilterInner(DWORD dwExceptionCode) { MessageBox(NULL, TEXT("Inner try`s filter running"), TEXT("Inner Try"), MB_OK); if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) { return EXCEPTION_CONTINUE_SEARCH; } else { return EXCEPTION_CONTINUE_EXECUTION; } } int ExceptFilterOuter() { MessageBox(NULL, TEXT("Outer try`s filter running"), TEXT("Outer Try"), MB_OK); //return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_EXECUTE_HANDLER; } void RaiseExcept() { __try { _try { *(PDWORD)NULL = 0; } __finally { MessageBox(NULL, TEXT("Inner::Finally block execute"), TEXT("In Inner::finally"), MB_OK); } } __except (ExceptFilterInner(GetExceptionCode())) { MessageBox(NULL, TEXT("Never execute"), TEXT("haha"), MB_OK); } } int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd) { __try { __try { RaiseExcept(); } __finally { MessageBox(NULL, TEXT("Outer::Finally block execute"), TEXT("In Outer::finally"), MB_OK); } } __except (ExceptFilterOuter()) { MessageBox(NULL, TEXT("Outer try catched"), TEXT("haha"), MB_OK); } return 0; }
在WinMain中设置了两个try块,其中:
内层的try块为try-finally终止异常处理
外层try块为try-except型异常处理,能处理任何异常程序
而在WinMain中被try块保护范围内,调用了一个名为RaiseExcept的函数,该函数被两层try块保护,其中执行了一段导致违规访问的代码:
内层try块为try-finally终止型异常处理
外层try块为try-except异常处理,但不处理违规访问类型的异常
按照之前的分析,执行的顺序是:
RaiseExcept中外层try-except中的过滤函数,而该异常处理块不处理此类型异常;
转而执行WinMain中外层try-except的过滤函数,并确定该异常处理块可以执行此类异常;
执行RaiseExcept内层finally块的代码
执行WinMain内层finally块的代码
执行WinMain中try-except的异常处理代码
异常处理块的安装和卸载
WinMain中的异常处理函数
43: int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd) 44: { 00401220 55 push ebp 00401221 8B EC mov ebp,esp 00401223 6A FF push 0FFh 00401225 68 38 21 42 00 push offset string "In Outer::finally"+18h (00422138) 0040122A 68 60 14 40 00 push offset __except_handler3 (00401460) 0040122F 64 A1 00 00 00 00 mov eax,fs:[00000000] 00401235 50 push eax 00401236 64 89 25 00 00 00 00 mov dword ptr fs:[0],esp ; 省略部分代码 45: __try 00401255 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 ; tryLevel置为0,表明进入外层try块 46: { 47: __try 0040125C C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 ; tryLevel置为1,表明进入第二层try块 48: { 49: RaiseExcept(); 00401263 E8 A2 FD FF FF call @ILT+5(_RaiseExcept) (0040100a) 00401268 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 ; tryLevel置为0,表明回到外层try块 0040126F E8 02 00 00 00 call $L74113 (00401276) ; 调用finally函数 00401274 EB 1E jmp $L74116 (00401294) ; 调到finally块后继续执行 50: } 51: __finally 52: { 53: MessageBox(NULL, TEXT("Outer::Finally block execute"), TEXT("In Outer::finally"), MB_OK); ; 省略部分代码 $L74114: 00401293 C3 ret ; finally函数执行后ret $L74116: 00401294 C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh ; 要出try块了,tryLevel置-1 0040129B EB 2D jmp $L74110+27h (004012ca) ; 跳到ret 0处 54: } 55: } 56: __except (ExceptFilterOuter()) 0040129D E8 6D FD FF FF call @ILT+10(_ExceptFilterOuter) (0040100f) $L74111: 004012A2 C3 ret $L74110: 004012A3 8B 65 E8 mov esp,dword ptr [ebp-18h] 57: { 58: MessageBox(NULL, TEXT("Outer try catched"), TEXT("haha"), MB_OK); ; 省略部分代码 004012C3 C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh 59: } 60: return 0; 004012CA 33 C0 xor eax,eax 61: } 004012CC 8B 4D F0 mov ecx,dword ptr [ebp-10h] 004012CF 64 89 0D 00 00 00 00 mov dword ptr fs:[0],ecx 004012D6 5F pop edi 004012D7 5E pop esi 004012D8 5B pop ebx 004012D9 83 C4 58 add esp,58h 004012DC 3B EC cmp ebp,esp 004012DE E8 4D 00 00 00 call __chkesp (00401330) 004012E3 8B E5 mov esp,ebp 004012E5 5D pop ebp 004012E6 C2 10 00 ret 10h
__except_handler3与被保护函数
在VC中,异常处理程序的安装和卸载是基于函数的。只要在该函数中用到了VC提供的结构化异常处理机制(当然,这种机制也是建立在Windows SEH机制之上的),无论使用了几个try块,都会在函数入口处将__except_handler3函数加入到本线程SEH链的链首:0040122A 68 60 14 40 00 push offset __except_handler3 (00401460) 0040122F 64 A1 00 00 00 00 mov eax,fs:[00000000] 00401235 50 push eax 00401236 64 89 25 00 00 00 00 mov dword ptr fs:[0],esp
而在函数返回前,将该节点从SEH链中摘除:
004012CC 8B 4D F0 mov ecx,dword ptr [ebp-10h] 004012CF 64 89 0D 00 00 00 00 mov dword ptr fs:[0],ecx
这里用到ebp-10进行寻址。那么这个地址存的是什么呢?画个栈帧图看一下:
地址 | 内容 |
---|---|
ebp-0x14 | …… |
ebp-0x10 | 原fs:[0] |
ebp-0xC | __except_handler3 (00401460) |
ebp-8 | 0x00422138 |
ebp-4 | 0xFF |
ebp | 上帧ebp的值 |
tryLevel
在这段代码中,我们注意到ebp-4这个地址,在文件符号中,该地址对应的符号名为tryLevel。该值在函数入口处为0xFF:
00401223 6A FF push 0FFh
在进入最外层try块时被置为0:
45: __try 00401255 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 ; tryLevel置0,表明进入外层try块
而在进入第二层try块时被置为1:
47: __try 0040125C C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 ; tryLevel置1,表明进入第二层try块
在出第二层try块时又被置回0:
00401263 E8 A2 FD FF FF call @ILT+5(_RaiseExcept) (0040100a) 00401268 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 ; tryLevel置为0,表明回到外层try块
在出最外层try块是被置为0xFFFFFFFF:
00401294 C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh ; 要出try块了,tryLevel置-1 0040129B EB 2D jmp $L74110+27h (004012ca) ; 跳到ret 0处
根据上述事实,我们推测tryLevel局部变量用于记录当前所处的try的层级。
RaiseExcept中的异常处理函数
24: void RaiseExcept() 25: { 00401100 55 push ebp 00401101 8B EC mov ebp,esp 00401103 6A FF push 0FFh ; tryLevel为0xFF 00401105 68 C8 20 42 00 push offset string "In Inner::finally"+18h (004220c8) 0040110A 68 60 14 40 00 push offset __except_handler3 (00401460) 0040110F 64 A1 00 00 00 00 mov eax,fs:[00000000] 00401115 50 push eax 00401116 64 89 25 00 00 00 00 mov dword ptr fs:[0],esp ; 在SEH链头加入新节点 0040111D 83 C4 B4 add esp,0FFFFFFB4h 00401120 53 push ebx 00401121 56 push esi 00401122 57 push edi 00401123 89 65 E8 mov dword ptr [ebp-18h],esp 00401126 8D 7D A4 lea edi,[ebp-5Ch] 00401129 B9 11 00 00 00 mov ecx,11h 0040112E B8 CC CC CC CC mov eax,0CCCCCCCCh 00401133 F3 AB rep stos dword ptr [edi] 26: __try 00401135 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 ; tryLevel置0 27: { 28: _try 0040113C C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 ; tryLevel置1 29: { 30: *(PDWORD)NULL = 0; 00401143 C7 05 00 00 00 00 00 mov dword ptr ds:[0],0 0040114D C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 ; tryLevel置0 00401154 E8 02 00 00 00 call $L74095 (0040115b) 00401159 EB 1E jmp $L74098 (00401179) 31: } 32: __finally 33: { 34: MessageBox(NULL, TEXT("Inner::Finally block execute"), TEXT("In Inner::finally"), MB_OK); 0040115B 8B F4 mov esi,esp 0040115D 6A 00 push 0 0040115F 68 B0 20 42 00 push offset string "In Inner::finally" (004220b0) 00401164 68 8C 20 42 00 push offset string "Inner::Finally block execute" (0042208c) 00401169 6A 00 push 0 0040116B FF 15 AC A2 42 00 call dword ptr [__imp__MessageBoxA@16 (0042a2ac)] 00401171 3B F4 cmp esi,esp 00401173 E8 B8 01 00 00 call __chkesp (00401330) $L74096: 00401178 C3 ret $L74098: 00401179 C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh ; tryLevel置为0xFFFFFFFF 00401180 EB 3E jmp $L74092+27h (004011c0) 35: } 36: } 37: __except (ExceptFilterInner(GetExceptionCode())) 00401182 8B 45 EC mov eax,dword ptr [ebp-14h] 00401185 8B 08 mov ecx,dword ptr [eax] 00401187 8B 11 mov edx,dword ptr [ecx] 00401189 89 55 E4 mov dword ptr [ebp-1Ch],edx 0040118C 8B 45 E4 mov eax,dword ptr [ebp-1Ch] 0040118F 50 push eax 00401190 E8 70 FE FF FF call @ILT+0(_ExceptFilterInner) (00401005) 00401195 83 C4 04 add esp,4 $L74093: 00401198 C3 ret $L74092: 00401199 8B 65 E8 mov esp,dword ptr [ebp-18h] 38: { 39: MessageBox(NULL, TEXT("Never execute"), TEXT("haha"), MB_OK); 0040119C 8B F4 mov esi,esp 0040119E 6A 00 push 0 004011A0 68 84 20 42 00 push offset string "haha" (00422084) 004011A5 68 74 20 42 00 push offset string "Never execute" (00422074) 004011AA 6A 00 push 0 004011AC FF 15 AC A2 42 00 call dword ptr [__imp__MessageBoxA@16 (0042a2ac)] 004011B2 3B F4 cmp esi,esp 004011B4 E8 77 01 00 00 call __chkesp (00401330) 004011B9 C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh 40: } 41: } 004011C0 8B 4D F0 mov ecx,dword ptr [ebp-10h] 004011C3 64 89 0D 00 00 00 00 mov dword ptr fs:[0],ecx ; 摘链 004011CA 5F pop edi 004011CB 5E pop esi 004011CC 5B pop ebx 004011CD 83 C4 5C add esp,5Ch 004011D0 3B EC cmp ebp,esp 004011D2 E8 59 01 00 00 call __chkesp (00401330) 004011D7 8B E5 mov esp,ebp 004011D9 5D pop ebp 004011DA C3 ret
经过分析,该函数中的相关代码与我们之前的推测相符,关键节点都用注释在代码中标出了。在接下来的文章中,我们将结合调试,揭开__except_handler3函数的神秘面纱以及它背后的数据结构支持。
相关文章推荐
- Windows异常世界历险记(一)——Windows系统用户级结构化异常处理机制(SEH)的基础知识和Unwind展开操作
- Windows异常世界历险记(二)——Win32用户层下SEH机制之对RtlUnwind的逆向分析
- NGINX原理分析 之 SLAB分配机制 (转)
- Android应用程序消息处理机制(Looper、Handler)分析
- 分析Android版QQ游戏大厅中游戏的启动机制
- 深入分析 Java I/O 的工作机制
- Android系统篇之----Binder机制和远程服务调用机制分析
- 消息机制Handler及相关源码分析
- POLL机制分析
- Android -- 消息处理机制源码分析(Looper,Handler,Message)
- 从一道面试题分析Linux进程+IO缓冲区机制
- 详细分析Android中onTouch事件传递机制
- Zend的MVC机制使用分析(二)
- IOS Table中Cell的重用reuse机制分析
- Android 消息传递机制分析
- Android内存机制分析上篇:了解Android堆和栈
- iOS中引用计数内存管理机制分析
- android的消息处理机制(图+源码分析)——Looper,Handler,Message
- android 休眠唤醒机制分析(一) — wake_lock
- [置顶] [Android源代码分析]Android消息机制,Handler,Message,Looper,MessageQueue