異常篇——異常處理

寂靜的羽夏發表於2022-02-27

寫在前面

  此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。

  看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。


? 華麗的分割線 ?


概述

  之前我們介紹了異常的記錄。既然記錄好的異常,我肯定需要分發來處理它。對於異常有來自使用者模式的異常和核心態的異常。在我們之前學習中,它們的區別還是挺大的。無論是核心的異常,還是使用者的異常,異常處理的重心我們得放到KiDispatchException這個函式的分析上,先介紹相對簡單的核心異常處理流程。

核心異常處理

  對於核心異常處理,我們先看看KiDispatchException的處理流程:

  1. KeContextFromKframesTrapFrame備份到Context,為返回3環做準備。
  2. 判斷先前模式,0是核心呼叫反之是使用者層呼叫。
  3. 是否是第一次機會。
  4. 是否有核心偵錯程式。
  5. 如果沒有或者核心偵錯程式不處理。
  6. 呼叫RtlDispatchException,查詢並呼叫異常處理函式。
  7. 如果返回FALSE,再次判斷是否有核心偵錯程式,有呼叫,沒有直接藍屏。

  下面我們通過反彙編的形式過一遍這個流程:

push    390h
push    offset stru_401FA0
call    __SEH_prolog
mov     eax, ___security_cookie
mov     [ebp+security_cookie], eax
mov     esi, [ebp+ExceptionRecord]
mov     [ebp+var_ExceptionRecord], esi
mov     ecx, [ebp+ExceptionFrame]
mov     [ebp+var_ExceptionFrame], ecx
mov     ebx, [ebp+TrapFrame]
mov     [ebp+var_TrapFrame], ebx
db      3Eh
mov     eax, ds:0FFDFF020h
inc     [eax+_KPRCB.KeExceptionDispatchCount] ; 異常派發數加1
mov     [ebp+Context.ContextFlags], 10017h
cmp     [ebp+PreviousMode], 1
jz      short IsUserMode
cmp     _KdDebuggerEnabled, 0
jz      short KernelModeAndNoDBG

  函式開始開始賦值一波,然後判斷否是為3環,但我們是核心的,故不會跳轉,然後cmp _KdDebuggerEnabled, 0判斷偵錯程式是否開啟,具體將會在除錯篇進行講解,這裡就不多說了。
  這裡我們沒有偵錯程式,就會跳轉到下面的彙編程式碼:

KernelModeAndNoDBG:                     ; CODE XREF: KiDispatchException(x,x,x,x,x)+55↑j
                                        ; KiDispatchException(x,x,x,x,x)+68↑j
                lea     eax, [ebp+Context]
                push    eax             ; ContextRecord
                push    ecx             ; ExceptionFrame
                push    ebx             ; TrapFrame
                call    _KeContextFromKframes@12 ; KeContextFromKframes(x,x,x)
                mov     eax, [esi]
                cmp     eax, STATUS_BREAKPOINT
                jz      short IsBreakPoint
                cmp     eax, KI_EXCEPTION_ACCESS_VIOLATION
                jnz     short OtherExceptionProc
                mov     dword ptr [esi], STATUS_ACCESS_VIOLATION
                cmp     [ebp+PreviousMode], 1
                jnz     short OtherExceptionProc
                lea     eax, [ebp+Context]
                push    eax             ; Context
                push    esi             ; ExceptionRecord
                call    _KiCheckForAtlThunk@8 ; KiCheckForAtlThunk(x,x)
                test    al, al
                jnz     loc_425AFD
                cmp     byte ptr ds:0FFDF0280h, 1
                jnz     short OtherExceptionProc
                cmp     dword ptr [esi+14h], 8
                jnz     short OtherExceptionProc
                test    _KeNumberProcessors+0Fh, 40h
                jnz     short loc_425A30
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                mov     eax, [eax+_KTHREAD.ApcState.Process]
                test    [eax+_EPROCESS.Pcb.___u27.Flags._bf_0], 2
                jnz     short loc_425A30
                test    _KeNumberProcessors+0Fh, 80h
                jnz     short OtherExceptionProc
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                mov     eax, [eax+_KTHREAD.ApcState.Process]
                test    [eax+_EPROCESS.Pcb.___u27.Flags._bf_0], 1
                jnz     short OtherExceptionProc

  這裡就開始將TrapFrame備份到Context,首先判斷是否是int3斷點,如果是的話就會跳轉到如下程式碼:

IsBreakPoint:                           ; CODE XREF: KiDispatchException(x,x,x,x,x)+89↑j
                dec     [ebp+Context._Eip]

OtherExceptionProc:                     ; CODE XREF: KiDispatchException(x,x,x,x,x)+90↑j
                                        ; KiDispatchException(x,x,x,x,x)+9C↑j ...
                xor     edi, edi

DebugRoutine:                           ; CODE XREF: KiDispatchException(x,x,x,x,x)+F7↑j

  該程式碼就會修正EIP,繼續下面的流程。如果不是斷點,就判斷一系列的異常,並進行相應的處理,如果都不是就跳轉到所謂的OtherExceptionProc進行處理:

DebugRoutine:                           ; CODE XREF: KiDispatchException(x,x,x,x,x)+F7↑j
                cmp     [ebp+PreviousMode], 0
                jnz     short UserMode
                cmp     [ebp+FirstChance], 1
                jnz     short SecondChance
                mov     eax, _KiDebugRoutine
                cmp     eax, edi        ; edi = 0
                jz      short NoDebugRoutine
                push    edi             ; SecondChance
                push    edi             ; PreviousMode
                lea     ecx, [ebp+Context]
                push    ecx             ; ContextRecord
                push    esi             ; ExceptionRecord
                push    [ebp+var_ExceptionFrame] ; ExceptionFrame
                push    ebx             ; TrapFrame
                call    eax ; _KiDebugRoutine
                test    al, al
                jnz     loc_425AFD

NoDebugRoutine:                         ; CODE XREF: KiDispatchException(x,x,x,x,x)+114↑j
                lea     eax, [ebp+Context]
                push    eax             ; Context
                push    esi             ; ExceptionRecord
                call    _RtlDispatchException@8 ; RtlDispatchException(x,x)
                cmp     al, 1
                jz      short loc_425AFD

  這裡同理,我們沒有偵錯程式,就會呼叫RtlDispatchException這個函式。如果失敗,就會繼續執行下面的程式碼:

                 jz      short loc_425AFD

 SecondChance:                           ; CODE XREF: KiDispatchException(x,x,x,x,x)+10B↑j
                 mov     eax, _KiDebugRoutine
                 cmp     eax, edi
                 jz      NoDebugRoutine_0
                 push    1               ; SecondChance
                 push    edi             ; PreviousMode
                 lea     ecx, [ebp+Context]
                 push    ecx             ; ContextRecord
                 push    esi             ; ExceptionRecord
                 push    [ebp+var_ExceptionFrame] ; ExceptionFrame
                 push    ebx             ; TrapFrame
                 call    eax ; _KiDebugRoutine
                 test    al, al
                 jnz     short loc_425AFD
                 jmp     NoDebugRoutine_0

  就繼續判斷有沒有偵錯程式,但我這裡沒有,就會跳到下面的彙編程式碼:

NoDebugRoutine_0:                       ; CODE XREF: KiDispatchException(x,x,x,x,x)+149↑j
                                        ; KiDispatchException(x,x,x,x,x)+167↑j
                push    edi             ; BugCheckParameter4
                push    ebx             ; BugCheckParameter3
                push    [esi+_EXCEPTION_RECORD32.ExceptionAddress] ; BugCheckParameter2
                push    [esi+_EXCEPTION_RECORD32.ExceptionCode] ; BugCheckParameter1
                push    KERNEL_MODE_EXCEPTION_NOT_HANDLED ; BugCheckCode
                call    _KeBugCheckEx@20 ; KeBugCheckEx(x,x,x,x,x)

  上面的程式碼,就是所謂的藍屏。有關核心異常處理就介紹到這裡,具體的細節將會到總結與提升篇進行介紹。

使用者異常處理

  對於使用者異常處理,我們先看看KiDispatchException的處理流程:

  1. KeContextFromKframesTrapFrame備份到Context,為返回3環做準備。
  2. 判斷先前模式,0是核心呼叫反之是使用者層呼叫。
  3. 是否是第一次機會。
  4. 是否有核心偵錯程式。
  5. 傳送給3環除錯。

  有了核心異常的介紹,我就不贅述了,直接定位到關鍵程式碼:

inc     [eax+_KPRCB.KeExceptionDispatchCount] ; 異常派發數加1
mov     [ebp+Context.ContextFlags], 10017h
cmp     [ebp+PreviousMode], 1
jz      short IsUserMode
cmp     _KdDebuggerEnabled, 0
jz      short KernelModeAndNoDBG

  這裡我們是使用者的異常,所以會跳轉:

IsUserMode:                             ; CODE XREF: KiDispatchException(x,x,x,x,x)+4C↑j
                mov     [ebp+Context.ContextFlags], 1001Fh
                cmp     _KeI386XMMIPresent, 0
                jz      short KernelModeAndNoDBG
                mov     [ebp+Context.ContextFlags], 1003Fh

KernelModeAndNoDBG:                     ; CODE XREF: KiDispatchException(x,x,x,x,x)+55↑j
                                        ; KiDispatchException(x,x,x,x,x)+68↑j
                lea     eax, [ebp+Context]

  這裡就是賦值,不是我們關注的重點,我們繼續:

KernelModeAndNoDBG:                     ; CODE XREF: KiDispatchException(x,x,x,x,x)+55↑j
                                        ; KiDispatchException(x,x,x,x,x)+68↑j
                lea     eax, [ebp+Context]
                push    eax             ; ContextRecord
                push    ecx             ; ExceptionFrame
                push    ebx             ; TrapFrame
                call    _KeContextFromKframes@12 ; KeContextFromKframes(x,x,x)
                mov     eax, [esi]
                cmp     eax, STATUS_BREAKPOINT
                jz      short IsBreakPoint
                cmp     eax, KI_EXCEPTION_ACCESS_VIOLATION
                jnz     short OtherExceptionProc
                mov     dword ptr [esi], STATUS_ACCESS_VIOLATION
                cmp     [ebp+PreviousMode], 1
                jnz     short OtherExceptionProc
                lea     eax, [ebp+Context]
                push    eax             ; Context
                push    esi             ; ExceptionRecord
                call    _KiCheckForAtlThunk@8 ; KiCheckForAtlThunk(x,x)
                test    al, al
                jnz     loc_425AFD
                cmp     byte ptr ds:0FFDF0280h, 1
                jnz     short OtherExceptionProc
                cmp     dword ptr [esi+14h], 8
                jnz     short OtherExceptionProc
                test    _KeNumberProcessors+0Fh, 40h
                jnz     short loc_425A30
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                mov     eax, [eax+_KTHREAD.ApcState.Process]
                test    [eax+_EPROCESS.Pcb.___u27.Flags._bf_0], 2
                jnz     short loc_425A30
                test    _KeNumberProcessors+0Fh, 80h
                jnz     short OtherExceptionProc
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                mov     eax, [eax+_KTHREAD.ApcState.Process]
                test    [eax+_EPROCESS.Pcb.___u27.Flags._bf_0], 1
                jnz     short OtherExceptionProc

  由於我們是3環的,就會呼叫到KiCheckForAtlThunk這個函式作用如下:

This marks the beginning of an x86 ATL thunk. Control will branch here if the ATL thunk was built in memory marked no execute. If that is the case, then the thunk code will be emulated and control returned to the thread.

  看描述並不是我們關注的重點,繼續走到了我所標註的DebugRoutine

DebugRoutine:                           ; CODE XREF: KiDispatchException(x,x,x,x,x)+F7↑j
                cmp     [ebp+PreviousMode], 0
                jnz     short UserMode_0
                cmp     [ebp+FirstChance], 1
                jnz     short SecondChance
                mov     eax, _KiDebugRoutine
                cmp     eax, edi        ; edi = 0
                jz      short NoDebugRoutine
                push    edi             ; SecondChance
                push    edi             ; PreviousMode
                lea     ecx, [ebp+Context]
                push    ecx             ; ContextRecord
                push    esi             ; ExceptionRecord
                push    [ebp+var_ExceptionFrame] ; ExceptionFrame
                push    ebx             ; TrapFrame
                call    eax ; _KiDebugRoutine
                test    al, al
                jnz     loc_425AFD

  由於我們是使用者態,就直接跳轉到UserMode_0

UserMode_0:                             ; CODE XREF: KiDispatchException(x,x,x,x,x)+105↑j
                cmp     [ebp+FirstChance], 1
                jnz     SecondChance_0
                cmp     _KiDebugRoutine, edi
                jz      short NoDebugRoutine_1
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                mov     eax, [eax+_KTHREAD.ApcState.Process]
                cmp     [eax+_EPROCESS.DebugPort], edi
                jz      short NoDebugPort
                push    1
                lea     eax, [ebp+Context]
                push    eax
                push    esi
                call    _KdIsThisAKdTrap@12 ; KdIsThisAKdTrap(x,x,x)
                test    al, al
                jz      short NoDebugRoutine_1

NoDebugPort:                            ; CODE XREF: KiDispatchException(x,x,x,x,x)+18D↑j
                push    edi             ; SecondChance
                push    dword ptr [ebp+PreviousMode] ; PreviousMode
                lea     eax, [ebp+Context]
                push    eax             ; ContextRecord
                push    esi             ; ExceptionRecord
                push    [ebp+var_ExceptionFrame] ; ExceptionFrame
                push    ebx             ; TrapFrame
                call    _KiDebugRoutine
                test    al, al
                jz      short NoDebugRoutine_1

  由於我們不是除錯模式,會跳到NoDebugRoutine_1

NoDebugRoutine_1:                       ; CODE XREF: KiDispatchException(x,x,x,x,x)+17C↑j
                                        ; KiDispatchException(x,x,x,x,x)+1A0↑j ...
                push    edi             ; SecondChance
                push    1               ; DebugException
                push    esi             ; ExceptionRecord
                call    _DbgkForwardException@12 ; DbgkForwardException(x,x,x)
                test    al, al
                jnz     short EndProc
                mov     [ebp+var_SecondChance], edi

  DbgkForwardException就是向3環派發處理異常了,如果沒有偵錯程式的話,就會繼續往下走,最終會到如下程式碼:

mov     [ebx+_KTRAP_FRAME.SegFs], eax
and     [ebx+_KTRAP_FRAME.SegGs], 0
mov     eax, _KeUserExceptionDispatcher
mov     [ebx+_KTRAP_FRAME._Eip], eax
or      [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh

  這裡就開始修改KTRAP_FRAME開始回到3環進行處理,剩餘的具體細節的講解同樣將會在總結與提升進行。

下一篇

  異常篇—— VEH 與 SEH