SEH("Structured Exception Handling"),即結構化異常處理.是操作系統提供給程序設計者的強有力的處理程序錯
誤或異常的武器.在VISUAL C++中你或許已經熟悉了_try{} _finally{} 和_try{} _except {} 結構,這些並不是
編譯程序本身所固有的,本質上只不過是對windows內在提供的結構化異常處理的包裝.
發生異常時系統的處理順序(by Jeremy Gordon):
1.系統首先判斷異常是否應發送給目標程序的異常處理例程,如果決定應該發送,並且目標程序正在被調試,則系統掛起程序並向調試器發送EXCEPTION_DEBUG_EVENT消息.呵呵,這不是正好可以用來探測調試器的存在嗎?
2.如果你的程序沒有被調試或者調試器未能處理異常,系統就會繼續查找你是否安裝了線程相關的異常處理例程,如果你安裝了線程相關的異常處理例程,系統就把異常發送給你的程序seh處理例程,交由其處理.
3.每個線程相關的異常處理例程可以處理或者不處理這個異常,如果他不處理並且安裝了多個線程相關的異常處理例程, 可交由鏈起來的其他例程處理.
4.如果這些例程均選擇不處理異常,如果程序處於被調試狀態,操作系統仍會再次掛起程序通知debugger.
5.如果程序未處於被調試狀態或者debugger沒有能夠處理,並且你調用SetUnhandledExceptionFilter安裝了最後異常處理例程的話,系統轉向對它的調用.
6.如果你沒有安裝最後異常處理例程或者他沒有處理這個異常,系統會調用默認的系統處理程序,通常顯示一個對話框, 你可以選擇關閉或者最後將其附加到調試器上的調試按鈕.如果沒有調試器能被附加於其上或者調試器也處理不了,系統就調用ExitProcess終結程序.
7.不過在終結之前,系統仍然對發生異常的線程異常處理句柄來一次展開,這是線程異常處理例程最後清理的機會.
從上面來看,值得提到的一點是,如果一個程序應用SetUnhandledExceptionFilter來安裝異常處理函數,並且在此異常處理函數裏面完成該程序的功能代碼,並且通過int 3(單步執行)來引起異常,那麼如果此程序被調試的話並且處理了int 3異常的話,SetUnhandledExceptionFilter所安裝的異常處理函數就不會被調用,程序功能也因此會不正常,這也是反調試的一個有效手段.要對付這種手段需要修改windows的異常分發函數,強行讓windows總把異常分發給最後異常處理函數.
那麼安裝異常處理函數就有兩種方法,一種是SetUnhandledExceptionFilter,這樣的話異常處理函數總是被安裝到異常鏈的最後,並且具有一定的侷限性但是此種安裝方法能保證異常處理函數在進程有效的,此無論是哪個線程發生異常未能被處理,都會調用這個例程.另外一種方法是找到異常鏈直接把異常處理函數加入到異常鏈裏面去,這樣安裝的異常處理函數是線程有效的,只有本線程纔有用.
異常鏈的鏈表頭放在fs:[0]裏面在,每個異常結構如下:
prev dd ? ;前一個_EXCEPTION_REGISTRATION結構
handler dd ? ;異常處理例程入口....呵呵,現在明白該怎麼作了吧
_EXCEPTION_REGISTRATION ends
prev爲0xFFFFFFFF的時候表示是異常鏈的鏈尾了.可以使用以下的代碼完成安裝:
push offset perThread_Handler ;//異常處理函數
push fs:[0]
mov fs:[0],esp ;//建立SEH的基本ERR結構,如果不明白,就仔細研究一下吧
perThread_Handler有四個參數:pExcept:DWORD,pErr:DWORD,pContext:DWORD,pDispatch意義如下:
pExcept: --- EXCEPTION_RECORD結構的指針
pErr: --- 前面ERR結構的指針
pContext: --- CONTEXT結構的指針
pDispatch:---沒有發現有啥意義
EXCEPTION_RECORD以及CONTEXT結構如下:
==================EXCEPTION_RECORD STRUCT======================
EXCEPTION_RECORD STRUCT
ExceptionCode DWORD ? ;//異常碼
ExceptionFlags DWORD ? ;//異常標誌
pExceptionRecord DWORD ? ;//指向另外一個EXCEPTION_RECORD的指針
ExceptionAddress DWORD ? ;//異常發生的地址
NumberParameters DWORD ? ;//下面ExceptionInformation所含有的dword數目
ExceptionInformation DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)
EXCEPTION_RECORD ENDS ;//EXCEPTION_MAXIMUM_PARAMETERS ==15
===================CONTEXT STRUCT================================
CONTEXT STRUCT ; _
ContextFlags DWORD ? ; | +0
iDr0 DWORD ? ; | +4
iDr1 DWORD ? ; | +8
iDr2 DWORD ? ; >調試寄存器 +C
iDr3 DWORD ? ; | +10
iDr6 DWORD ? ; | +14
iDr7 DWORD ? ; _| +18
FloatSave FLOATING_SAVE_AREA <> ;浮點寄存器區 +1C~~~88h
regGs DWORD ? ;--| +8C
regFs DWORD ? ; |段寄存器 +90
regEs DWORD ? ; |/ +94
regDs DWORD ? ;--| +98
regEdi DWORD ? ;____________ +9C
regEsi DWORD ? ; | 通用 +A0
regEbx DWORD ? ; | 寄 +A4
regEdx DWORD ? ; | 存 +A8
regEcx DWORD ? ; | 器 +AC
regEax DWORD ? ;_______|___組_ +B0
regEbp DWORD ? ;++++++++++++++++ +B4
regEip DWORD ? ; |控制 +B8
regCs DWORD ? ; |寄存 +BC
regFlag DWORD ? ; |器組 +C0
regEsp DWORD ? ; | +C4
regSs DWORD ? ;++++++++++++++++ +C8
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS
而硬件斷點都需要利用iDr0-iDr3來實現,那麼perThread_Handler函數如下書寫就可以清除硬件斷點:
mov ebp,esp
push ebx
mov eax,dword ptr ss:[ebp+10] //得到Context結構
push ebp
xor ebx,ebx
mov dword ptr ds:[eax+4],ebx //分別清除iDr0-iDr3
mov dword ptr ds:[eax+8],ebx
mov dword ptr ds:[eax+C],ebx
mov dword ptr ds:[eax+10],ebx
mov eax,0
pop ebx
leave
retn 10