编译器扩展SEH(2)
文章目錄
- 問題
- 自動掛入的異常處理函數
- __try __except嵌套,重復
- 原始的`_EXCEPTION_REGISTRATION_RECORD`結構體
- 編譯器拓展的_EXCEPTION_REGISTRATION_RECORD結構體
- scopetable_entry結構體
- scopetable_entry舉例子:
- 第一個:
- 第二個:
- 第三個:
- 第四個:
- _except_handler4執行過程
- 解釋:
- trylevel
問題
我們在制作SEH鏈的時候,每增加一個,就得進行一次如下代碼
__asm{ mov eax,FS:[0] mov temp,eax lea ecx,myException mov FS:[0],ecx -------------->修改FS:[0]掛入鏈表 }那么當編譯進行自行生成時,會不會也是每增加一個try,except,然后就壓入一個_EXCEPTION_REGISTRATION_RECORD結構體呢?接下來我們來看看如下代碼會壓入多少_EXCEPTION_REGISTRATION_RECORD結構體
#include<Windows.h> #include<iostream>int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo) {pExceptionInfo->ContextRecord->Ecx = 1;return EXCEPTION_CONTINUE_EXECUTION; }void TestException() {__try { __try {}__except (EXCEPTION_EXECUTE_HANDLER) {printf("異常處理函數Y");}}__except (EXCEPTION_EXECUTE_HANDLER) {printf("異常處理函數X");}__try {__try { }__except (GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {printf("異常處理函數Y");}}__except (ExceptFilter(GetExceptionInformation())) {printf("異常處理函數Z");}}int main() {TestException(); }反匯編代碼如下:
看了一下,就只有一個_EXCEPTION_REGISTRATION_RECORD結構體,只不過這個結構體跟我們手動掛的原始_EXCEPTION_REGISTRATION_RECORD結構體不太一樣,接下來我們來解釋一下。
自動掛入的異常處理函數
_except_handler4(編譯器固定,那么異常處理函數名也就固定;隨著編譯器改變而改變)
__try __except嵌套,重復
每個使用__try __except的函數,不管其內部嵌套或反復使用多少__try, __except,都只注冊一遍,即只將一個_EXCEPTION_REGISTRATION_RECORD掛入當前線程的異常鏈表中(對于遞歸函數,每一次調用都會創建一個_EXCEPTION_REGISTRATION_RECORD,并掛入線程的異常鏈表中)。
原始的_EXCEPTION_REGISTRATION_RECORD結構體
typedef struct _EXCEPTION_REGISTRATION_RECORD{ struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEOTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD;然而如果編譯器的_EXCEPTION_REGISTRATION_RECORD結構體是這樣的話,那么壓入一個_EXCEPTION_REGISTRATION_RECORD結構體就能辦到__try __except重復嵌套的話,那是根本不可能的,所以需要自行拓展
編譯器拓展的_EXCEPTION_REGISTRATION_RECORD結構體
typedef struct _EXCEPTION_REGISTRATION_RECORD{ struct _EXCEPTION_REGISTRATION_RECORD *Next; void (*void)(PEXCEPTION_RECORD,PEXCEPTION_REGISTRATION, PCONTEXT,PEXCEPTION_RECORD); struct scopetable_entry *scopetable; int trylevel; int _ebp; }這里trylevel暫時等于-2,scopetable地址等于 0x1990F0h,接下來看看這兩值有何作用
scopetable_entry結構體
scopetable_entry成員分析struct scopetable_entry{ DWORD previousTryLevel //上一個try{}結構編號 PDWRD IpfnFilter //過濾函數的起始地址(小括號里面的常量或者表達式或者函數) PDWRD IpfnHandler //異常處理程序的地址 }scopetable[0].previousTryLevel=-2
scopetable[0]. IpfnFilter=過濾函數1
scopetable[0]. IpfnHandler=異常函數1
scopetable[1].previousTryLevel=0
scopetable[1]. IpfnFilter=過濾函數2
scopetable[1]. IpfnHandler=異常函數2
scopetable[2].previousTryLevel=-1
scopetable[2]. IpfnFilter=過濾函數3
scopetable[2]. IpfnHandler=異常函數3
scopetable[3].previousTryLevel=2
scopetable[3]. IpfnFilter=過濾函數4
scopetable[3]. IpfnHandler=異常函數4
scopetable_entry舉例子:
隨便找出一個來舉個例子:
第一個:
上一個try結構編號是-2,過濾函數的起始地址是0x19532F,異常處理程序的地址:0x195335
它把過濾函數當成一個代碼來執行,然后執行完后直接返回相應的返回結果。
緊接著,進入了異常處理程序代碼
第二個:
上一個try結構編號是0,過濾函數的起始地址是0x195309,異常處理程序的地址:0x19530f
它把過濾函數當成一個代碼來執行,然后執行完后直接返回相應的返回結果。
緊接著,進入了異常處理程序代碼
第三個:
上一個try結構編號是-1,過濾函數的起始地址是0x1953b9,異常處理程序的地址:0x1953c6
過濾函數中有一個函數,進行跳轉之后,再次進行返回,然后執行完后直接返回相應的返回結果。
緊接著,進入了異常處理程序代碼
第四個:
上一個try結構編號是2(編號為2,即上一個try位于第三個try),過濾函數的起始地址是0x195363,異常處理程序的地址:0x195399
還是把過濾函數中的當成幾段代碼,執行完之后再返回,最后進行返回值判斷,根據返回值判斷下一步如何進行
緊接著,進入了異常處理程序代碼
_except_handler4執行過程
1.CPU檢測異常 查中斷表執行處理函數 CommonDispatchException----->
KiDispatchException---->KiUserExceptionDispatcher------->RtlDispatchException—
—>VEH---->SEH
解釋:
CPU發現異常,根據異常類型來查找中斷表,查中斷表找到具體的異常處理函數,執行處理函數之后,處理函數會調用CommonDispatchException,CommonDispatchException把出異常的那些相關信息存儲起來(哪里出異常,什么類型的異常等),然后調用KiDispatchException(異常分發處理函數),這個函數會判斷這個異常是0環異常還是3環異常,如果是3環異常,那么會修正EIP,把Trap_Frame指向KiUserExceptionDispatcher,然后當線程回到三環時,會從KiUserExceptionDispatcher開始執行,這個函數第一件事就是查RtlDispatchException,來查找異常處理函數在哪里?先找VEH,然后再找SEH(fs:[0]))
當如果我們自己拋出的異常,執行流程除了前三步(CPU檢測異常 查中斷表執行處理函數 CommonDispatchException),后面的一模一樣
2.執行_except_handler4函數
EXCEPTION_EXECUTE_HANDLER(1)執行except代碼
EXCEPTION_CONTINUE_SEARCH(0)尋找下一個
EXCEPTION_CONTINUE_EXECUTION(-1)重新執行
trylevel
trylevel表示代碼正在執行在第幾個try中(它是時刻正在變化的,進入一個try或者走出一個try, trylevel都會被改變)
trylevel初始化為-1,此時的話也就代表未處于try中
當后面開始進入try時
進入第一個try,trylevel 值被賦為0;進入第二個try,trylevel 值被賦為1;退出第二個try,trylevel 值被賦為0
進入第三個try,trylevel 值被賦為2;進入第四個try,trylevel 值被賦為3;退出第四個try,trylevel 值被賦為2
當退出所有的try時,trylevel值變為-1
這樣的話trylevel結合scopetable數組就可以準確找到出錯位置。
注意:
如果出異常后,就不會往下執行了,然后異常就被CPU捕獲了,通過各種判斷,最終轉到_except_handler4,然后
1.EXCEPTION_EXECUTE_HANDLER(1)執行except代碼
2.EXCEPTION_CONTINUE_SEARCH(0)尋找下一個
3.EXCEPTION_CONTINUE_EXECUTION(-1)重新執行
總結
以上是生活随笔為你收集整理的编译器扩展SEH(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编译器扩展SEH(1)
- 下一篇: EASYHOOK逆向寒假生涯(20/10