Windows異常世界歷險記(三)——VC6中結構化異常處理機制的反彙編分析(上)

在《Visual C++異常處理機制原理與應用》部分,我們講解了Visual C++提供的結構化異常處理機制,並對其中比較簡單的終止型異常處理程序在部分條件下(自然執行、提前越出、__leave關鍵字等)進行了反彙編分析。下面我們繼續對整個Visual C++結構化異常處理機制進行分析。

本次分析的目標環境是VC++ 6.0,這是一款已經有些“美人遲暮”的集成開發環境。使用它進行分析,主要原因如下:

  1. 仍有不少程序使用VC++ 6.0開發和編譯(特別是惡意代碼、灰產代碼等)

  2. 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塊,其中:

  1. 內層的try塊爲try-finally終止異常處理
  2. 外層try塊爲try-except型異常處理,能處理任何異常程序

而在WinMain中被try塊保護範圍內,調用了一個名爲RaiseExcept的函數,該函數被兩層try塊保護,其中執行了一段導致違規訪問的代碼:

  1. 內層try塊爲try-finally終止型異常處理

  2. 外層try塊爲try-except異常處理,但不處理違規訪問類型的異常

按照之前的分析,執行的順序是:

  1. RaiseExcept中外層try-except中的過濾函數,而該異常處理塊不處理此類型異常;

  2. 轉而執行WinMain中外層try-except的過濾函數,並確定該異常處理塊可以執行此類異常;

  3. 執行RaiseExcept內層finally塊的代碼

  4. 執行WinMain內層finally塊的代碼

  5. 執行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的值

從上圖可以看出,ebp-0x10處存的正好是SEH鏈上下一節點的地址。

tryLevel

在這段代碼中,我們注意到ebp-4這個地址,在文件符號中,該地址對應的符號名爲tryLevel。

  1. 該值在函數入口處爲0xFF:

    00401223 6A FF                push        0FFh
  2. 在進入最外層try塊時被置爲0:

    45:       __try
    00401255 C7 45 FC 00 00 00 00 mov         dword ptr [ebp-4],0    ; tryLevel0,表明進入外層try
  3. 而在進入第二層try塊時被置爲1:

    47:           __try
    0040125C C7 45 FC 01 00 00 00 mov         dword ptr [ebp-4],1    ; tryLevel1,表明進入第二層try
  4. 在出第二層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
  5. 在出最外層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函數的神祕面紗以及它背後的數據結構支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章