軟件調試(9) - 深入探討Windows結構化異常(SEH)底層實現

r3/r0一樣, FS:[0] 保存著SEH的鏈表頭

應用層:
TEB與TIB結構
TEB中的起始處是指向TIB的地址,TIB第一個字段位exceptionList即為鏈表表頭

內核層
KPCR與TIB結構
KPCR->TIB


TIB的ExceptionList字段
-------------------------------------------------------
該字段位於TIB的起始處
typedef struct _EXCEPTION_REGISTERATION_RECORD{
struct _EXCEPTION_REGISTERATION_RECORD *next; //0xFFFFFFFF代表最後一個節點
ULONG handler;           //異常處理器的地址(需符合seh函數原形)
}
EXCEPTION_DISPOSITION SehHandler(_EXCEPTION_RECORD *exceptionRecord, //處理的異常
void * EstablisherFrame,  //異常函數的EBP
_CONTEXT *contextRecord,  //線程上下文
void * DispatcherContext  //分發函數
)
TIB自身不在棧中,而SEH鏈表則是存在棧中


登記異常函數
-------------------------------------------------------
push seh_handler  //處理函數地址   棧空間:
push FS:[0]   //舊的鏈表節點處理器地址  FS:[0]    
move FS:[0],ESP   //登記新結構    SEH_HANDLER


.....

mov esp,dword ptr fs:[0]  //保存下一個結構地址
pop dword ptr fs:[0]  //將下一個結構的地址變成鏈表頭


用戶態版本RtlDispatchException
-------------------------------------------------------
(內核中的異常分發:修改EIP到R3的KiUserExceptionDispatcher->)RtlDispatchException->先調用RtlCallVectoredExceptionHandlers嘗試尋找VEH->如果沒有則調用RtlpGetRegisterationHead依次遍歷鏈表 直至-1

透過ExecuteHandler內部調用pfnHandler所指向的異常處理函數(即鏈表元素)

scopetable
--------------------------------------------------------
範圍表是一個數組,用於標誌__try __except() 範圍
數組元素為以下結構:
struct scopetable_entry
{
 DWORD previousTryLevel; //之前一個Trylevel
 FARPROC lpfnFilter;     //過濾表達式起始地址
 FARPROC lpfnHandler;    //異常處理函數地址
}

TryLevel
--------------------------------------------------------
多層Try時使用

__except_handler3按照trylevel來尋找scopetable_entry結構,找到後調用結構中的lpnFilter所指定的過濾表達式


_except_handler3工作:
---------------------------------------------------------
位於__try{}中的代碼發生異常時,異常分發函數便會調用_except_handler3這樣的處理函數,他的工作如下:
他的調用路徑為: RtlDispatchException-> ExecuteHandler -> ExecuteHandler2 -> _except_handler3 (透過_EXCEPTION_REGISTRATION結構) ->尋找scopetable的異常塊地址 __except{}

1. 將第二個參數pRegsitrationRecord從系統默認的EXCEPTION_REGISTRATION_RECORD結構強制轉化為包含擴展字段的_EXCEPTION_REGISTRATION

2. 先從pRegistration結構提取trylevel字段,傳給局部變量,按照它的值從scopetable索引出scopetable_entry結構

3. 從scopetable提出lpfnFilter字段,如不為空,調用函數->即評估過濾表達式,若為空跳到第五步

4. 判斷是否為exception_continue_search

5. 判斷scopetable_entry結構的previousTryLevel是否-1,不等於則用previousTryLevel交給局部變量,再重覆跳到第2步;否則跳到第六步

6. 返回DISPOSITION_CONTINUE_SEARCH, 讓系統繼續尋找其他異常處理器

兩者不同之處:
------------------------------------------------------------
SEH _Try{} _except{};

1. 由劃一的_except_handler3 來處理所有SEH異常,
2. 先壓入trylevel和scopetable指針 用作找出異常處理代碼, 棧中會形成以下結構
_struct _EXCEPTION_REGISTRATION{
 struct _EXCEPTION_REGISTRATION *prev; //ebp-10
 void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT,PEXCEPTION_RECORD); //ebp-c
 struct scoptable_entry *scoptable; //ebp-8
 int trylevel;    //ebp-4
 int ebp;    //ebp
}


總結:
兩種方法製造SEH
1. 直接保存fs:[0],再插入新處理函數,指向新節點
2. 使用try..except方法,透過統一處理函數_except_handler3處理

分別: 前者由ExecuteHandle2->直接執行我們添加的異常處理函數, 後者為集中到_except_handler3中由scopetable決定調用哪一個級別(trylevel)的異常處理函數

RtlDispatchException(第一次異常處理)查找失敗會調用NtRaiseException引發第二輪異常處理,成功則調用NtContiue繼續讓程序運行,兩者都不會返回到KiUserExceptionDispatcher



參考:Windows軟件調試

Comments

Popular posts from this blog

Android Kernel Development - Kernel compilation and Hello World

How does Nested-Virtualization works?

Understanding ACPI and Device Tree