SEH分析筆記(X64篇)

SEH分析筆記(X64篇)
v1.0.0
boxcounter

歷史:
v1.0.0, 2011-11-4:最第一版本。

[不介意轉載,但請註明出處 www.boxcounter.com 
附件裏有本文的原始稿,同樣的內容,更好的高亮和排版。
本文的部分代碼可能會由於論壇的自動換行變得很亂,須要的朋友手動複製到本身的代碼編輯器就能夠正常顯示了]

在以前的《SEH分析筆記(X86篇)》中,我藉助 wrk1.2 介紹了 x86 下 windows 系統內核中的 SEH 實現。此次咱們來看看 x64 位 windows 系統內核中 SEH 的實現。
本文須要你們熟悉 x64 位系統的一些特性,好比調用約定、Prolog 和 Epilog。能夠經過這幾篇文章熟悉一下:
Overview of x64 Calling Conventions, MSDN
The history of calling conventions, part 5: amd64 , The Old New Thing
Everything You Need To Know To Start Programming 64-Bit Windows Systems, Matt Pietrek

首先回顧一下前一篇文章。
在 x86 windows 中,函數經過如下幾個步驟來參與 SEH :
1. 在自身的棧空間中分配並初始化一個 EXCEPTION_REGISTRATION(_RECORD) 結構體。
2. 將該 EXCEPTION_REGISTRATION(_RECORD) 掛入當前線程的異常鏈表。

當某函數觸發異常時,系統首先會經過調用 KiDispatchException 來給內核調試器一個機會,若是內核調試器沒有處理該異常,則該機會被轉給 RtlDispatchException,這個函數就開始分發該異常。分發過程爲:
從當前線程的異常鏈表頭開始遍歷,對於每個 SEH 註冊信息(即 EXCEPTION_REGISTRATION(_RECORD)),調用其 Handler。根據 Handler 的返回值作相應的後續處理:
1. 返回 ExceptionContinueExecution,表示 Handler 已經修復了異常觸發點,從異常觸發點繼續執行。
2. 返回 ExceptionContinueSearch,表示該 Handler 沒有處理該異常,繼續遍歷異常鏈表。
3. Handler 沒有修復異常觸發點,可是卻能處理該異常(某個 __except 過濾代碼返回 EXCEPTION_EXECUTE_HANDLER)。這種狀況下,處理完該異常後就從異常解決代碼(__except 代碼塊)繼續執行,Handler 不會返回。
以上是簡略的 x86 SEH 流程,其中省略了不少細節,好比展開、錯誤處理、ExceptionNestedException 和 ExceptionCollidedUnwind 等等。


之因此在這裏重溫這個流程,是由於 x64 中 SEH 的流程整體思路也是如此,只是細節上作了一些修改。但這並不表示熟悉 x86 SEH 就能很輕鬆的掌握 x64 SEH。

本文分爲四個部分:「異常註冊」、「異常分發」、「展開、解決」和「ExceptionNestedException 和 ExceptionCollidedUnwind」。依然以 MSC 的加強版爲分析對象。分析環境爲:WDK 7600.16385.1,內置的 cl 的版本是15.00.30729.207,link 的版本是9.00.30729.207,測試虛擬機系統爲 amd64 WinXP + wrk1.2。

在講述以前,須要先定義幾個名詞,以簡化後續的講述。

RVA —— 熟悉 PE 格式的朋友都懂的,表示某個絕對地址相對於所在模塊的基地址的偏移。
EXCEPT_POINT —— 異常觸發點。
EXCEPT_FILTER —— __except 小括號內的異常過濾代碼。
EXCEPT_HANDLER —— __except 大括號內的異常解決代碼。
FINALLY_HANDLER —— __finally 大括號內的代碼。

如下面的僞碼爲例,

 
Code:
    1  __try
    2  {
    3      __try
    4      {
    5           *((ULONG*)NULL) = 0; 
    6      }
    7      __except((STATUS_INVALID_PARAMETER == GetExceptionCode()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER)
    8      {
    9          ...
    10     }
    11 }
    12 __finally
    13 {
    14     ...
    15 {
EXCEPT_POINT 指的是行5中的代碼。
EXCEPT_FILTER 指的是行7中的「(STATUS_INVALID_PARAMETER == GetExceptionCode()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER」。
EXCEPT_HANDLER 指的是行8到行10中全部的代碼。
FINALLY_HANDLER 指的是行13到行15中全部的代碼。


1、異常註冊

在 x64 windows 中,異常註冊信息發生了巨大的改變。x86 中異常註冊信息是在函數執行過程當中在棧中分配並初始化的。x64 中變成這樣:
異常註冊信息再也不是動態建立,而是編譯過程當中生成,連接時寫入 PE+ 頭中的 ExceptionDirectory(參考 winnt.h 中 IMAGE_RUNTIME_FUNCTION_ENTRY 的定義)。ExceptionDirectory 裏包含幾乎全部函數的棧操做、異常處理等信息。

來看看新異常註冊信息的數據結構:

 
Code:
    typedef struct _RUNTIME_FUNCTION {
        ULONG BeginAddress;
        ULONG EndAddress;
        ULONG UnwindData;
    } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

    typedef enum _UNWIND_OP_CODES {
        UWOP_PUSH_NONVOL = 0,
        UWOP_ALLOC_LARGE,       // 1
        UWOP_ALLOC_SMALL,       // 2
        UWOP_SET_FPREG,         // 3
        UWOP_SAVE_NONVOL,       // 4
        UWOP_SAVE_NONVOL_FAR,   // 5
        UWOP_SPARE_CODE1,       // 6
        UWOP_SPARE_CODE2,       // 7
        UWOP_SAVE_XMM128,       // 8
        UWOP_SAVE_XMM128_FAR,   // 9
        UWOP_PUSH_MACHFRAME     // 10
    } UNWIND_OP_CODES, *PUNWIND_OP_CODES;

    typedef union _UNWIND_CODE {
        struct {
            UCHAR CodeOffset;
            UCHAR UnwindOp : 4;
            UCHAR OpInfo : 4;
        };
    
        USHORT FrameOffset;
    } UNWIND_CODE, *PUNWIND_CODE;
    
    #define UNW_FLAG_NHANDLER 0x0
    #define UNW_FLAG_EHANDLER 0x1
    #define UNW_FLAG_UHANDLER 0x2
    #define UNW_FLAG_CHAININFO 0x4

    typedef struct _UNWIND_INFO {
        UCHAR Version : 3;
        UCHAR Flags : 5;
        UCHAR SizeOfProlog;
        UCHAR CountOfCodes;
        UCHAR FrameRegister : 4;
        UCHAR FrameOffset : 4;
        UNWIND_CODE UnwindCode[1];
    
    //
    // The unwind codes are followed by an optional DWORD aligned field that
    // contains the exception handler address or a function table entry if
    // chained unwind information is specified. If an exception handler address
    // is specified, then it is followed by the language specified exception
    // handler data.
    //
    //  union {
    //      struct {
    //          ULONG ExceptionHandler;
    //          ULONG ExceptionData[];
    //      };
    //
    //      RUNTIME_FUNCTION FunctionEntry;
    //  };
    //
    
    } UNWIND_INFO, *PUNWIND_INFO;

    typedef struct _SCOPE_TABLE {
        ULONG Count;
        struct
        {
            ULONG BeginAddress;
            ULONG EndAddress;
            ULONG HandlerAddress;
            ULONG JumpTarget;
        } ScopeRecord[1];
    } SCOPE_TABLE, *PSCOPE_TABLE;
x64 中,MSC 爲幾乎全部的函數都登記了完備的信息,用來在展開過程當中完整的回滾函數所作的棧、寄存器操做。登記的信息包括:
函數是否使用了 SEH、
函數使用的是什麼組合的 SEH(__try/__except?__try/__finally?)、
函數申請了多少棧空間、
函數保存了哪些寄存器、
函數是否創建了棧幀,
等等,
同時也記錄了這些操做的順序(以保證回滾的時候不會亂套)。

這些信息就存儲在 UNWIND_INFO 之中。
UNWIND_INFO 至關於 x86 下的 EXCEPTION_REGISTRATION。它的成員分別是:
Version —— 結構體的版本。
Flags —— 標誌位,能夠有這麼幾種取值:
UNW_FLAG_NHANDLER (0x0): 表示既沒有 EXCEPT_FILTER 也沒有 EXCEPT_HANDLER。
UNW_FLAG_EHANDLER (0x1): 表示該函數有 EXCEPT_FILTER & EXCEPT_HANDLER。
UNW_FLAG_UHANDLER (0x2): 表示該函數有 FINALLY_HANDLER。
UNW_FLAG_CHAININFO (0x4): 表示該函數有多個 UNWIND_INFO,它們串接在一塊兒(所謂的 chain)。
SizeOfProlog —— 表示該函數的 Prolog 指令的大小,單位是 byte。
CountOfCodes —— 表示當前 UNWIND_INFO 包含多少個 UNWIND_CODE 結構。
FrameRegister —— 若是函數創建了棧幀,它表示棧幀的索引(相對於 CONTEXT::RAX 的偏移,詳情參考 RtlVirtualUnwind 源碼)。不然該成員的值爲0。
FrameOffset —— 表示 FrameRegister 距離函數最初棧頂(剛進入函數,尚未執行任何指令時的棧頂)的偏移,單位也是 byte。
UnwindCode —— 是一個 UNWIND_CODE 類型的數組。元素數量由 CountOfCodes 決定。
須要說明幾點:
1. 若是 Flags 設置了 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER,那麼在最後一個 UNWIND_CODE 以後存放着 ExceptionHandler(至關於 x86 EXCEPTION_REGISTRATION::handler)和 ExceptionData(至關於 x86 EXCEPTION_REGISTRATION::scopetable)。
2. UnwindCode 數組詳細記錄了函數修改棧、保存非易失性寄存器的指令。
3. MSDN 中有 UNWIND_INFO 和 UNWIND_CODE 的詳細說明,推薦閱讀。

那 UNWIND_INFO 是如何與其描述的函數關聯起來的呢?答案是:經過一個 RUNTIME_FUNCTION 結構體。
RUNTIME_FUNCTION::BeginAddress 同 RUNTIME_FUNCTION::EndAddress 一塊兒以 RVA 形式描述了函數的範圍。
RUNTIME_FUNCTION::UnwindData 就是 UNWIND_INFO 了,它也是一個 RVA 值。

PE+ 中的 ExceptionDirectory 中存放着全部函數的 RUNTIME_FUNCTION,按 RUNTIME_FUNCTION::BeginAddress 升序排列。一旦觸發異常,系統能夠經過 EXCEPT_POINT 的 RVA 在 ExceptionDirectory 中二分查找到 RUNTIME_FUNCTION,進而找到 UNWIND_INFO。

前面有提到,MSC 爲幾乎全部的函數都登記了完畢的信息,那是否是有一些特殊函數沒有登記信息呢?
是的。x64 新增了一個概念,叫作「葉函數」。熟悉數據結構的朋友可能第一時間就聯想到「葉節點」。沒錯,「葉函數」的含義跟「葉節點」很相似,葉函數不會有子函數,也就是說它不會再​調用任何函數。另外 x64 對這個概念額外加了一些要求:不修改棧指針(好比分配棧空間)、沒有使用 SEH。總結下來就是:既不調用函數、又沒有修改棧指針,也沒有使用 SEH 的函數就叫作「葉函數」。
葉函數能夠沒有登記信息,緣由很簡單,它根本就沒信息須要登記~

還有一個 SCOPE_TABLE 結構,熟悉 x86 SEH 的朋友應該很眼熟 :-),它等同於 x86 SEH 中的 REGISTRATIOIN_RECORD::scopetable 的類型。其成員有:
Count —— 表示 ScopeRecord 數組的大小。
ScopeRecord —— 等同於 x86 中的 scopetable_entry 成員。其中,
BeginAddress 和 EndAddress 表示某個 __try 保護域的範圍。
HandlerAddress 和 JumpTarget 表示 EXCEPTION_FILTER、EXCEPT_HANDLER 和 FINALLY_HANDLER。具體對應狀況爲:
對於 __try/__except 組合,HandlerAddress 表明 EXCEPT_FILTER,JumpTarget 表明 EXCEPT_HANDLER。
對於 __try/__finally 組合,HandlerAddress 表明 FINALLY_HANDLER,JumpTarget 等於 0。
這四個域一般都是 RVA,但當 EXCEPT_FILTER 簡單地返回或等於 EXCEPTION_EXECUTE_HANDLER 時,HandlerAddress 可能直接等於 EXCEPTION_EXECUTE_HANDLER,而再也不是一個 RVA。

咱們能夠經過 windbg 中的 .fnent 命令來查看某個函數的異常註冊信息。好比,

1 kd> .fnent passThrough!SehTest
2 Debugger function entry 00000000`00778210 for:
3 d:\workspace\code\mycode\r0\passthrough\passthrough.c(51)
4 (fffffadf`f140f020) PassThrough!SehTest | (fffffadf`f140f0c0) PassThrough!Caller2
5 Exact matches:
6 PassThrough!SehTest (void)

8 BeginAddress = 00000000`00001020
9 EndAddress = 00000000`000010b2
10 UnwindInfoAddress = 00000000`00002668
11 
12 Unwind info at fffffadf`f1410668, 10 bytes
13 version 1, flags 1, prolog 4, codes 1
14 handler routine: PassThrough!_C_specific_handler (fffffadf`f140f4ce), data 3
15 00: offs 4, unwind op 2, op info 4 UWOP_ALLOC_SMALL.

行8到行10描述的是 RUNTIME_FUNCTION。
行12到行15描述的是 UNWIND_INFO。

對於葉函數,輸出是這樣的,

kd> .fnent passthrough!LeafTest
No function entry for fffffadf`f240c080

到這裏,異常註冊就講完了,咱們認識了相關的數據結構和定位方法。下面咱們進入異常分發流程。


2、異常分發

x64 異常分發過程使用的仍然是 KiDispatchException、RtlDispatchException、RtlpExecuteHandlerForException 等函數。

其中,KiDispatchException 中有關內核異常部分的代碼徹底沒有變化,這裏我偷懶直接拷貝《SEH分析筆記(X86篇)》中的部分描述,

原型:
Code:
    VOID
    KiDispatchException (
        IN PEXCEPTION_RECORD ExceptionRecord,
        IN PKEXCEPTION_FRAME ExceptionFrame,
        IN PKTRAP_FRAME TrapFrame,
        IN KPROCESSOR_MODE PreviousMode,
        IN BOOLEAN FirstChance
        );
對於內核異常,它的處理步驟以下:
若是 FirstChance 爲 TRUE,那麼,
1. 首先將該異常傳達給內核調試器(KD),若是 KD 處理了該異常,那麼函數返回。
2. KD 沒有處理,調用 RtlDispatchException 進行異常分發。若是分發成功,RtlDispatchExcetpion 返回 TRUE,或者根本不返回。
3. RtlDispatchException 分發失敗,那麼再給 KD 一次處理機會,若是仍是沒有處理,那麼 BUGCHECK。
若是 FirstChance 爲 FALSE,那麼將該異常傳達給 KD,若是 KD 沒有處理,那麼 BUGCHECK。
它的源碼實現位於 $WRK-v1.2\base\ntos\ke\amd64\exceptn.c:430。

RtlDispatchException 發生了一些改變。
從以前描述的異常註冊信息的數據結構能夠發現,x64 中已經不存在異常註冊鏈這個概念了(雖然 _NT_TIB 結構體中還保留了 ExceptionList 域)。那 x64 中 RtlDispatchException 是如何遍歷異常註冊信息的呢?前面雖然有提到如何經過 EXCEPT_POINT 找到 UNWIND_INFO 結構,可是假如這個 UNWIND_INFO 沒有處理該異常,如何繼續遍歷呢?

在解答這個問題以前,咱們先來認識一個新函數和相關的數據結構,

 
Code:
    #define UNWIND_HISTORY_TABLE_SIZE 12
    
    typedef struct _UNWIND_HISTORY_TABLE_ENTRY {
            ULONG64 ImageBase;
            PRUNTIME_FUNCTION FunctionEntry;
    } UNWIND_HISTORY_TABLE_ENTRY, *PUNWIND_HISTORY_TABLE_ENTRY;
    
    #define UNWIND_HISTORY_TABLE_NONE 0
    #define UNWIND_HISTORY_TABLE_GLOBAL 1
    #define UNWIND_HISTORY_TABLE_LOCAL 2
    
    typedef struct _UNWIND_HISTORY_TABLE {
            ULONG Count;
            UCHAR Search;
            ULONG64 LowAddress;
            ULONG64 HighAddress;
            UNWIND_HISTORY_TABLE_ENTRY Entry[UNWIND_HISTORY_TABLE_SIZE];
    } UNWIND_HISTORY_TABLE, *PUNWIND_HISTORY_TABLE;

    PRUNTIME_FUNCTION
    RtlLookupFunctionEntry (
        IN ULONG64 ControlPc,
        OUT PULONG64 ImageBase,
        IN OUT PUNWIND_HISTORY_TABLE HistoryTable OPTIONAL
        );
RtlLookupFunctionEntry 的功能是查找指定地址所在函數的 RUNTIME_FUNCTION 和所在模塊的基地址。它的參數分爲爲,
ControlPc —— 須要查找的指令地址,
ImageBase —— 返回的模塊基地址,
HistoryTable —— 用於加速查找。

工做流程:
RtlLookupFunctionEntry 在搜索的過程會根據是否傳入 HistoryTable 而採起不一樣的搜索方法:
若是傳入 HistoryTable,則根據 HistoryTable->Search 表示的搜索方式在表中進行搜索:
若是搜索方式爲 UNWIND_HISTORY_TABLE_NONE,那麼不在 HistoryTable 中進行搜索。
若是搜索方式爲 UNWIND_HISTORY_TABLE_GLOBAL,則首先在全局表 RtlpUnwindHistoryTable 開始搜索,若是搜索到,則結束搜索,函數返回。不然再在 HistoryTable 中搜索。
若是搜索方式爲 UNWIND_HISTORY_TABLE_LOCAL,那麼在 HistoryTable 中搜索。
若是上述過程當中沒有搜索到須要的結果,那麼找到模塊基地址,從模塊的 PE+ 頭結構中解析出 RUNTIME_FUNCTION。若是搜索方式爲 UNWIND_HISTORY_TABLE_NONE,還會將解決加入到 HistoryTable。

以前的描述中 RtlDispatchException 定位 EXCEPT_POINT 所對應的 RUNTIME_FUNCTION 就是經過調用 RtlLookupFunctionEntry 實現的。

回到剛纔的問題,如何推進遍歷呢?爲了解決這個問題,x64 又引進了一個新函數。如今咱們有請 x64 SEH 核心成員 RtlVirtualUnwind 登場~

先來看看它的原型:
Code:
    PEXCEPTION_ROUTINE
    RtlVirtualUnwind (
        IN ULONG HandlerType,
        IN ULONG64 ImageBase,
        IN ULONG64 ControlPc,
        IN PRUNTIME_FUNCTION FunctionEntry,
        IN OUT PCONTEXT ContextRecord,
        OUT PVOID *HandlerData,
        OUT PULONG64 EstablisherFrame,
        IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL
        );
它的主要功能是:
根據傳入的 ControlPc 和 ContextRecord 等參數虛擬(模擬)展開該函數,並返回該函數的一些信息,好比 HandlerData(SCOPE_TABLE)、EstablisherFrame(rsp 或 棧幀)。
流程是:
1. 經過 FunctionEntry 和 ImageBase 找到 UNWIND_INFO。根據 UNWIND_INFO 中記錄的信息,查找 EstablisherFrame(即棧幀或者 rsp)。
2. 根據 ControlPc 分以下兩種狀況展開:
a. ControlPc >= EpilogOffset,即 ControlPc 在 Epilog 之中。那麼把剩餘的 Epilog 指令模擬執行完畢便可。
所謂模擬是指,若是下一條 EpiLog 指令是「sub rsp, 0x32」,那麼將 ContextRecord->rsp 減去0x32。並非真正執行 sub 指令。
b. ControlPc < EpilogOffset,那麼把 Prolog 反向模擬回滾一遍便可。
所謂反向回滾是指,若是 Prolog 的指令是
mov [RSP + 8], RCX
push R15
push R14
push R13 
那麼反向回滾就是
pop ContextRecord->R13 (其實是從 ContextRecord->Rsp 指向的內存中取出值存入 ContextRecord->R13,而後 ContextRecord->Rsp 加上8。並非真正執行 pop)
pop ContextRecord->R14
pop ContextRecord->R15
mov ContextRecord->RCX, [ContextRecord->RSP+8]
而後把 ContextRecord->Rip 修改成 ControlPc 所在函數的返回地址,即父函數中的某一處 call 的下一條指令。
這樣,ContextRecord 就被恢復成父函數在調用 ControlPc 所在函數以後的狀態了。
3. 若是 HandlerType 包含 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER,那麼將 UNWIND_INFO::ExceptionData 賦給傳出參數 HandlerData,並返回 UNWIND_INFO::ExceptionRoutine。
對於 MSC 編譯器生成的模塊,UNWIND_INFO::ExceptionRoutine 通常指向 nt!__C_specific_handler。UNWIND_INFO::ExceptionData 指向 ControlPc 所在函數的 SCOPE_TABLE。
RtlVirtualUnwind 的實現源碼位於 $WRK-v1.2\base\ntos\rtl\amd64\exdsptch.c:1202。

RtlVirtualUnwind 返回後,RtlDispatchException 就能夠根據 ContextRecord->Rip 找到父函數對應的 RUNTIME_FUNCTION,進而找到 UNWIND_INFO。就這樣推進整個遍歷過程。

這是通常狀況,對於沒有 UNWIND_INFO 的葉函數呢?
對於葉函數,RtlLookupFunctionEntry 返回 NULL,因而 RtlDispatchException 知道這是個葉函數,就找到該葉函數的父函數,從父函數繼續遍歷。也就是徹底無視葉函數,由於葉函數對整個異常處理過程沒有任何影響。

RtlDispatchException 調用 UNWIND_INFO::ExceptionHandler 依然是經過 RtlpExecuteHandlerForException,其函數原型沒有變化:

 
Code:
    EXCEPTION_DISPOSITION
    RtlpExecuteHandlerForException (
        IN PEXCEPTION_RECORD ExceptionRecord,
        IN PVOID EstablisherFrame,
        IN OUT PCONTEXT ContextRecord,
        IN OUT PVOID DispatcherContext
        );
該函數的實現源碼位於 $\WRK-v1.2\base\ntos\rtl\amd64\xcptmisc.asm:84。
RtlpExecuteHandlerForException 的邏輯較 x86 版本沒什麼大變化,內部註冊了一個異常處理函數 RtlpExceptionHandler。RtlpExceptionHandler 至關於 x86 中的 nt!ExecuteHandler2,其內部會返回 ExceptionNestedException 或 ExceptionContinueSearch。它的實現源碼位於 $\WRK-v1.2\base\ntos\rtl\amd64\xcptmisc.asm:26。

須要一提的是,最後一個參數 DispatchContext 的類型是 DISPATCHER_CONTEXT,相對於 x86 版本,它擴充了不少,

 
Code:
    typedef struct _DISPATCHER_CONTEXT {
        ULONG64 ControlPc;
        ULONG64 ImageBase;
        PRUNTIME_FUNCTION FunctionEntry;
        ULONG64 EstablisherFrame;
        ULONG64 TargetIp;
        PCONTEXT ContextRecord;
        PEXCEPTION_ROUTINE LanguageHandler;
        PVOID HandlerData;
        PUNWIND_HISTORY_TABLE HistoryTable;
        ULONG ScopeIndex;
        ULONG Fill0;
    } DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;
成員分別爲:
ControlPc —— 異常觸發點。
ImagePase —— ControlPc 所在模塊的基地址。
FunctionEntry —— ControlPc 所在函數的 RUNTIME_FUNCTION。
EstablisherFrame —— ControlPc 所在函數的棧幀(若是創建了棧幀)或 RSP。
TargetIp —— 解決異常的 EXCEPT_HANDLER 地址,該成員只在展開的過程當中被使用。RtlpExecuteHandlerForException 沒有使用它。
ContextRecord —— 供展開過程當中使用,只有當展開過程當中觸發新異常(返回 ExceptionCollidedUnwind)時,纔會被 RtlDispatchException 真正的使用到(參考 RtlDispatchException 處理 ExceptionCollidedUnwind 的代碼)。
LanguageHandler —— ControlPc 所在函數的 UNWIND_INFO::ExceptionRoutine。
HandlerData —— ControlPc 所在函數的 UNWIND_INFO::ExceptionData。
ScopeIndex —— UNWIND_INFO::ExceptionData 中 SCOPE_TABLE::ScopeRecord 的索引,一般設置爲0(注:請不要與 x86 中運行時不斷改變的 EXCEPTION_REGISTRATION::trylevel 相混淆,ScopeIndex 不會在在函數執行過程當中改變)
Fill0 —— 未用。

再看一下它的 .fnent 輸出,

1 kd> .fnent nt!RtlpExecuteHandlerForException
2 Debugger function entry 00000000`01458210 for:
3 (fffff800`008bd950) nt!RtlpExecuteHandlerForException | (fffff800`008bd970) nt!RtlpUnwindHandler
4 Exact matches:
5 nt!RtlpExecuteHandlerForException (void)

7 BeginAddress = 00000000`000bd950
8 EndAddress = 00000000`000bd963
9 UnwindInfoAddress = 00000000`000dfeb8
10 
11 Unwind info at fffff800`008dfeb8, 10 bytes
12 version 1, flags 3, prolog 4, codes 1
13 handler routine: nt!RtlpExceptionHandler (fffff800`008bd920), data 0
14 00: offs 4, unwind op 2, op info 4 UWOP_ALLOC_SMALL.

行12中顯示 flags 等於3,即 UNW_FLAG_EHANDLER (0x1) | UNW_FLAG_UHANDLER (0x2),說明行13中顯示的異常處理函數 nt!RtlpExceptionHandler 既負責解決異常,也負責展開。

RtlpExecuteHandlerForException 會調用 DISPATCHER_CONTEXT::LanguageHandler。對於 MSC 編譯獲得的模塊,它是 nt!__C_specific_handler,咱們來看看這個函數,

原型:
Code:
    EXCEPTION_DISPOSITION 
    __C_specific_handler (
        IN PEXCEPTION_RECORD pExceptionRecord,
        IN PVOID pEstablisherFrame,
        IN OUT PCONTEXT pContext,
        IN OUT PVOID pDispatcherContext
        );
反彙編碼:
Code:
kd> uf nt!__C_specific_handler
               nt!__C_specific_handler:
               fffff800`008a42d0 mov     qword ptr [rsp+10h],rdx ; 在棧上保存 pEstablisherFrame
               fffff800`008a42d5 mov     rax,rsp
               fffff800`008a42d8 sub     rsp,88h
               fffff800`008a42df mov     qword ptr [rax-8],rbx
               fffff800`008a42e3 mov     qword ptr [rax-10h],rbp
               fffff800`008a42e7 mov     rbp,qword ptr [r9]      ; rbp = pDispatcherContext->ControlPc
               fffff800`008a42ea mov     qword ptr [rax-18h],rsi
               fffff800`008a42ee mov     qword ptr [rax-20h],rdi
               fffff800`008a42f2 mov     qword ptr [rax-28h],r12
               fffff800`008a42f6 mov     r12,qword ptr [r9+38h]  ; r12 = pDispatcherContext->HandlerData
               fffff800`008a42fa mov     qword ptr [rax-30h],r13
               fffff800`008a42fe mov     qword ptr [rax-38h],r14
               fffff800`008a4302 mov     r14,qword ptr [r9+8]    ; r14 = pDispatcherContext->ImageBase
               fffff800`008a4306 mov     qword ptr [rax-40h],r15
               fffff800`008a430a mov     r13,r9                  ; r13 = pDispatcherContext
               fffff800`008a430d sub     rbp,r14                 ; l_OffsetInFunc = pDispatcherContext->ControlPc - pDispatcherContext->ImageBase
               fffff800`008a4310 test    byte ptr [rcx+4],66h    ; pExceptionRecord->ExceptionFlags, EXCEPTION_UNWIND (0x66)
               fffff800`008a4314 mov     rsi,rdx                 ; rsi = pEstablisherFrame
               fffff800`008a4317 mov     r15,rcx                 ; r15 = pExceptionRecord
<              fffff800`008a431a jne     nt!__C_specific_handler+0xf5 (fffff800`008a43c5)
:              
:              -------------------------------------------------------------------
:              nt!__C_specific_handler+0x50:
:              fffff800`008a4320 movsxd  rdi,dword ptr [r9+48h]  ; l_ScopeIndex (rdi) = pDispatcherContext->ScopeIndex
:              fffff800`008a4324 mov     qword ptr [rax-58h],rcx ; [rax-58h] = pExceptionRecord,供給 GetExceptionCode(Information) 使用
:              fffff800`008a4328 mov     qword ptr [rax-50h],r8  ; [rax-50h] = pContext,供給 GetExceptionCode(Information) 使用
:              fffff800`008a432c cmp     edi,dword ptr [r12]     ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
:              fffff800`008a4330 mov     rax,rdi                 ; rax = l_ScopeIndex
:<             fffff800`008a4333 jae     nt!__C_specific_handler+0x166 (fffff800`008a4436)
::             
::             nt!__C_specific_handler+0x69:
::             fffff800`008a4339 add     rax,rax             ; 這裏 *2,下面緊接着 *8,目的是跳過指定數目的 ScopeRecord(大小爲16字節)
::             fffff800`008a433c lea     rbx,[r12+rax*8+0Ch] ; rbx = &(pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress)
::             
::             nt!__C_specific_handler+0x71:
::             ; 檢查 ControlPc 處於哪一個 __try 保護域,之步驟一
::      >      fffff800`008a4341 mov     eax,dword ptr [rbx-8] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
::      :      fffff800`008a4344 cmp     rbp,rax               ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
::<     :      fffff800`008a4347 jb      nt!__C_specific_handler+0xdd (fffff800`008a43ad)
:::     :      
:::     :      nt!__C_specific_handler+0x79:
:::     :      ; 檢查 ControlPc 處於哪一個 __try 保護域,之步驟二
:::     :      fffff800`008a4349 mov     eax,dword ptr [rbx-4] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
:::     :      fffff800`008a434c cmp     rbp,rax               ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
:::<    :      fffff800`008a434f jae     nt!__C_specific_handler+0xdd (fffff800`008a43ad)
::::    :      
::::    :      nt!__C_specific_handler+0x81:
::::    :      ; 判斷是不是 __try/__finally(JumpTarget 爲 NULL)。若是是,那麼跳轉到下一個 ScopeRecord 繼續遍歷。
::::    :      fffff800`008a4351 cmp     dword ptr [rbx+4],0 ; cmp pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget, NULL
::::<   :      fffff800`008a4355 je      nt!__C_specific_handler+0xdd (fffff800`008a43ad)
:::::   :      
:::::   :      nt!__C_specific_handler+0x87:
:::::   :      ; 到這裏,已經找到與異常地址最匹配的 __try/__except
:::::   :      fffff800`008a4357 mov     eax,dword ptr [rbx] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress
:::::   :      fffff800`008a4359 cmp     eax,1               ; cmp pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress, EXCEPTION_EXECUTE_HANDLER (0x1)
:::::<  :      fffff800`008a435c je      nt!__C_specific_handler+0xa3 (fffff800`008a4373) ; 若是返回 EXCEPTION_EXECUTE_HANDLER 則跳轉
::::::  :      
::::::  :      nt!__C_specific_handler+0x8e:
::::::  :      ; 是 __try/__except,且過濾域並非 EXCEPTION_EXECUTE_HANDLER,執行 HandlerAddress 
::::::  :      ; (注:HandlerAddress 指向的函數仍有可能會返回 EXCEPTION_EXECUTE_HANDLER) 
::::::  :      fffff800`008a435e lea     rcx,[rsp+30h]
::::::  :      fffff800`008a4363 add     rax,r14 ; rax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress + pDispatcherContext->ImageBase
::::::  :      fffff800`008a4366 mov     rdx,rsi ; rdx = pEstablisherFrame
::::::  :      fffff800`008a4369 call    rax     ; 調用 EXCEPT_FILTER
::::::  :      fffff800`008a436b test    eax,eax
::::::< :      fffff800`008a436d js      nt!__C_specific_handler+0xee (fffff800`008a43be) ; 返回 EXCEPTION_CONTINUE_EXECUTION (-1) 則跳轉
::::::: :      
::::::: :      nt!__C_specific_handler+0x9f:
::::::: :      fffff800`008a436f test    eax,eax
:::::::<:      fffff800`008a4371 jle     nt!__C_specific_handler+0xdd (fffff800`008a43ad) ; 返回 EXCEPTION_CONTINUE_SEARCH (0) 則跳轉
:::::::::      
:::::::::      nt!__C_specific_handler+0xa3:
:::::::::      ; 返回的是 EXCEPTION_EXECUTE_HANDLER
:::::>:::      fffff800`008a4373 mov     ecx,dword ptr [rbx+4] ; ecx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
::::: :::      fffff800`008a4376 mov     r8d,1
::::: :::      fffff800`008a437c mov     rdx,rsi ; rdx = pEstablisherFrame
::::: :::      fffff800`008a437f add     rcx,r14 ; rcx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget + pDispatcherContext->ImageBase
::::: :::      fffff800`008a4382 call    nt!_NLG_Notify (fffff800`008b1460)
::::: :::      fffff800`008a4387 mov     rax,qword ptr [r13+40h] ; rax = pDispatcherContext->HistoryTable
::::: :::      fffff800`008a438b mov     edx,dword ptr [rbx+4]   ; edx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
::::: :::      fffff800`008a438e movsxd  r9,dword ptr [r15]      ; r9 = pExceptionRecord->ExceptionCode
::::: :::      fffff800`008a4391 mov     qword ptr [rsp+28h],rax ; _ARG_6 = pDispatcherContext->HistoryTable
::::: :::      fffff800`008a4396 mov     rax,qword ptr [r13+28h] ; rax = pDispatcherContext->ContextRecord
::::: :::      fffff800`008a439a add     rdx,r14 ; rdx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget + pDispatcherContext->ImageBase
::::: :::      fffff800`008a439d mov     r8,r15  ; r8 = pExceptionRecord
::::: :::      fffff800`008a43a0 mov     rcx,rsi ; rcx = pEstablisherFrame
::::: :::      fffff800`008a43a3 mov     qword ptr [rsp+20h],rax ; _ARG_5 = pDispatcherContext->ContextRecord
::::: :::      fffff800`008a43a8 call    nt!RtlUnwindEx (fffff800`00891e80) ; 這裏不會返回
::::: :::      ; RtlUnwindEx(pEstablisherFrame, 
::::: :::      ;             pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget + pDispatcherContext->ImageBase
::::: :::      ;             pExceptionRecord,
::::: :::      ;             pExceptionRecord->ExceptionCode
::::: :::      ;             pDispatcherContext->ContextRecord,
::::: :::      ;             pDispatcherContext->HistoryTable)
::::: :::      
::::: :::      nt!__C_specific_handler+0xdd:
::>>> :>:      fffff800`008a43ad inc     edi     ; l_ScopeIndex += 1
::    : :      fffff800`008a43af add     rbx,10h ; 調整到下一個 ScopeRecord::HandlerAddress
::    : :      fffff800`008a43b3 cmp     edi,dword ptr [r12] ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
::    : <      fffff800`008a43b7 jb      nt!__C_specific_handler+0x71 (fffff800`008a4341)
::    :        
::    :        nt!__C_specific_handler+0xe9:
::    :        ; pDispatcherContext->HandlerData 遍歷完畢
::<   :        fffff800`008a43b9 jmp     nt!__C_specific_handler+0x166 (fffff800`008a4436)
:::   :        
:::   :        nt!__C_specific_handler+0xee:
:::   >        fffff800`008a43be xor     eax,eax ; eax = ExceptionContinueExecution
:::<           fffff800`008a43c0 jmp     nt!__C_specific_handler+0x16b (fffff800`008a443b)
::::           
::::           -------------------------------------------------------------------------------------
::::           nt!__C_specific_handler+0xf5:
::::           ; 設置了 EXCEPTION_UNWIND,當前是展開過程
>:::           fffff800`008a43c5 movsxd  rdi,dword ptr [r9+48h] ; l_ScopeIndex (rdi) = pDispatcherContext->ScopeIndex
:::           fffff800`008a43c9 mov     rsi,qword ptr [r9+20h] ; rsi = pDispatcherContext->TargetIp
:::           fffff800`008a43cd sub     rsi,r14                ; rsi = pDispatcherContext->TargetIp - pDispatcherContext->ImageBase
:::           fffff800`008a43d0 cmp     edi,dword ptr [r12]    ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
:::           fffff800`008a43d4 mov     rax,rdi                ; rax = l_ScopeIndex
:::<          fffff800`008a43d7 jae     nt!__C_specific_handler+0x166 (fffff800`008a4436)
::::          
::::          nt!__C_specific_handler+0x109:
::::          fffff800`008a43d9 add     rax,rax ; 
::::          fffff800`008a43dc lea     rbx,[r12+rax*8+8] ; rbx = &(pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress)
::::          
::::          nt!__C_specific_handler+0x111:
::::          ; 檢查 ControlPc 處於哪一個 __try 保護域,之步驟一
::::        > fffff800`008a43e1 mov     eax,dword ptr [rbx-4] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
::::        : fffff800`008a43e4 cmp     rbp,rax ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
::::<       : fffff800`008a43e7 jb      nt!__C_specific_handler+0x15a (fffff800`008a442a)
:::::       : 
:::::       : nt!__C_specific_handler+0x119:
:::::       : ; 檢查 ControlPc 處於哪一個 __try 保護域,之步驟二
:::::       : fffff800`008a43e9 mov     ecx,dword ptr [rbx] ; ecx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
:::::       : fffff800`008a43eb cmp     rbp,rcx ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
:::::<      : fffff800`008a43ee jae     nt!__C_specific_handler+0x15a (fffff800`008a442a)
::::::      : 
::::::      : nt!__C_specific_handler+0x120:
::::::      : ; 到這裏,已經找到與異常地址匹配的最內層(若是有多層) __try/__except
::::::      : fffff800`008a43f0 cmp     rsi,rax ; cmp pDispatcherContext->TargetIp - pDispatcherContext->ImageBase, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
::::::<     : fffff800`008a43f3 jb      nt!__C_specific_handler+0x131 (fffff800`008a4401)
:::::::     : 
:::::::     : nt!__C_specific_handler+0x125:
:::::::     : fffff800`008a43f5 cmp     rsi,rcx ; cmp pDispatcherContext->TargetIp - pDispatcherContext->ImageBase, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
:::::::<    : fffff800`008a43f8 ja      nt!__C_specific_handler+0x131 (fffff800`008a4401)
::::::::    : 
::::::::    : nt!__C_specific_handler+0x12a:
::::::::    : ; 若是標記了 EXCEPTION_TARGET_UNWIND,說明是最後一個須要局部展開的函數。可是該次局部展開只展開到 EXCEPT_HANDLER(不包含 EXCEPT_HANDLER),因此須要判斷 TargetIp
::::::::    : fffff800`008a43fa test    byte ptr [r15+4],20h ; test pExceptionRecord->ExceptionFlags, EXCEPTION_TARGET_UNWIND (0x20)
::::::::<   : fffff800`008a43ff jne     nt!__C_specific_handler+0x166 (fffff800`008a4436)
:::::::::   : 
:::::::::   : nt!__C_specific_handler+0x131:
::::::>>:   : fffff800`008a4401 mov     eax,dword ptr [rbx+8] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
::::::  :   : fffff800`008a4404 test    eax,eax ; 判斷 pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget 是否爲 NULL,便是否是 __try/__finally
::::::  :<  : fffff800`008a4406 je      nt!__C_specific_handler+0x13f (fffff800`008a440f) ; 若是是 __try/__finally 則跳轉
::::::  ::  : 
::::::  ::  : nt!__C_specific_handler+0x138:
::::::  ::  : fffff800`008a4408 cmp     rsi,rax ; cmp pDispatcherContext->TargetIp, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
::::::  ::< : fffff800`008a440b je      nt!__C_specific_handler+0x166 (fffff800`008a4436)
::::::  ::: : 
::::::  ::: : nt!__C_specific_handler+0x13d:
::::::  :::<: fffff800`008a440d jmp     nt!__C_specific_handler+0x15a (fffff800`008a442a)
::::::  ::::: 
::::::  ::::: nt!__C_specific_handler+0x13f:
::::::  ::::: ; 注意這裏是先修改 pDispatcherContext->ScopeIndex,而後調用 EXCEPT_HANDLER。這樣若是 EXCEPT_HANDLER 觸發異常,後續展開就會跳過這個 EXCEPT_HANDLER。
::::::  :>::: fffff800`008a440f mov     rdx,qword ptr [rsp+98h]
::::::  : ::: fffff800`008a4417 lea     eax,[rdi+1]             ; eax = l_ScopeIndex + 1
::::::  : ::: fffff800`008a441a mov     cl,1
::::::  : ::: fffff800`008a441c mov     dword ptr [r13+48h],eax ; pDispatcherContext->ScopeIndex = eax
::::::  : ::: fffff800`008a4420 mov     r8d,dword ptr [rbx+4]   ; r8d = pDispatcherContext->HandlerData->ScopeRecord[i].HandlerAddress
::::::  : ::: fffff800`008a4424 add     r8,r14                  ; r8 = pDispatcherContext->HandlerData->ScopeRecord[i].HandlerAddress + pDispatcherContext->ImageBase
::::::  : ::: fffff800`008a4427 call    r8                      ; 調用 __finally 處理塊,會返回(注:對於 __try/__finally,HandlerAddress 保存的是 __finally 代碼塊的 RVA)
::::::  : ::: 
::::::  : ::: nt!__C_specific_handler+0x15a:
::::>>  : :>: fffff800`008a442a inc     edi                 ; l_ScopeIndex += 1
::::    : : : fffff800`008a442c add     rbx,10h             ; 調整到下一個 ScopeRecord::HandlerAddress
::::    : : : fffff800`008a4430 cmp     edi,dword ptr [r12] ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
::::    : : < fffff800`008a4434 jb      nt!__C_specific_handler+0x111 (fffff800`008a43e1)
::::    : :   
::::    : :   nt!__C_specific_handler+0x166:
>>:>    > >   fffff800`008a4436 mov     eax,1 ; eax = ExceptionContinueSearch (0n1)
   :           
   :           nt!__C_specific_handler+0x16b:
   >           fffff800`008a443b mov     r15,qword ptr [rsp+48h]
               fffff800`008a4440 mov     r14,qword ptr [rsp+50h]
               fffff800`008a4445 mov     r13,qword ptr [rsp+58h]
               fffff800`008a444a mov     r12,qword ptr [rsp+60h]
               fffff800`008a444f mov     rdi,qword ptr [rsp+68h]
               fffff800`008a4454 mov     rsi,qword ptr [rsp+70h]
               fffff800`008a4459 mov     rbp,qword ptr [rsp+78h]
               fffff800`008a445e mov     rbx,qword ptr [rsp+80h]
               fffff800`008a4466 add     rsp,88h
               fffff800`008a446d ret
nt!__C_specific_handler 至關於 x86 中的 nt!_except_handler3。從上面的反彙編代碼也能夠看出它的邏輯跟 nt!_except_handler3 基本上一致。
函數代碼不長。主要分爲兩個大分支,一個分支處理異常,一個分支處理展開(我用橫線分隔開了)。

異常解決的代碼負責遍歷 SCOPE_TABLE,依次調用 SCOPE_TABLE::ScopeRecord.HandlerAddress 表明的 EXCEPT_FILTER,並針對返回值作出相應的處理:
1. 返回 EXCEPTION_CONTINUE_EXECUTION,說明異常已經被 EXCEPT_FILTER 修復。返回 ExceptionContinueExecution。
2. 返回 EXCEPTION_CONTINUE_SEARCH,繼續遍歷下一個 ScopeRecord。
3. 返回 EXCEPTION_EXECUTE_HANDLER,說明當前 ScopeRecord.JumpTarget 表明的 EXCEPT_HANDLER 能夠處理該異常。那麼調用 RtlUnwindEx 進行展開。

熟悉 x86 的朋友可能會疑惑:在 x86 中 nt!_except_handler3 先進行全局展開,而後對本函數自身進行不徹底的局部展開,最後執行 EXCEPT_HANDLER。而在 nt!__C_specific_handler 中卻找不到執行 EXCEPT_HANDLER 的指令,這是怎麼回事?
實際上,x64 對這個流程作了一些調整,EXCEPT_HANDLER 不是由 nt!__C_specific_handler 直接調用,而是做爲參數傳給 RtlUnwindEx,RtlUnwindEx 處理完展開以後才執行 EXCEPT_HANDLER。後續咱們在講展開的時候會看到具體的方法。

__C_specific_handler 的展開分支,是對 SCOPE_TABLE 進行展開,邏輯很簡單,很少講了。

更詳細的信息,請參考上面反彙編代碼中我附的註釋。

另外還須要說一下 SCOPE_TABLE。
在 x86 中,遍歷 scopetable 時是經過運行時動態改變的 EXCEPTION_REGISTRATION::trylevel 來肯定應該首先遍歷哪個 scopetable_entry。而 x64 中沒有等同於 trylevel 的數據,有的朋友可能會說「SCOPE_TABLE 中不是有每一個 __try 保護域的範圍 RVA 嗎?經過範圍不就能夠肯定在哪一個 __try 中觸發了異常嗎?」。
咱們能夠先試試這種方法,如下面這段僞碼爲例,

 
Code:
    1 VOID SehTest()
    2 {
    3     __try // 1
    4     {
    5     }
    6     __except()
    7     {
    8     }
    9 
    10    __try // 2
    11    {
    12        __try // 3
    13        {
    14            ...
    15        }
    16        __except()
    17        {
    18        }
    19    }
    20    __except()
    21    {
    22    }
    23
    24    __try // 4
    25    {
    26    }
    27    __finally()
    28    {
    29    }
    30}
上述僞碼中總共有4個 __try,按照 x86 中的方法,SCOPE_TABLE 的內容應該是順序排列的,像這樣:

SCOPE_TABLE::Count 等於4,
SCOPE_TABLE::ScopeRecord[0] 表示行3開始的 __try/__except,
SCOPE_TABLE::ScopeRecord[1] 表示行10開始的 __try/__except,
SCOPE_TABLE::ScopeRecord[2] 表示行12開始的 __try/__except,
SCOPE_TABLE::ScopeRecord[3] 表示行24開始的 __try/__finally。

假設行14處觸發了異常,遍歷過程應該是這樣,
首先檢查 ScopeRecord[0],發現其範圍不包含 EXCEPT_POINT,繼續下一個,
開始檢查 ScopeRecord[1],範圍匹配了。

那是否是該把異常交給 ScopeRecord[1] 處理呢?
不是!從僞碼中能夠很明顯的看出,行14觸發的異常應該首先由行12開始的 __try/__except,即 ScopeRecord[1] 處理。

可見這種方法是行不通的。
MSC 經過調整 SCOPE_TABLE::ScopeRecord 的排列順序來解決這個問題:

SCOPE_TABLE::Count 等於4,
SCOPE_TABLE::ScopeRecord[0] 表示行3開始的 __try/__except,
SCOPE_TABLE::ScopeRecord[1] 表示行12開始的 __try/__except,
SCOPE_TABLE::ScopeRecord[2] 表示行10開始的 __try/__except,
SCOPE_TABLE::ScopeRecord[3] 表示行24開始的 __try/__finally。

即對於嵌套的 __try/__except/__finally,ScopeRecord 的排列順序是,最內層的 __try 排在前面,其次是次內層的,依次排到最外層。
這樣就能正確的遍歷 SCOPE_TABLE 了。

再用僞碼完整的展現一下 SCOPE_TABLE 的佈置,

SCOPE_TABLE::Count = 4。

SCOPE_TABLE::ScopeRecord[0].BeginAddress = RVA_L4; (行4的 RVA) // 第一個 __try
SCOPE_TABLE::ScopeRecord[0].EndAddress = RVA_L5;
SCOPE_TABLE::ScopeRecord[0].HandlerAddress = RVA_L6_EXCEPT_FILTER; (行6 __except 過濾代碼首地址的 RVA)
SCOPE_TABLE::ScopeRecord[0].JumpTarget = RVA_L7; 

SCOPE_TABLE::ScopeRecord[1].BeginAddress = RVA_L13; // 第三個 __try
SCOPE_TABLE::ScopeRecord[1].EndAddress = RVA_L15;
SCOPE_TABLE::ScopeRecord[1].HandlerAddress = RVA_L16_EXCEPT_FILTER; 
SCOPE_TABLE::ScopeRecord[1].JumpTarget = RVA_L7; 

SCOPE_TABLE::ScopeRecord[2].BeginAddress = RVA_L11; // 第二個 __try
SCOPE_TABLE::ScopeRecord[2].EndAddress = RVA_L19;
SCOPE_TABLE::ScopeRecord[2].HandlerAddress = RVA_L20_EXCEPT_FILTER;
SCOPE_TABLE::ScopeRecord[2].JumpTarget = RVA_L21; 

SCOPE_TABLE::ScopeRecord[3].BeginAddress = RVA_L25; // 第四個 __try
SCOPE_TABLE::ScopeRecord[3].EndAddress = RVA_L26;
SCOPE_TABLE::ScopeRecord[3].HandlerAddress = RVA_L28;
SCOPE_TABLE::ScopeRecord[3].JumpTarget = 0; 

咱們再模擬一下 nt!__C_specific_handler 是如何遍歷 SCOPE_TABLE 的:
1. 首先經過傳入參數中的 pDispatcherContext->ControlPc 和 pDispatcherContext->ImageBase 計算出異常觸發點的 RVA(簡稱 E_RVA)。參見 fffff800`008a430d 處的指令。
2. 經過 pDispatcherContext->ScopeIndex 確認是否須要遍歷。若是須要遍歷,則從它指定的 ScopeRecord 開始遍歷。pDispatcherContext->ScopeIndex 通常都爲0,只有返回 ExceptionCollidedUnwind 時,RtlDispatchException 纔可能將它設置爲其餘值。
3. 經過比較 E_RVA 和 ScopeRecord[?].BeginAddress、ScopeRecord[?].EndAddress 來找到正確的處理函數,
首先 ScopeRecord[0] 範圍不匹配,遍歷下一個,
而後 ScopeRecord[1],發現範圍匹配,而且是 __try/__except 組合。因而調用 ScopeRecord[1].HandlerAddress,假設它返回的是 EXCEPTION_CONTINUE_SEARCH,那麼繼續遍歷下一個,
此次是 ScopeRecord[2],發現範圍匹配,而且是 __try/__except 組合。因而調用 ScopeRecord[2].HandlerAddress,假設它返回的是 EXCEPTION_EXECUTE_HANDLER,那麼說明找到了解決方案。
4. 調用 RtlUnwindEx,把 ScopeRecord[2].JumpTarget 對應的絕對地址做爲 TargetIp 參數傳給它。RtlUnwindEx 全局展開完畢後執行 TargetIp。

到這裏,異常分發就大體講述完畢。接下來是關於展開和解決的內容。


3、展開、解決

x64 中展開使用的函數有 RtlVirtualUnwind、RtlUnwindEx 和 RtlpExecuteHandlerForUnwind。其中 RtlVirtualUnwind 已經講了,咱們來看看餘下的兩個。

首先是 RtlUnwindEx,原型以下:

 
Code:
    VOID
    RtlUnwindEx (
        IN PVOID TargetFrame OPTIONAL,
        IN PVOID TargetIp OPTIONAL,
        IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
        IN PVOID ReturnValue,
        IN PCONTEXT OriginalContext,
        IN PUNWIND_HISTORY_TABLE HistoryTable OPTIONAL
        );
參數分別是:
TargetFrame —— 目標幀,即最後一個須要展開的幀。
TargetIp —— 前面有講過,它就是 ScopeRecord[?].JumpTarget 表明的地址,即 EXCEPT_HANDLER。
ExceptionRecord —— 異常信息。
ReturnValue —— 傳遞給 TargetIp 的返回值,分析過程當中沒發現它有什麼用處。
OriginalContext —— 雖然被聲明爲 IN,可是實際上 RtlUnwindEx 並無使用它內部的數據,
HistoryTable —— 用於加速查找 RUNTIME_FUNCTION。

主要功能是:
從自身開始展開,到 TargetFrame 中止。而後跳轉到 TargetIp 繼續執行。
流程:
1. 申請一個類型爲 CONTEXT 的局部變量 l_Context,調用 RtlCaptureContext 將當前自身的環境複製到 l_Context。
2. 經過 RtlVirtualUnwind 對 l_Context 進行模擬展開,推進遍歷。對每一個遍歷到的 UNWIND_INFO,檢查 UNWIND_INFO::Flags 是否包含 UNW_FLAG_UHANDLER。若是包含,則調用 UNWIND_INFO::ExceptionHandler 進行局部展開。不然繼續遍歷下一個。
循環本步驟,直到展開到 TargetFrame,即到達解決異常的 EXCEPT_HANDLER 所在的函數(簡稱爲 ExceptionHandlerFunc)了。
3. 這時 l_Context 內已是從 RtlUnwindEx 完整展開到 EXCEPT_HANDLER 的環境了。即此時 l_Context 已是 ExceptionHandlerFunc 的執行環境了。
調用 RtlRestoreContext,用 l_Context 替換當前線程的執行環境,因而就跳轉到 EXCEPT_HANDLER 繼續執行。
這樣就完美的從觸發異常的環境跳到了新的環境中。

這個過程有點相似這種手法:
1. 將某臺機器的系統 ghost 到一個 bak.gho 文件。
2. 把 bak.gho 恢復到一臺臨時機器,而後對這臺臨時機器作一些調整。調整完畢後製做一個臨時機器的 bak_mod.gho。
3. 將 bak_mod.gho 恢復到原來那臺機器。

這個流程很重要,我手繪了一副圖幫助理解,

僞碼:
Code:
    __try
    {
        ExRaiseStatus(STATUS_INVALID_PARAMETER);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
    }

圖:
1. 異常解決流程,從 EXCEPT_POINT 到 RtlUnwindEx,途中已經找到可以解決該異常的 EXCEPT_HANDLER 了(以參數 TargetIp 表示),當前線程狀態爲 ThreadContext
Code:
+---------------------------------------+
| ......                                |
| RtlRaiseStatus                    |調 |
| RtlDispatchException              |用 |-> ThreadContext &
| RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler
| __C_specific_handler              |向 |
| RtlUnwindEx                       v   |
|                                       |
+---------------------------------------+

2. RtlUnwindEx 將當前自身狀態複製到 ThreadContext_Copy 中
Code:
+---------------------------------------+                                     +---------------------------------------+
| ......                                |                                     | ......                                |
| RtlRaiseStatus                    |調 |                                     | RtlRaiseStatus                    |調 |
| RtlDispatchException              |用 |-> ThreadContext &                   | RtlDispatchException              |用 |-> ThreadContext_Copy &
| RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler       | RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler
| __C_specific_handler              |向 |                                     | __C_specific_handler              |向 |
| RtlUnwindEx                       v   |                                     | RtlUnwindEx                       v   |
|                                       |                                     |                                       |
+---------------------------------------+                                     +---------------------------------------+

3. 用 ThreadContext_Copy 進行展開,一直展開到異常觸發點中止。
Code:
+---------------------------------------+                                     +---------------------------------------+
| .....                                 |                                     | ......                                |
| RtlRaiseStatus                    |調 |                                     | RtlRaiseStatus                    ^展 |
| RtlDispatchException              |用 |-> ThreadContext                     | RtlDispatchException              |開 |-> ThreadContext_Copy &
| RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler       | RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler
| __C_specific_handler              |向 |                                     | __C_specific_handler              |向 |
| RtlUnwindEx                       v   |                                     | RtlUnwindEx                       |   |
|                                       |                                     |                                       |
+---------------------------------------+                                     +---------------------------------------+

4. 將 ThreadContext_Copy.Rip 設置爲 TargetIp,以 ThreadContext_Copy 爲參數調用 RtlpRestoreContext。跳轉到 TargetIp 繼續執行。
Code:
+---------------------------------------+
| ......                                |
| EXCEPT_HANDLER                        |-> ThreadContext (ThreadContext.Rip = TargetIp)
|                                       |
+---------------------------------------+
這樣就完成了展開和執行 EXCEPT_HANDLER 的工做。

RtlpExecuteHandlerForUnwind 沒有什麼改變,原型依舊:

 
Code:
    EXCEPTION_DISPOSITION
    RtlpExecuteHandlerForUnwind (
        IN PEXCEPTION_RECORD ExceptionRecord,
        IN PVOID EstablisherFrame,
        IN OUT PCONTEXT ContextRecord,
        IN OUT PVOID DispatcherContext
        );
它會註冊一個異常處理函數 RtlpUnwindHandle,當觸發新異常時 RtlpUnwindHandler 會返回 ExceptionCollidedUnwind。關於 ExceptionCollidedUnwind,咱們後面還會詳細講述。
RtlpExecuteHandlerForUnwind 的實現源碼位於 $\WRK-v1.2\base\ntos\rtl\amd64\xcptmisc.asm:199。
RtlpUnwindHandle 的實現源碼位於 $\WRK-v1.2\base\ntos\rtl\amd64\xcptmisc.asm:136。

到這裏,咱們講完了展開的邏輯。接下來咱們要講述兩個比較特殊的返回值: ExceptionNestedException 和 ExceptionCollidedUnwind。


4、ExceptionNestedException 和 ExceptionCollidedUnwind

之因此專門講述這兩個返回值,是由於在分析過程當中,我感受常規狀況的 SEH 流程理解起來並不困難,難理解的是這兩種不通常的狀況。它們不通常之處在於:在處理異常的過程當中又觸發了新的異常。
先來說一下這兩個返回值的含義:
ExceptionNestedException —— 在異常分發過程當中觸發新的異常,好比執行 EXCEPT_FILTER 時觸發異常。
ExceptionCollidedUnwind —— 在展開過程當中觸發新的異常,好比執行 FINALLY_HANDLER 時觸發異常。

首先來說講 ExceptionNestedException,以以下僞碼爲例:

 
Code:
    1  VOID SehTest()
    2  {
    3      __try
    4      {
    5          ExRaiseStatus();
    6      }
    7      __except(ExRaiseStatus(), EXCEPTION_CONTINUE_SEARCH) // EXCEPT_FILTER_1
    8      { // EXCEPT_HANDLER_1
    9      }
    10 }
    11 
    12 VOID Caller()
    13 {
    14     __try
    15     {
    16         SehTest();
    17     }
    18     __except(EXCEPTION_EXECUTE_HANDLER) // EXCEPT_FILTER_2
    19     { // EXCEPT_HANDLER_2
    20     }
    21 }
上述代碼會兩次觸發異常,第一次是行5的 ExRaiseStatus,第二次是行7的 ExRaiseStatus。爲了方便區分,我將它們分別標記爲 EXCEPT_POINT#一、EXCEPT_POINT#2。
咱們來看一下這兩個異常的處理流程:

1. ExRaiseStatus#1 會建立保存 EXCEPT_POINT#1 觸發時的狀態 Context#1,並構建一個 EXCEPTION_RECORD,而後將他們做爲參數來調用 RtlDispatchException#1。(注:這種方式的的觸發點是 ExRaiseStatus 內部,而非 SehTest 的第5行。即Context#1 記錄的異常觸發點是 ExRaiseStatus#1 內部)

2. RtlDispatchException#1 根據 Context#1 首先找到 EXCEPT_POINT#1 所在函數 ExRaiseStatus#1 的 UNWIND_INFO,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_NHANDLER,因而繼續遍歷。

3. RtlDispatchException#1 遍歷到 SehTest,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_EHANDLER,因而調用其 UNWIND_INFO::ExceptionHandler,即 __C_specific_handler$2。__C_specific_handler$2 遍歷 SehTest 的 SCOPE_TABLE,發現惟一的一個 ScopeRecord。因而執行 ScopeRecord[0].HandlerAddress,即行7的 SehTest::EXCEPT_FILTER_1#1。此時的調用棧以下(豎線後的內容爲函數的 UNWIND_INFO::Flags 和 UNWIND_INFO::ExceptionHandler,其中 Flags 縮寫爲 E、U、N):
Code:
        (1)  Caller                                 | E  & __C_specific_handler$1
        (2)  SehTest                                | E  & __C_specific_handler$2
        (3)  ExRaiseStatus#1                        | N
        (4)  RtlDispatchException#1                 | N
        (5)  RtlpExecuteHandlerForException#1       | EU & RtlpExceptionHandler$5
        (6)  __C_specific_handler$2                 | N
        (7)  EXCEPT_FILTER_1#1                      | N

4. EXCEPT_FILTER#1 觸發 EXCEPT_POINT#2。同步驟1相似,ExRaiseStatus 會調用 RtlDispatchException#2,這個過程當中一樣會建立保存 EXCEPT_POINT#2 的狀態,咱們稱之爲 Context#2。

5. RtlDispatchException#2 根據 Context#2 找到了 EXCEPT_POINT#2 所在函數 ExRaiseStatus#2,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_NHANDLER,因而繼續遍歷。

6. RtlDispatchException#2 遍歷到 EXCEPT_FILTER_1#1,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_NHANDLER,因而繼續遍歷。(注:EXCEPT_FILTER 雖然代碼形式上從屬於 SehTest 函數,但實際上它是一個單獨的函數,有本身的 UNWIND_INFO,跟 SEH 的 UNWIND_INFO 並非同一個)

7. RtlDispatchException#2 遍歷到 __C_specific_handler$二、RltpExecuteHandlerForException#一、RtlDispatchException#一、E​xRaiseStatus#1,這些函數要麼被標記爲 UNW_FLAG_NHANDLER,要麼 UNWIND_INFO::ExceptionHandler 返回 ExcetpionNestedException,結果都是繼續遍歷,因此再也不一一講述。繼續遍歷下一個。

8. RtlDispatchException#2 遍歷到 SehTest,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_EHANDLER,因而調用其 UNWIND_INFO::ExceptionRoutine,即 __C_specific_handler$2,發現範圍匹配,因而調用 EXCEPT_FILTER_1#1,因而又觸發異常,此次是 #3 異常。此時的調用棧以下:
Code:
        (1)  Caller                                 | E  & __C_specific_handler$1
        (2)  SehTest                                | E  & __C_specific_handler$2
        (3)  ExRaiseStatus#1                        | N
        (4)  RtlDispatchException#1                 | N
        (5)  RtlpExecuteHandlerForException#1       | EU & RtlpExceptionHandler$5
        (6)  __C_specific_handler$2                 | N
        (7)  EXCEPT_FILTER_1#1                      | N
        (8)  ExRaiseStatus#2                        | N
        (9)  RtlDispatchException#2                 | N
        (10) RtlpExecuteHandlerForException#2       | EU & RtlpExceptionHandler$10
        (11) __C_specific_handler$2                 | N
        (12) EXCEPT_FILTER_1#2                      | N
        (13) ExRaiseStatus#3                        | N
9. #3 異常的處理流程同 #2 的處理流程相似,也會再遍歷到 __C_specific_handler$2,也會再調用 EXCEPT_FILTER_1,因而會觸發 #4 異常、#5 異常等等。最終內核棧溢出,BSOD。

以上就是 ExceptionNestedException 的產生以及處理的流程。過程當中還有一些細節操做,爲了描述簡潔,我沒有在上述過程當中一一講述。


再來看看 ExceptionCollidedUnwind。它比 ExceptionNestedException 更復雜一些,咱們以以下僞碼爲例,

 
Code:
    1  VOID SehTest()
    2  {
    3      __try
    4      {
    5          ExRaiseStatus();
    6      }
    7      __finally
    8      { // FINALLY_HANDLER_1
    9          ExRaiseStatus();
    10     }
    11 }
    12 
    13 VOID Caller()
    14 {
    15     __try
    16     {
    17         SehTest();
    18     }
    19     __except(EXCEPTION_EXECUTE_HANDLER) // EXCEPT_FILTER_2
    20     { // EXCEPT_HANDLER_2
    21     }
    22 }
僞碼中也有兩處觸發異常的地方,第一次在行5,第二次在行9。也分別標記爲 EXCEPT_POINT#1 和 EXCEPT_POINT#2。處理流程:

1. ExRaiseStatus#1 建立保存 EXCEPT_POINT#1 的狀態 Context#1,並構建一個 EXCEPTION_RECORD,而後將他們做爲參數來調用 RtlDispatchException#1。

2. RtlDispatchException#1 根據 Context#1 開始遍歷:
首先遍歷到 EXCEPT_POINT#1 所在函數 ExRaiseStatus,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_NHANDLER,因而遍歷下一個。
而後遍歷到 SehTest,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_UHANDLER,因而繼續遍歷下一個。
而後遍歷到 Caller,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_EHANDLER,因而調用其 UNWIND_INFO::ExceptionRoutine 即 __C_specific_handler。__C_specific_handler 發現能夠處理該異常,因而以 EXCEPT_HANDLER_2 爲 TargetIp 參數調用 RtlUnwindEx。

3. RtlUnwindEx 從自身開始展開,展開到 SehTest,執行 FINALLY_HANDLER_1 時觸發新異常。此時調用棧爲:
Code:
    (1)  Caller                              | E  & __C_specific_handler$1
       (2)  SehTest                             | U  & __C_specific_handler$2
       (3)  ExRaiseStatus#1                     | N
       (4)  RtlDispatchException#1              | N
       (5)  RtlpExecuteHandlerForException#1    | EU & RtlpExceptionHandler$5
       (6)  __C_specific_handler$1              | N
       (7)  RtlUnwindEx#1                       | N
       (8)  RtlpExecuteHandlerForUnwind#1       | EU & RtlpUnwindHandler$8
       (9)  __C_specific_handler$2              | N
       (10) FINALLY_HANDLER_1                   | N
       (11) ExRaiseStatus#2                     | N
須要說明的是,調用棧(7) RtlUnwindEx 建立並初始化了一個 DISPATCHER_CONTEXT 變量(後續稱之爲 pDispatcherContextForUnwind),並做爲參數傳遞給調用棧(8) RltpExecuteHandlerForUnwind,後者在調用(9) __C_specific_handler$2 以前將 pDispatcherContextForUnwind 保存在本身的棧中。此時 pDispatcherContextForUnwind 的內容表示的是調用棧(2) SehTest 的狀況。後續步驟會用到這個 pDispatcherContextForUnwind。

4. (11) ExRaiseStatus#2 將 EXCEPT_POINT#2 觸發時的狀態保存到 Context#2,而後調用 RtlDispatchException#2 進行 EXCEPT_POINT#2 的分發。

5. RtlDispatchException#2 根據 Context#2 開始遍歷,
首先遍歷到 EXCEPT_POINT#2 所在函數 ExRaiseStatus#2,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_NHANDLER,因而遍歷下一個。
而後遍歷到 FINALLY_HANDLER_1(同前面提到的 EXCEPT_FILTER 同樣,FINALLY_HANDLER 實際上也是一個單獨的函數,有本身的 RUNTIME_FUNCTION 和 UNWIND_INFO),發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_NHANDLER,因而遍歷下一個。
而後遍歷到(9) __C_specific_handler$2,發現其 UNWIND_INFO::Flags 爲 UNW_FLAG_NHANDLER,因而繼續遍歷。
而後遍歷到(8) RtlpExecuteHandlerForUnwind#1,發現其 UNWIND_INFO::Flags 包含 UNW_FLAG_EHANDLER。因而調用其 UNWIND_INFO::ExceptionRoutine 即 RtlpUnwindHandler$8。RtlpUnwindHandler$8 會取出步驟3中所提到的 pDispatcherContextForUnwind,將其內容拷貝到本身的傳出參數(參考 RtlpUnwindHandler 的函數原型)pDispatcherContext 中,而後返回 ExceptionCollidedUnwind。

6. RtlDispatchException#2 收到 ExceptionCollidedUnwind 後,從傳回來的 pDispatchContext 中取出諸如 ControlPc、EstablisherFrame 等值(如步驟3所說,此時這些值反應的是(2) SehTest 的狀態),用這些值來繼續遍歷。
首先遍歷到(2) SehTest,調用 RtlpExecuteHandlerForException#2,進而調用 __C_specific_handler$2,可是發現 pDispatcherContext->ScopeIndex(步驟(9)中在調用(10) FINALLY_HANDLER_1 以前+1了,參考 __C_specific_handler 反彙編碼)等於 pDispatcherContext->HandlerData->Count。因而繼續遍歷。
而後遍歷到(1) Caller,調用 RtlpExecuteHandlerForException#2,進而調用 __C_specific_handler$1,發現它能夠處理 #2 異常,因而以 EXCEPT_HANDLER#2 爲 TargetIp 參數調用 RtlUnwindEx。此時調用棧以下:
Code:
    (1)  Caller                              | E  & __C_specific_handler$1
       (2)  SehTest                             | U  & __C_specific_handler$2,但沒有 EXCEPT_HANDLER
       (3)  ExRaiseStatus#1                     | N
       (4)  RtlDispatchException#1              | N
       (5)  RtlpExecuteHandlerForException#1    | EU & RtlpExceptionHandler$6
       (6)  __C_specific_handler$1              | N
       (7)  RtlUnwindEx#1                       | N
       (8)  RtlpExecuteHandlerForUnwind#1       | EU & RtlpUnwindHandler$8
       (9)  __C_specific_handler$2              | N
       (10) FINALLY_HANDLER#1                   | N
       (11) ExRaiseStatus#2                     | N
       (12) RtlDispatchException#2              | N
       (13) RtlpExecuteHandlerForException#2    | EU & RtlpExceptionHandler
       (14) __C_specific_handler$1              | N
       (15) RtlUnwindEx                         | N
7. (17) RtlUnwindEx 展開完畢後,經過 RtlRestoreContext 跳轉到 EXCEPT_HANDLER#2 繼續執行。

在上述過程當中,咱們能夠發現,遍歷過程當中 RtlDispatchException 等系統函數被頻繁遍歷到。因而就有了前面提到的全局展開歷史表 RtlpUnwindHistoryTable,這個表中存放着 RtlDispatchException、RtlUnwindEx 等函數的 RUNTIME_FUNCTION 和 ImageBase 信息,這樣就不用每次都去解析 PE+ 中的 ExceptionDirectory,實現了加速。


到這裏,咱們就講完了 x64 SEH 的實現。能夠發現,x64 和 x86 的 SEH 思想或者說框架是同樣的:
1. RtlDispatchException 和 RtlUnwindEx 都對異常註冊信息進行遍歷。前者是爲了分發異常而遍歷,後者是爲了展開而遍歷。
2. MSC 提供的異常處理函數按照「異常解決」和「展開」兩個分支,對 SCOPE_TABLE/scopetable 進行遍歷。前者是爲了找到 EXCEPT_FILTER & EXCEPT_HANDLER,後者是爲了找到 FINALLY_HANDLER。
3. RtlDispatchException 和 RtlUnwindEx 藉助 MSC 提供的異常處理函數這個橋樑,配合處理異常。
主要的改變有兩點:
1. RtlDispatchException 和 RtlUnwindEx 經過調用 RtlVirtualUnwind 推進遍歷。
2. 全部的非葉函數都參與到 SEH,儘管大部分的函數都沒有使用到 SEH。

以上咱們主要講述的是 x64 SEH 的內部實現。對於使用者,也有一個好消息,咱們來看看,

C 代碼:

 
Code:
    VOID SehTest()
    {
        __try
        {
            __try
            {
                ExRaiseStatus(STATUS_INVALID_PARAMETER);
                DbgPrint("%u [%s] __try \n", __LINE__, __FILE__);
            }
            __except((STATUS_INVALID_PARAMETER == GetExceptionCode()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER)
            {
                DbgPrint("%u [%s] __except \n", __LINE__, __FILE__);
            }
        }
        __finally
        
            DbgPrint("%u [%s] __finally \n", __LINE__, __FILE__);
        }
    }
反彙編碼:
 
Code:
    kd> uf passthrough!SehTest
    PassThrough!SehTest:
    fffffadf`f1100020 sub     rsp,38h
    fffffadf`f1100024 mov     ecx,0C000000Dh
    fffffadf`f1100029 call    qword ptr [PassThrough!_imp_ExRaiseStatus (fffffadf`f1101050)]
    fffffadf`f110002f lea     r8,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100500)]
    fffffadf`f1100036 mov     edx,39h
    fffffadf`f110003b lea     rcx,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100540)]
    fffffadf`f1100042 call    PassThrough!DbgPrint (fffffadf`f11004a6)
    fffffadf`f1100047 jmp     PassThrough!SehTest+0x42 (fffffadf`f1100062)
    
    PassThrough!SehTest+0x42:
    fffffadf`f1100062 lea     r8,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100500)]
    fffffadf`f1100069 mov     edx,42h
    fffffadf`f110006e lea     rcx,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100570)]
    fffffadf`f1100075 call    PassThrough!DbgPrint (fffffadf`f11004a6)
    fffffadf`f110007a add     rsp,38h
    fffffadf`f110007e ret
咱們發現 SehTest 內部徹底沒有任何 SEH 的蹤影,不像 x86 那樣會有建立、銷燬 EXCEPTION_REGISTRATION_RECORD 和調整 EXCEPTION_REGISTRATION_RECORD::trylevel 等操做。
這樣的好處就是使用者無需再擔憂性能損耗,能夠放心大膽的使用 SEH 機制了。


附錄一

爲了方便本身分析,我寫了一個簡單的 windbg 擴展,提供了幾個 x64 seh 經常使用功能:
Code:
    !boxr.unwindinfo    module-name    unwindinfo_addr
    功能:
        用於查詢指定 UNWIND_INFO 結構的詳細信息。
    參數說明:
        module-name —— 待查詢的 UNWIND_INFO 結構對應函數的模塊名
        unwindinfo_addr —— UNWIND_INFO 結構的絕對地址
 
Code:
    !boxr.rtfn    option    module    runtimefunction_addr
    功能:
        用於查詢指定 RUNTIME_FUNCTION 結構的詳細信息。(rtfn 表示 RunTime_FunctioN)
    參數說明:
        option —— 參數選項,目前支持兩種:
            /a 表示 module 參數爲模塊基地址
            /n 表示 module 參數爲模塊名稱
        module —— RUNTIME_FUNCTION 結構對應函數所在的模塊,具體形式根據 option 而定。
        runtimefunction_addr —— 須要查詢的 RUNTIME_FUNCTION 結構體的絕對地址。支持 @rax 操做方式,但不支持複雜的組合,好比 @rax+8。
使用的方法是:用 .extpath+ 命令將 boxr.dll 所在的目錄添加到 windbg 的搜索路徑中,而後就可使用了。須要卸載時就 .unload。

簡單說明一下這兩個命令。

好比咱們要查看下面這個函數的 UNWIND_INFO 信息:
Code:
    VOID SehTest()
    {
        __try
        {
            DbgPrint("%u [%s] __try \n", __LINE__, __FILE__);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            DbgPrint("%u [%s] __except \n", __LINE__, __FILE__);
        }
    
        __try
        {
            __try
            {
                __try
                {
                    CollidedUnwind();
                }
                __except(EXCEPTION_EXECUTE_HANDLER)
                {
                    DbgPrint("%u [%s] __except \n", __LINE__, __FILE__);
                }
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                DbgPrint("%u [%s] __except \n", __LINE__, __FILE__);
            }
        }
        __finally
        {
            DbgPrint("%u [%s] __finally \n", __LINE__, __FILE__);
        }
    
        __try
        {
            DbgPrint("%u [%s] __try \n", __LINE__, __FILE__);
    
            __try
            {
                DbgPrint("%u [%s] __try \n", __LINE__, __FILE__);
            }
            __finally
            {
                DbgPrint("%u [%s] __finally \n", __LINE__, __FILE__);
            }
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            DbgPrint("%u [%s] __except \n", __LINE__, __FILE__);
        }
    
        return;
    }

操做步驟:
1. 使用 .fnent 命令得到 SehTest 的基本信息,
Code:
        kd> .fnent passthrough!SehTest
        Debugger function entry 00000000`00758210 for:
        (fffffadf`f135d080)   PassThrough!SehTest   |  (fffffadf`f135d180)   PassThrough!LeafTest
        Exact matches:
            PassThrough!SehTest (void)
        
        BeginAddress      = 00000000`00001080
        EndAddress        = 00000000`00001175
        UnwindInfoAddress = 00000000`000026b8
        
        Unwind info at fffffadf`f135e6b8, 10 bytes
          version 1, flags 3, prolog 4, codes 1
          handler routine: PassThrough!_C_specific_handler (fffffadf`f135d5de), data 6
          00: offs 4, unwind op 2, op info 4    UWOP_ALLOC_SMALL.
2. 使用 !boxr.unwindinfo 查詢詳細信息,
Code:
        kd> !boxr.unwindinfo passthrough fffffadf`f135e6b8
        _UNWIND_INFO for fffffadff135e6b8 
        Flags:
            EU
        ExceptionRoutine:
            PassThrough!_C_specific_handler (fffffadf`f135d5de)
        ScopeTable:
            Count: 6
            ScopeRecord[0]      (fffffadff135e6c8)
                BeginAddress: 
                    PassThrough!SehTest+0x4 (fffffadf`f135d084)
                EndAddress: 
                    PassThrough!SehTest+0x1e (fffffadf`f135d09e)
                HandlerAddress: 
                    PassThrough!SehTest$filt$0 (fffffadf`f135d8a0)
                JumpTarget: 
                    PassThrough!SehTest+0x1e (fffffadf`f135d09e)
            [省略中間3個 ScopeRecord 成員]
            ScopeRecord[5]      (fffffadff135e718)
                BeginAddress: 
                    PassThrough!SehTest+0x8b (fffffadf`f135d10b)
                EndAddress: 
                    PassThrough!SehTest+0xd7 (fffffadf`f135d157)
                HandlerAddress: 
                    PassThrough!SehTest$filt$5 (fffffadf`f135d960)
                JumpTarget: 
                    PassThrough!SehTest+0xd7 (fffffadf`f135d157)
!boxr.rtfn 的用法也相似,好比:
Code:
        kd> !box.rtfn /n passThrough @rax
        _RUNTIME_FUNCTION for fffffadff1113000 
        BeginAddress:
            PassThrough!CollidedUnwind (fffffadf`f1110020)
        EndAddress:
            PassThrough!CollidedUnwind+0x38 (fffffadf`f1110058)
        UnwindData:
            fffffadff1111688
        _UNWIND_INFO for fffffadff1111688 
        Flags:
            U
        ExceptionRoutine:
            PassThrough!_C_specific_handler (fffffadf`f11104ee)
        ScopeTable:
            Count: 1
            ScopeRecord[0]      (fffffadff1111698)
                BeginAddress: 
                    PassThrough!CollidedUnwind+0x4 (fffffadf`f1110024)
                EndAddress: 
                    PassThrough!CollidedUnwind+0x10 (fffffadf`f1110030)
                HandlerAddress: 
                    PassThrough!CollidedUnwind$fin$0 (fffffadf`f1110750)
                JumpTarget: 
                    0
須要說明的是,我寫這個擴展的目的僅僅是爲了分析 x64 SEH 過程當中能輕鬆的查看相關數據結構的詳細信息,因此並無在這個擴展上花不少時間。其代碼是從 MS 例子代碼的基礎上增長了我須要的功能。應該有一些 BUG,可是對我來講不重要,已經知足個人須要了。源碼也放在附件裏,方便分析的朋友根據本身的須要進行修改。
另外有一個疑問:我編譯的 x64 wrk1.2 內核沒法對 .c代碼進行源碼調試,對 .asm 代碼卻是能夠,這是爲何?我看了一下編譯選項,沒看出什麼貓膩。有經驗的朋友分享一下吧,感謝 :-)


附錄二 RtlUnwindEx 的反彙編代碼和註釋

因爲沒法源碼調試,只好把它反彙編出來加上註釋……
Code:
VOID
             RtlUnwindEx (
/* rcx    */     IN PVOID pEstablisherFrame OPTIONAL,
/* rdx    */     IN PVOID pJumpTargetIp OPTIONAL,
/* r8     */     IN PEXCEPTION_RECORD pExceptionRecord OPTIONAL,
/* r9     */     IN PVOID ReturnValue,
/* rsp+28 */     IN PCONTEXT pOriginalContext,
/* rsp+30 */     IN PUNWIND_HISTORY_TABLE pHistoryTable OPTIONAL
                 );

             kd> uf nt!RtlUnwindEx
             nt!RtlUnwindEx:
             fffff800`00891e70 mov     qword ptr [rsp+20h],r9
             fffff800`00891e75 mov     qword ptr [rsp+18h],r8
             fffff800`00891e7a mov     qword ptr [rsp+10h],rdx
             fffff800`00891e7f mov     rax,rsp
             fffff800`00891e82 sub     rsp,678h
             fffff800`00891e89 mov     qword ptr [rax-8],rbx
             fffff800`00891e8d mov     qword ptr [rax-10h],rbp
             fffff800`00891e91 mov     qword ptr [rax-18h],rsi
             fffff800`00891e95 mov     rsi,qword ptr [rsp+6A0h] ; rsi = pOriginalContext
             fffff800`00891e9d mov     qword ptr [rax-20h],rdi
             fffff800`00891ea1 mov     qword ptr [rax-28h],r12
             fffff800`00891ea5 mov     qword ptr [rax-40h],r15
             fffff800`00891ea9 mov     rbp,rcx
             fffff800`00891eac mov     r15,rdx
             fffff800`00891eaf lea     rdx,[rsp+40h] ; rsp+40 爲 l_HighLimit
             fffff800`00891eb4 lea     rcx,[rsp+50h] ; rsp+50 爲 l_LowLimit
             fffff800`00891eb9 mov     rbx,r8
             fffff800`00891ebc mov     rdi,rsi
             fffff800`00891ebf lea     r12,[rax-518h] ; r12 = &l_Context
             fffff800`00891ec6 call    nt!RtlpGetStackLimits (fffff800`00890da0)
             fffff800`00891ecb mov     rcx,rsi
             fffff800`00891ece call    nt!RtlCaptureContext (fffff800`008bd150)
             fffff800`00891ed3 mov     rax,qword ptr [rsp+6A8h] ; rax = pHistoryTable
             fffff800`00891edb test    rax,rax
<            fffff800`00891ede je      nt!RtlUnwindEx+0x74 (fffff800`00891ee4)
.            
.            nt!RtlUnwindEx+0x70:
.            fffff800`00891ee0 mov     byte ptr [rax+4],1 ; pHistoryTable->Search = UNWIND_HISTORY_TABLE_GLOBAL
.            
.            nt!RtlUnwindEx+0x74:
>            fffff800`00891ee4 xor     ecx,ecx
             fffff800`00891ee6 test    rbx,rbx ; 判斷 pExceptionRecord 是否爲 NULL
<            fffff800`00891ee9 jne     nt!RtlUnwindEx+0xbc (fffff800`00891f2c)
.            
.            nt!RtlUnwindEx+0x7b:
.            ; pExceptionRecord 等於 NULL
.            fffff800`00891eeb mov     rax,qword ptr [rsi+0F8h]  ; rax = pOriginalContext->Rip
.            fffff800`00891ef2 lea     rbx,[rsp+0C0h]            ; rbx = &l_ExceptionRecord
.            fffff800`00891efa mov     dword ptr [rsp+0C0h],0C0000027h ; l_ExceptionRecord.ExceptionCode = STATUS_UNWIND (0xC0000027)
.            fffff800`00891f05 mov     qword ptr [rsp+0D0h],rax  ; l_ExceptionRecord.ExceptionAddress = pOriginalContext->Rip
.            fffff800`00891f0d mov     rax,qword ptr [rsp+6A8h]  ; rax = pHistoryTable
.            fffff800`00891f15 mov     qword ptr [rsp+690h],rbx  ; [r8-home] = &l_ExceptionRecord ????
.            fffff800`00891f1d mov     qword ptr [rsp+0C8h],rcx  ; l_ExceptionRecord.ExceptionRecord = NULL
.            fffff800`00891f25 mov     dword ptr [rsp+0D8h],ecx  ; l_ExceptionRecord.NumberParameters = 0
.            
.            nt!RtlUnwindEx+0xbc:
>            fffff800`00891f2c mov     rbx,qword ptr [rsp+680h]  ; rbx = &[rcx-home]
             fffff800`00891f34 mov     esi,2 ; l_ExceptionFlags(esi) = EXCEPTION_UNWINDING (2)
             fffff800`00891f39 mov     ecx,6 ; ecx = EXCEPTION_EXIT_UNWIND (6)
             fffff800`00891f3e test    rbp,rbp ; 判斷 pEstablisherFrame 是否爲 NULL
             fffff800`00891f41 mov     qword ptr [rsp+648h],r13 ; 保存 r13 
             fffff800`00891f49 mov     qword ptr [rsp+640h],r14 ; 保存 r14
             fffff800`00891f51 cmove   esi,ecx ; if (NULL == pEstablisherFrame) { l_ExceptionFlags = EXCEPTION_EXIT_UNWIND (6) }
             fffff800`00891f54 xchg    ax,ax
             fffff800`00891f58 xchg    ax,ax
             fffff800`00891f5c xchg    ax,ax
             
             nt!RtlUnwindEx+0xf0:
           > fffff800`00891f60 mov     r13,qword ptr [rdi+0F8h] ; r13 = pOriginalContext->Rip
           . fffff800`00891f67 lea     rdx,[rsp+60h] ; rdx = &l_pImageBase
           . fffff800`00891f6c mov     r8,rax        ; r8 = pHistoryTable
           . fffff800`00891f6f mov     rcx,r13
           . fffff800`00891f72 mov     qword ptr [rsp+68h],r13
           . fffff800`00891f77 call    nt!RtlLookupFunctionEntry (fffff800`00890e60)
           .                   ; l_pFunctionEntry = RtlLookupFunctionEntry(pOriginalContext->Rip, 
           .                   ;                                           &l_pImageBase,
           .                   ;                                           pHistoryTable)
           . fffff800`00891f7c test    rax,rax ; 判斷 l_pFunctionEntry (eax) 是否爲 NULL
           . fffff800`00891f7f mov     r14,rax ; r14 = l_pFunctionEntry
<          . fffff800`00891f82 je      nt!RtlUnwindEx+0x3ab (fffff800`0089221b)
.          . 
.          . nt!RtlUnwindEx+0x118:
.          . fffff800`00891f88 mov     rdx,rdi ; rdx = pOriginalContext
.          . fffff800`00891f8b mov     rcx,r12 ; rcx = &l_Context
.          . fffff800`00891f8e call    nt!RtlpCopyContext (fffff800`00891080)
.          .                   ; RtlpCopyContext(&l_Context, pOriginalContext)
.          . fffff800`00891f93 mov     rdx,qword ptr [rsp+60h] ; rdx = l_pImageBase
.          . fffff800`00891f98 mov     qword ptr [rsp+38h],0   ; _ARG_8 = 0
.          . fffff800`00891fa1 lea     rax,[rsp+680h]          ; rax = &[rcx-home],這裏被用做局部變量 l_pEstablisherFrame 空間
.          . fffff800`00891fa9 mov     r9,r14                  ; r9 = l_pFunctionEntry
.          . fffff800`00891fac mov     r8,r13                  ; r8 = pOriginalContext->Rip
.          . fffff800`00891faf mov     qword ptr [rsp+30h],rax ; _ARG7 = &l_pEstablisherFrame
.          . fffff800`00891fb4 lea     rax,[rsp+58h]           ; rax = &l_pHandlerData
.          . fffff800`00891fb9 mov     ecx,2                   ; ecx = UNW_FLAG_UHANDLER (2)
.          . fffff800`00891fbe mov     qword ptr [rsp+28h],rax ; _ARG_6 = &l_pHandlerData
.          . fffff800`00891fc3 mov     qword ptr [rsp+20h],r12 ; _ARG_5 = &l_Context
.          . fffff800`00891fc8 call    nt!RtlVirtualUnwind (fffff800`00891380)
.          .                   ; l_pExceptionRoutine = RtlVirtualUnwind(UNW_FLAG_UHANDLER,
.          .                   ;                                        l_pImageBase,
.          .                   ;                                        pOriginalContext->Rip,
.          .                   ;                                        l_pFunctionEntry,
.          .                   ;                                        &l_Context,
.          .                   ;                                        &l_pHandlerData,
.          .                   ;                                        &l_pEstablisherFrame,
.          .                   ;                                        NULL);
.          . fffff800`00891fcd mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
.          . fffff800`00891fd5 mov     rcx,rax                  ; rcx = l_pExceptionRoutine
.          . fffff800`00891fd8 mov     qword ptr [rsp+48h],rax
.          . fffff800`00891fdd test    bl,7                     ; 檢查 l_pEstablisherFrame 是否對齊
.<         . fffff800`00891fe0 jne     nt!RtlUnwindEx+0x431 (fffff800`008922a1)
..         . 
..         . nt!RtlUnwindEx+0x176:
..         . fffff800`00891fe6 cmp     rbx,qword ptr [rsp+50h]  ; cmp l_pEstablisherFrame, l_LowLimit
..<        . fffff800`00891feb jb      nt!RtlUnwindEx+0x184 (fffff800`00891ff4)
...        . 
...        . nt!RtlUnwindEx+0x17d:
...        . fffff800`00891fed cmp     rbx,qword ptr [rsp+40h]  ; cmp l_pEstablisherFrame, l_HighLimit
...<       . fffff800`00891ff2 jb      nt!RtlUnwindEx+0x1d3 (fffff800`00892043)
....       . 
....       . nt!RtlUnwindEx+0x184:
....       . ; 檢查 l_pEstablisherFrame 是否合法
..>.       . fffff800`00891ff4 mov     cl,byte ptr gs:[20DEh]   ; cl = _KPCR->DpcRoutineActive
.. .       . fffff800`00891ffc test    cl,cl                    ; 判斷當前是否在執行 DPC
.< .       . fffff800`00891ffe jne     nt!RtlUnwindEx+0x431 (fffff800`008922a1) ; 若是是在執行 DPC 則失敗???
.. .       . 
.. .       . nt!RtlUnwindEx+0x194:
.. .       . fffff800`00892004 mov     rcx,qword ptr [rsp+40h] ; rcx = l_HighLimit
.. .       . fffff800`00892009 mov     rax,qword ptr [rcx-28h] ; rax = l_KernelStackCtrl->Previous.StackBase
.. .       . fffff800`0089200d test    rax,rax                 ; 判斷 l_KernelStackCtrl->Previous.StackBase 是否爲 NULL
.< .       . fffff800`00892010 je      nt!RtlUnwindEx+0x431 (fffff800`008922a1)
.. .       . 
.. .       . nt!RtlUnwindEx+0x1a6:
.. .       . fffff800`00892016 mov     rdx,qword ptr [rcx-20h]  ; rdx = l_KernelStackCtrl->Previous.StackLimit
.. .       . fffff800`0089201a mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
.. .       . fffff800`00892022 cmp     rbx,rdx                  ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackLimit
.< .       . fffff800`00892025 jb      nt!RtlUnwindEx+0x431 (fffff800`008922a1)
.. .       . 
.. .       . nt!RtlUnwindEx+0x1bb:
.. .       . fffff800`0089202b cmp     rbx,rax ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackBase
.< .       . fffff800`0089202e jae     nt!RtlUnwindEx+0x431 (fffff800`008922a1)
.. .       . 
.. .       . nt!RtlUnwindEx+0x1c4:
.. .       . fffff800`00892034 mov     rcx,qword ptr [rsp+48h] ; rcx = l_pExceptionRoutine
.. .       . fffff800`00892039 mov     qword ptr [rsp+50h],rdx ; l_LowLimit = l_KernelStackCtrl->Previous.StackLimit
.. .       . fffff800`0089203e mov     qword ptr [rsp+40h],rax ; l_HighLimit = l_KernelStackCtrl->Previous.StackBase
.. .       . 
.. .       . nt!RtlUnwindEx+0x1d3:
.. >       . fffff800`00892043 test    rbp,rbp ; 判斷 pEstablisherFrame 是否爲 NULL
.. <       . fffff800`00892046 je      nt!RtlUnwindEx+0x1e1 (fffff800`00892051)
.. .       . 
.. .       . nt!RtlUnwindEx+0x1d8:
.. .       . fffff800`00892048 cmp     rbp,rbx ; cmp pEstablisherFrame, l_pEstablisherFrame
.< .       . fffff800`0089204b jb      nt!RtlUnwindEx+0x431 (fffff800`008922a1)
.. .       . 
.. .       . nt!RtlUnwindEx+0x1e1:
.. >       . fffff800`00892051 test    rcx,rcx ; 判斷 l_pExceptionRoutine 是否爲 NULL
..  <      . fffff800`00892054 je      nt!RtlUnwindEx+0x39b (fffff800`0089220b)
..  .      . 
..  .      . nt!RtlUnwindEx+0x1ea:
..  .      . fffff800`0089205a mov     r13,qword ptr [rsp+58h] ; r13 = l_pHandlerData
..  .      . fffff800`0089205f mov     qword ptr [rsp+90h],r15 ; [rsp+90] = pJumpTargetIp
..  .      . fffff800`00892067 xor     r15d,r15d
..  .      . fffff800`0089206a xchg    ax,ax
..  .      . fffff800`0089206d xchg    ax,ax
..  .      . 
..  .      . nt!RtlUnwindEx+0x200:
..  .    > . fffff800`00892070 cmp     rbp,rbx ; cmp pEstablisherFrame, l_pEstablisherFrame
..  .<   . . fffff800`00892073 jne     nt!RtlUnwindEx+0x208 (fffff800`00892078)
..  ..   . . 
..  ..   . . nt!RtlUnwindEx+0x205:
..  ..   . . fffff800`00892075 or      esi,20h ; l_ExceptionFlags |= EXCEPTION_TARGET_UNWIND (0x20)
..  ..   . . 
..  ..   . . nt!RtlUnwindEx+0x208:
..  .>   . . fffff800`00892078 mov     r10,qword ptr [rsp+690h] ; r10 = &l_ExceptionRecord
..  .    . . fffff800`00892080 mov     rax,qword ptr [rsp+698h] ; rax = ReturnValue
..  .    . . fffff800`00892088 mov     qword ptr [rsp+0A0h],rcx ; l_DispatcherContext.LanguageHandler = l_pExceptionRoutine
..  .    . . fffff800`00892090 mov     dword ptr [r10+4],esi    ; l_ExceptionRecord.ExceptionFlags = l_ExceptionFlags
..  .    . . fffff800`00892094 mov     qword ptr [rdi+78h],rax  ; pOriginalContext->Rax = ReturnValue
..  .    . . fffff800`00892098 mov     rax,qword ptr [rsp+68h]  ; rax = pOriginalContext->Rip
..  .    . . fffff800`0089209d mov     qword ptr [rsp+70h],rax  ; l_DispatcherContext.ControlPc = pOriginalContext->Rip
..  .    . . fffff800`008920a2 mov     rax,qword ptr [rsp+60h]  ; rax = l_pImageBase
..  .    . . fffff800`008920a7 lea     r9,[rsp+70h]             ; r9 = &l_DispatcherContext
..  .    . . fffff800`008920ac mov     qword ptr [rsp+78h],rax  ; l_DispatcherContext.ImageBase = l_pImageBase
..  .    . . fffff800`008920b1 mov     rax,qword ptr [rsp+6A8h] ; rax = pHistoryTable
..  .    . . fffff800`008920b9 mov     r8,rdi                   ; r8 = pOriginalContext
..  .    . . fffff800`008920bc mov     rdx,rbx                  ; rdx = l_pEstablisherFrame
..  .    . . fffff800`008920bf mov     rcx,r10                  ; rcx = &l_ExceptionRecord
..  .    . . fffff800`008920c2 mov     qword ptr [rsp+80h],r14   ; l_DispatcherContext.FunctionEntry = l_pFunctionEntry
..  .    . . fffff800`008920ca mov     qword ptr [rsp+0B0h],rax  ; l_DispatcherContext.HistoryTable = pHistoryTable
..  .    . . fffff800`008920d2 mov     qword ptr [rsp+88h],rbx   ; l_DispatcherContext.EstablisherFrame = l_pEstablisherFrame
..  .    . . fffff800`008920da mov     qword ptr [rsp+98h],rdi   ; l_DispatcherContext.ContextRecord = pOriginalContext
..  .    . . fffff800`008920e2 mov     qword ptr [rsp+0A8h],r13  ; l_DispatcherContext.HandlerData = l_pHandlerData
..  .    . . fffff800`008920ea mov     dword ptr [rsp+0B8h],r15d ; l_DispatcherContext.ScopeIndex = 0
..  .    . . fffff800`008920f2 and     esi,0FFFFFF9Fh            ; l_ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND|EXCEPTION_COLLIDED_UNWIND)
..  .    . . fffff800`008920f5 call    nt!RtlpExecuteHandlerForUnwind (fffff800`008bd9d0)
..  .    . .                   ; RtlpExecuteHandlerForUnwind(&l_ExceptionRecord,
..  .    . .                   ;                             l_pEstablisherFrame,
..  .    . .                   ;                             pOriginalContext,
..  .    . .                   ;                             &l_DispatcherContext);
..  .    . . fffff800`008920fa dec     eax
..  .<   . . fffff800`008920fc je      nt!RtlUnwindEx+0x368 (fffff800`008921d8) ; 若是返回 ExceptionContinueSearch 則跳轉
..  ..   . . 
..  ..   . . nt!RtlUnwindEx+0x292:
..  ..   . . fffff800`00892102 cmp     eax,2 ; cmp eax, ExceptionCollidedUnwind (3 - 1)
..  ..<  . . fffff800`00892105 jne     nt!RtlUnwindEx+0x426 (fffff800`00892296)
..  ...  . . 
..  ...  . . nt!RtlUnwindEx+0x29b:
..  ...  . . ; ExceptionCollidedUnwind 的狀況
..  ...  . . fffff800`0089210b mov     r8,qword ptr [rsp+70h]   ; r8 = l_DispatcherContext.ControlPc
..  ...  . . fffff800`00892110 mov     r10,qword ptr [rsp+78h]  ; r10 = l_DispatcherContext.ImageBase
..  ...  . . fffff800`00892115 mov     rdx,qword ptr [rsp+98h]  ; rdx = l_DispatcherContext.ContextRecord
..  ...  . . fffff800`0089211d mov     rcx,qword ptr [rsp+6A0h] ; rcx = pOriginalContext
..  ...  . . fffff800`00892125 mov     r14,qword ptr [rsp+80h]  ; r14 = l_DispatcherContext.FunctionEntry
..  ...  . . fffff800`0089212d mov     qword ptr [rsp+68h],r8
..  ...  . . fffff800`00892132 mov     qword ptr [rsp+60h],r10
..  ...  . . fffff800`00892137 call    nt!RtlpCopyContext (fffff800`00891080)
..  ...  . . ;                 RtlpCopyContext(pOriginalContext, l_DispatcherContext.ContextRecord);
..  ...  . . fffff800`0089213c mov     rdx,rcx        ; rdx = pOriginalContext
..  ...  . . fffff800`0089213f mov     rdi,rcx        ; rdi = pOriginalContext
..  ...  . . fffff800`00892142 lea     rcx,[rsp+160h] ; rcx = &l_Context
..  ...  . . fffff800`0089214a lea     r12,[rsp+160h] ; r12 = &l_Context
..  ...  . . fffff800`00892152 call    nt!RtlpCopyContext (fffff800`00891080)
..  ...  . . ;                 RtlpCopyContext(&l_Context, pOriginalContext);
..  ...  . . fffff800`00892157 mov     qword ptr [rsp+38h],0   ; _ARG_8 = NULL
..  ...  . . fffff800`00892160 lea     rax,[rsp+680h]          ; rax = &l_pEstablisherFrame
..  ...  . . fffff800`00892168 mov     qword ptr [rsp+30h],rax ; _ARG_7 = &l_pEstablisherFrame
..  ...  . . fffff800`0089216d lea     rax,[rsp+58h]           ; rax = &l_pHandlerData
..  ...  . . fffff800`00892172 mov     r9,r14                  ; r9 = l_DispatcherContext.FunctionEntry
..  ...  . . fffff800`00892175 mov     qword ptr [rsp+28h],rax ; _ARG_6 = &l_pHandlerData
..  ...  . . fffff800`0089217a lea     rax,[rsp+160h]          ; rax = &l_Context
..  ...  . . fffff800`00892182 mov     rdx,r10                 ; rdx = l_DispatcherContext.ImageBase
..  ...  . . fffff800`00892185 xor     ecx,ecx                 ; ecx = UNW_FLAG_NHANDLER (0)
..  ...  . . fffff800`00892187 mov     qword ptr [rsp+20h],rax ; _ARG_5 = &l_Context
..  ...  . . fffff800`0089218c call    nt!RtlVirtualUnwind (fffff800`00891380)
..  ...  . .                   ; RtlVirtualUnwind(UNW_FLAG_NHANDLER,
..  ...  . .                   ;                  l_DispatcherContext.ImageBase,
..  ...  . .                   ;                  l_DispatcherContext.ControlPc,
..  ...  . .                   ;                  l_DispatcherContext.FunctionEntry,
..  ...  . .                   ;                  &l_Context,
..  ...  . .                   ;                  &l_pHandlerData,
..  ...  . .                   ;                  &l_pEstablisherFrame,
..  ...  . .                   ;                  NULL);
..  ...  . . fffff800`00892191 mov     rbx,qword ptr [rsp+88h]   ; rbx = l_DispatcherContext.EstablisherFrame
..  ...  . . fffff800`00892199 mov     rcx,qword ptr [rsp+0A0h]  ; rcx = l_DispatcherContext.LanguageHandler
..  ...  . . fffff800`008921a1 mov     r13,qword ptr [rsp+0A8h]  ; r13 = l_DispatcherContext.HandlerData
..  ...  . . fffff800`008921a9 mov     rax,qword ptr [rsp+0B0h]  ; rax = l_DispatcherContext.HistoryTable
..  ...  . . fffff800`008921b1 mov     r15d,dword ptr [rsp+0B8h] ; r15d = l_DispatcherContext.ScopeIndex
..  ...  . . fffff800`008921b9 mov     qword ptr [rsp+680h],rbx  ; l_pEstablisherFrame = l_DispatcherContext.EstablisherFrame
..  ...  . . fffff800`008921c1 mov     qword ptr [rsp+48h],rcx   ; l_pExceptionRoutine = l_DispatcherContext.LanguageHandler
..  ...  . . fffff800`008921c6 mov     qword ptr [rsp+58h],r13   ; l_pHandlerData = l_DispatcherContext.HandlerData
..  ...  . . fffff800`008921cb mov     qword ptr [rsp+6A8h],rax  ; pHistoryTable = l_DispatcherContext.HistoryTable
..  ...  . . fffff800`008921d3 or      esi,40h                   ; l_ExceptionFlags |= EXCEPTION_COLLIDED_UNWIND (40)
..  ...< . . fffff800`008921d6 jmp     nt!RtlUnwindEx+0x382 (fffff800`008921f2)
..  .... . . 
..  .... . . nt!RtlUnwindEx+0x368:
..  .>.. . . fffff800`008921d8 cmp     rbx,rbp ; cmp l_DispatcherContext.EstablisherFrame, pEstablisherFrame
..  . ..<. . fffff800`008921db je      nt!RtlUnwindEx+0x37d (fffff800`008921ed)
..  . .... . 
..  . .... . nt!RtlUnwindEx+0x36d:
..  . .... . fffff800`008921dd mov     rcx,qword ptr [rsp+48h] ; rcx = l_pExceptionRoutine
..  . .... . fffff800`008921e2 mov     rax,rdi                 ; rax = pOriginalContext
..  . .... . fffff800`008921e5 mov     rdi,r12                 ; rdi = &l_Context
..  . .... . fffff800`008921e8 mov     r12,rax                 ; r12 = pOriginalContext
..  . .v.. . fffff800`008921eb jmp     nt!RtlUnwindEx+0x382 (fffff800`008921f2)
..  . .... . 
..  . .... . nt!RtlUnwindEx+0x37d:
..  . ..>. . fffff800`008921ed mov     rcx,qword ptr [rsp+48h] ; rcx = l_pExceptionRoutine
..  . .. . . 
..  . .. . . nt!RtlUnwindEx+0x382:
..  . .> . . fffff800`008921f2 test    sil,40h  ; test sil, EXCEPTION_COLLIDED_UNWIND (40)
..  . .  < . fffff800`008921f6 jne     nt!RtlUnwindEx+0x200 (fffff800`00892070)
..  . .    . 
..  . .    . nt!RtlUnwindEx+0x38c:
..  . .    . fffff800`008921fc mov     r13,qword ptr [rsp+68h]  ; r13 = pOriginalContext->Rip
..  . .    . fffff800`00892201 mov     r15,qword ptr [rsp+688h] ; r15 = pJumpTargetIp
..  . .<   . fffff800`00892209 jmp     nt!RtlUnwindEx+0x3c7 (fffff800`00892237)
..  . ..   . 
..  . ..   . nt!RtlUnwindEx+0x39b:
..  > ..   . fffff800`0089220b cmp     rbx,rbp  ; cmp l_DispatcherContext.EstablisherFrame, pEstablisherFrame
..    .<   . fffff800`0089220e je      nt!RtlUnwindEx+0x3c7 (fffff800`00892237)
..    ..   . 
..    ..   . nt!RtlUnwindEx+0x3a0:
..    ..   . fffff800`00892210 mov     rax,rdi ; rax = &l_Context
..    ..   . fffff800`00892213 mov     rdi,r12 ; rdi = pOriginalContext
..    ..   . fffff800`00892216 mov     r12,rax ; r12 = &l_Context
..    .<   . fffff800`00892219 jmp     nt!RtlUnwindEx+0x3c7 (fffff800`00892237)
..    ..   . 
..    ..   . nt!RtlUnwindEx+0x3ab:
>.    ..   . fffff800`0089221b mov     rcx,qword ptr [rdi+98h]  ; rcx = pOriginalContext->Rsp
.    ..   . fffff800`00892222 mov     rax,qword ptr [rcx]      ; rax = pOriginalContext->Rsp[0]
.    ..   . fffff800`00892225 mov     qword ptr [rdi+0F8h],rax ; pOriginalContext->Rip = pOriginalContext->Rsp[0]
.    ..   . fffff800`0089222c lea     rax,[rcx+8]              ; rax = pOriginalContext->Rsp + 8
.    ..   . fffff800`00892230 mov     qword ptr [rdi+98h],rax  ; pOriginalContext->Rsp = pOriginalContext->Rsp + 8 (跳過返回值)
.    ..   .
.    ..   . nt!RtlUnwindEx+0x3c7:
.    .>   . fffff800`00892237 test    bl,7 ; 檢查 l_DispatcherContext.EstablisherFrame 是否對齊
.    .<   . fffff800`0089223a jne     nt!RtlUnwindEx+0x444 (fffff800`008922b4)
.    ..   . 
.    ..   . nt!RtlUnwindEx+0x3cc:
.    ..   . fffff800`0089223c cmp     rbx,qword ptr [rsp+50h] ; cmp l_DispatcherContext.EstablisherFrame, l_LowLimit
.    ..<  . fffff800`00892241 jb      nt!RtlUnwindEx+0x3da (fffff800`0089224a)
.    ...  . 
.    ...  . nt!RtlUnwindEx+0x3d3:
.    ...  . fffff800`00892243 cmp     rbx,qword ptr [rsp+40h] ; cmp l_DispatcherContext.EstablisherFrame, l_HighLimit
.    ...< . fffff800`00892248 jb      nt!RtlUnwindEx+0x414 (fffff800`00892284)
.    .... . 
.    .... . nt!RtlUnwindEx+0x3da:
.    ..>. . fffff800`0089224a mov     al,byte ptr gs:[20DEh] ; al = _KPCR->DpcRoutineActive
.    .. . . fffff800`00892252 test    al,al
.    .. .<. fffff800`00892254 jne     nt!RtlUnwindEx+0x43c (fffff800`008922ac)
.    .. ... 
.    .. ... nt!RtlUnwindEx+0x3e6:
.    .. ... fffff800`00892256 mov     rcx,qword ptr [rsp+40h] ; rcx = l_HighLimit
.    .. ... fffff800`0089225b mov     rax,qword ptr [rcx-28h] ; rax = l_KernelStackCtrl->Previous.StackBase
.    .. ... fffff800`0089225f test    rax,rax
.    .. .<. fffff800`00892262 je      nt!RtlUnwindEx+0x43c (fffff800`008922ac)
.    .. ... 
.    .. ... nt!RtlUnwindEx+0x3f4:
.    .. ... fffff800`00892264 mov     rdx,qword ptr [rcx-20h]  ; rdx = l_KernelStackCtrl->Previous.StackLimit
.    .. ... fffff800`00892268 mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
.    .. ... fffff800`00892270 cmp     rbx,rdx                  ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackLimit
.    .< ... fffff800`00892273 jb      nt!RtlUnwindEx+0x444 (fffff800`008922b4)
.    .. ... 
.    .. ... nt!RtlUnwindEx+0x405:
.    .. ... fffff800`00892275 cmp     rbx,rax ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackBase
.    .< ... fffff800`00892278 jae     nt!RtlUnwindEx+0x444 (fffff800`008922b4)
.    .. ... 
.    .. ... nt!RtlUnwindEx+0x40a:
.    .. ... fffff800`0089227a mov     qword ptr [rsp+50h],rdx ; l_LowLimit = l_KernelStackCtrl->Previous.StackLimit
.    .. ... fffff800`0089227f mov     qword ptr [rsp+40h],rax ; l_HighLimit = l_KernelStackCtrl->Previous.StackBase
.    .. ... 
.    .. ... nt!RtlUnwindEx+0x414:
.    .. >.. fffff800`00892284 cmp     rbx,rbp ; cmp l_pEstablisherFrame, pEstablisherFrame
.    .. <.. fffff800`00892287 je      nt!RtlUnwindEx+0x449 (fffff800`008922b9)
.    .. ... 
.    .. ... nt!RtlUnwindEx+0x419:
.    .. ... fffff800`00892289 mov     rax,qword ptr [rsp+6A8h] ; rax = pHistoryTable
.    .. ..^ fffff800`00892291 jmp     nt!RtlUnwindEx+0xf0 (fffff800`00891f60)
.    .. ..  
.    .. ..  nt!RtlUnwindEx+0x426:
.    >. ..  fffff800`00892296 mov     ecx,0C0000026h ; ecx = STATUS_INVALID_DISPOSITION
.     . ..  fffff800`0089229b call    nt!RtlRaiseStatus (fffff800`00889df0)
.     . ..  fffff800`008922a0 int     3
.     . ..  
.     . ..  nt!RtlUnwindEx+0x431:
>     . ..  fffff800`008922a1 mov     ecx,0C0000028h ; ecx = STATUS_BAD_STACK
       . ..  fffff800`008922a6 call    nt!RtlRaiseStatus (fffff800`00889df0)
       . ..  fffff800`008922ab int     3
       . ..  
       . ..  nt!RtlUnwindEx+0x43c:
       . .>  fffff800`008922ac mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
       . .   
       . .   nt!RtlUnwindEx+0x444:
       > .   fffff800`008922b4 cmp     rbx,rbp ; cmp l_pEstablisherFrame, pEstablisherFrame
<        .   fffff800`008922b7 jne     nt!RtlUnwindEx+0x479 (fffff800`008922e9)
.        .   
.        .   nt!RtlUnwindEx+0x449:
.        >   fffff800`008922b9 mov     rax,qword ptr [rsp+698h]  ; rax = ReturnValue
.            fffff800`008922c1 mov     rbx,qword ptr [rsp+690h]  ; rbx = pExceptionRecord
.            fffff800`008922c9 mov     qword ptr [rdi+78h],rax   ; pOriginalContext->Rax, ReturnValue
.            fffff800`008922cd cmp     dword ptr [rbx],80000029h ; cmp pExceptionRecord->ExceptionCode, STATUS_UNWIND_CONSOLIDATE
.<           fffff800`008922d3 je      nt!RtlUnwindEx+0x46c (fffff800`008922dc)
..           
..           nt!RtlUnwindEx+0x465:
..           fffff800`008922d5 mov     qword ptr [rdi+0F8h],r15 ; pOriginalContext->Rip, pJumpTargetIp
..           
..           nt!RtlUnwindEx+0x46c:
.>           fffff800`008922dc mov     rdx,rbx ; rdx = pExceptionRecord
.            fffff800`008922df mov     rcx,rdi ; rcx = pOriginalContext
.            fffff800`008922e2 call    nt!RtlRestoreContext (fffff800`008bd290)
.<           fffff800`008922e7 jmp     nt!RtlUnwindEx+0x4a0 (fffff800`00892310)
..           
..           nt!RtlUnwindEx+0x479:
>.           fffff800`008922e9 cmp     r13,qword ptr [rdi+0F8h] ; pOriginalContext->Rip
.<          fffff800`008922f0 jne     nt!RtlUnwindEx+0x48d (fffff800`008922fd)
..          
..          nt!RtlUnwindEx+0x482:
..          fffff800`008922f2 mov     ecx,0C00000FFh ; ecx = STATUS_BAD_FUNCTION_TABLE
..          fffff800`008922f7 call    nt!RtlRaiseStatus (fffff800`00889df0)
..          fffff800`008922fc int     3
..          
..          nt!RtlUnwindEx+0x48d:
.>          fffff800`008922fd mov     rcx,qword ptr [rsp+690h] ; rcx = pExceptionRecord
.           fffff800`00892305 xor     r8d,r8d                  ; r8d = 0
.           fffff800`00892308 mov     rdx,rdi                  ; rdx = pOriginalContext
.           fffff800`0089230b call    nt!ZwRaiseException (fffff800`008b9b80)
.                             ; ZwRaiseException(pExceptionRecord, pOriginalContext, FALSE);
.           
.           nt!RtlUnwindEx+0x4a0:
>           fffff800`00892310 mov     r15,qword ptr [rsp+638h]
             fffff800`00892318 mov     r14,qword ptr [rsp+640h]
             fffff800`00892320 mov     r13,qword ptr [rsp+648h]
             fffff800`00892328 mov     r12,qword ptr [rsp+650h]
             fffff800`00892330 mov     rdi,qword ptr [rsp+658h]
             fffff800`00892338 mov     rsi,qword ptr [rsp+660h]
             fffff800`00892340 mov     rbp,qword ptr [rsp+668h]
             fffff800`00892348 mov     rbx,qword ptr [rsp+670h]
             fffff800`00892350 add     rsp,678h
             fffff800`00892357 ret

參考資料
相關文章
相關標籤/搜索