您的位置:首页 > 其它

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++结构化异常处理机制。

测试代码与分析

下面是用于测试的代码:

#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-80x00422138
ebp-40xFF
ebp上帧ebp的值
从上图可以看出,ebp-0x10处存的正好是SEH链上下一节点的地址。

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函数的神秘面纱以及它背后的数据结构支持。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息