異常篇——異常記錄

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

寫在前面

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

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

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


? 華麗的分割線 ?


概述

  異常產生後,首先是要記錄異常資訊。異常分為CPU產生的異常和軟體模擬產生的異常。廢話不多說,下面我們開始介紹:

CPU 異常

  當出現CPU異常時,比如除以零的操作。我們就以該異常(中斷號為0)為例,出現除零異常之後,CPU就會在IDT查詢,執行中斷處理函式,那麼是如何記錄異常的呢?下面我們來簡單通過反彙編瞭解一下:

_KiTrap00       proc near               ; DATA XREF: INIT:_IDT↓o

var_2           = word ptr -2
arg_4           = dword ptr  8

; FUNCTION CHUNK AT .text:00467013 SIZE 00000021 BYTES

                push    0
                mov     word ptr [esp+2], 0
                push    ebp
                push    ebx
                push    esi
                push    edi
                push    fs
                mov     ebx, 30h ; '0'
                mov     fs, bx
                assume fs:nothing
                mov     ebx, large fs:_KPCR
                push    ebx
                sub     esp, 4
                push    eax
                push    ecx
                push    edx
                push    ds
                push    es
                push    gs
                mov     ax, 23h ; '#'
                sub     esp, 30h
                mov     ds, ax
                assume ds:nothing
                mov     es, ax
                assume es:nothing
                mov     ebp, esp
                test    [esp+_KTRAP_FRAME.EFlags], 20000h
                jnz     short V86_kit0_a

loc_4671DE:                             ; CODE XREF: V86_kit0_a+25↑j
                cld
                mov     ebx, [ebp+_KTRAP_FRAME._Ebp]
                mov     edi, [ebp+_KTRAP_FRAME._Eip]
                mov     [ebp+_KTRAP_FRAME.DbgArgPointer], edx
                mov     [ebp+_KTRAP_FRAME.DbgArgMark], 0BADB0D00h
                mov     [ebp+_KTRAP_FRAME.DbgEbp], ebx
                mov     [ebp+_KTRAP_FRAME.DbgEip], edi
                test    byte ptr ds:0FFDFF050h, 0FFh ; KPCR.DebugActive
                jnz     Dr_kit0_a

loc_467202:                             ; CODE XREF: Dr_kit0_a+10↑j
                                        ; Dr_kit0_a+7C↑j
                test    [ebp+_KTRAP_FRAME.EFlags], 20000h
                jnz     short isVM8086
                test    byte ptr [ebp+_KTRAP_FRAME.SegCs], 1
                jz      short isKernelMode
                cmp     word ptr [ebp+_KTRAP_FRAME.SegCs], 1Bh
                jnz     short loc_467235

isKernelMode:                           ; CODE XREF: _KiTrap00+73↑j
                sti
                push    ebp
                call    _Ki386CheckDivideByZeroTrap@4 ; Ki386CheckDivideByZeroTrap(x)
                mov     ebx, [ebp+_KTRAP_FRAME._Eip]
                jmp     loc_467013
; ---------------------------------------------------------------------------

loc_467227:                             ; CODE XREF: _KiTrap00+A9↓j
                                        ; _KiTrap00+B4↓j
                sti
                mov     ebx, [ebp+_KTRAP_FRAME._Eip]
                mov     eax, STATUS_INTEGER_DIVIDE_BY_ZERO
                jmp     loc_467013
; ---------------------------------------------------------------------------

loc_467235:                             ; CODE XREF: _KiTrap00+7A↑j
                mov     ebx, ds:0FFDFF124h
                mov     ebx, [ebx+_KTHREAD.ApcState.Process]
                cmp     [ebx+_EPROCESS.VdmObjects], 0
                jz      short loc_467227

isVM8086:                               ; CODE XREF: _KiTrap00+6D↑j
                push    0
                call    _Ki386VdmReflectException_A@4 ; Ki386VdmReflectException_A(x)
                or      al, al
                jz      short loc_467227
                jmp     Kei386EoiHelper@0 ; Kei386EoiHelper()
_KiTrap00       endp

  可以看出最後執行到如下程式碼:

loc_467227:                             ; CODE XREF: _KiTrap00+A9↓j
                                        ; _KiTrap00+B4↓j
                sti
                mov     ebx, [ebp+_KTRAP_FRAME._Eip]
                mov     eax, STATUS_INTEGER_DIVIDE_BY_ZERO
                jmp     loc_467013

  最後跳到loc_467013,我們來看看它的彙編程式碼:

loc_467013:                             ; CODE XREF: _KiTrap00+86↓j
                                        ; _KiTrap00+94↓j ...
                xor     ecx, ecx
                call    CommonDispatchException

  發現它會呼叫CommonDispatchException函式,這個函式就是用來派發異常的,我們繼續分析流程:

CommonDispatchException proc near       ; CODE XREF: _KiTrap00-187↑p
                                        ; _KiTrap00-17B↑p ...

var_50          = dword ptr -50h
var_4C          = dword ptr -4Ch
var_48          = dword ptr -48h
var_44          = dword ptr -44h
var_40          = dword ptr -40h
var_3C          = byte ptr -3Ch

                sub     esp, EXCEPTION_RECORD_LENGTH
                mov     [esp+_EXCEPTION_RECORD32.ExceptionCode], eax
                xor     eax, eax
                mov     [esp+_EXCEPTION_RECORD32.ExceptionFlags], eax
                mov     [esp+_EXCEPTION_RECORD32.ExceptionRecord], eax
                mov     [esp+_EXCEPTION_RECORD32.ExceptionAddress], ebx
                mov     [esp+_EXCEPTION_RECORD32.NumberParameters], ecx
                cmp     ecx, 0
                jz      short loc_46705D
                lea     ebx, [esp+_EXCEPTION_RECORD32.ExceptionInformation]
                mov     [ebx], edx
                mov     [ebx+4], esi
                mov     [ebx+8], edi

loc_46705D:                             ; CODE XREF: CommonDispatchException+1B↑j
                mov     ecx, esp
                test    [ebp+_KTRAP_FRAME.EFlags], 20000h
                jz      short loc_46706F
                mov     eax, 0FFFFh
                jmp     short loc_467072
; ---------------------------------------------------------------------------

loc_46706F:                             ; CODE XREF: CommonDispatchException+32↑j
                mov     eax, [ebp+_KTRAP_FRAME.SegCs]

loc_467072:                             ; CODE XREF: CommonDispatchException+39↑j
                and     eax, 1
                push    1               ; FirstChance
                push    eax             ; PreviousMode
                push    ebp             ; TrapFrame
                push    0               ; ExceptionFrame
                push    ecx             ; ExceptionRecord
                call    _KiDispatchException@20 ; KiDispatchException(x,x,x,x,x)
                mov     esp, ebp
                jmp     Kei386EoiHelper@0 ; Kei386EoiHelper()
CommonDispatchException endp

  可以看出它會提升堆疊提供EXCEPTION_RECORD結構體,儲存一些異常資訊,這就是CPU的異常記錄流程。如下就是該結構體的成員:

type struct _EXCEPTION_RECORD
{
    DWORD ExceptionCode;        //異常程式碼
    DWORD ExceptionFlags;       //異常狀態
    struct _EXCEPTION_RECORD* ExceptionRecord;  //下一個異常
    PVOID ExceptionAddress;     //異常發生地址
    DWORD NumberParameters;     //附加引數個數
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //附加引數指標
}  

  不同的CPU異常對應不同的IDT索引,與此同時Windows自己定義了不同的錯誤碼標識不同的錯誤:

異常篇——異常記錄

  CommonDispatchException最後呼叫KiDispatchException函式真正地派發異常,有關CPU異常記錄就介紹到這裡。

軟體模擬異常

  對於軟體模擬的異常,我們以C++的為例,如下是測試程式碼:

#include "stdafx.h"

int main(int argc, char* argv[])
{
    throw;
    return 0;
}

  程式碼很簡單,我們生成的反彙編如下:

#include "stdafx.h"

int main(int argc, char* argv[])
{
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,40h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-40h]
0040101C   mov         ecx,10h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
    throw;
00401028   push        0
0040102A   push        0
0040102C   call        __CxxThrowException@8 (00401090)
    return 0;
}
00401031   pop         edi
00401032   pop         esi
00401033   pop         ebx
00401034   add         esp,40h
00401037   cmp         ebp,esp
00401039   call        __chkesp (00401050)
0040103E   mov         esp,ebp
00401040   pop         ebp
00401041   ret

  可以發現,丟擲一個異常是用CxxThrowException函式實現的,我們檢視它的反彙編:

__CxxThrowException@8:
00401090   push        ebp
00401091   mov         ebp,esp
00401093   sub         esp,20h
00401096   push        esi
00401097   push        edi
00401098   mov         ecx,8
0040109D   mov         esi,offset string "The value of ESP was not properl"...+0E0h (00422110)
004010A2   lea         edi,[ebp-20h]
004010A5   rep movs    dword ptr [edi],dword ptr [esi]
004010A7   mov         eax,dword ptr [ebp+8]
004010AA   mov         dword ptr [ebp-8],eax
004010AD   mov         ecx,dword ptr [ebp+0Ch]
004010B0   mov         dword ptr [ebp-4],ecx
004010B3   lea         edx,[ebp-0Ch]
004010B6   push        edx
004010B7   mov         eax,dword ptr [ebp-10h]
004010BA   push        eax
004010BB   mov         ecx,dword ptr [ebp-1Ch]
004010BE   push        ecx
004010BF   mov         edx,dword ptr [ebp-20h]
004010C2   push        edx
004010C3   call        dword ptr [__imp__RaiseException@16 (0042a154)]
004010C9   pop         edi
004010CA   pop         esi
004010CB   mov         esp,ebp
004010CD   pop         ebp
004010CE   ret         8

  該函式又是呼叫RaiseException函式實現功能,傳入的異常號為E06D7363,注意不同的編譯器模擬異常實現,這個異常號是不同的。我們繼續檢視RaiseException函式,由於反編譯的結果十分好,為了節省篇幅虛擬碼如下:

void __stdcall RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments)
{
  DWORD v4; // ecx
  struct _EXCEPTION_RECORD ExceptionRecord; // [esp+4h] [ebp-50h] BYREF

  ExceptionRecord.ExceptionRecord = 0;
  ExceptionRecord.ExceptionCode = dwExceptionCode;
  ExceptionRecord.ExceptionFlags = dwExceptionFlags & 1;
  ExceptionRecord.ExceptionAddress = RaiseException;
  if ( lpArguments )
  {
    v4 = nNumberOfArguments;
    if ( nNumberOfArguments > 0xF )
      v4 = 15;
    ExceptionRecord.NumberParameters = v4;
    if ( v4 )
      qmemcpy(ExceptionRecord.ExceptionInformation, lpArguments, 4 * v4);
  }
  else
  {
    ExceptionRecord.NumberParameters = 0;
  }
  RtlRaiseException(&ExceptionRecord);
}

  這個函式只是構造了一個結構體用來記錄,最後又會呼叫RtlRaiseException函式實現功能,其彙編程式碼如下:

; void __stdcall RtlRaiseException(PEXCEPTION_RECORD ExceptionRecord)
                public _RtlRaiseException@4
_RtlRaiseException@4 proc near          ; CODE XREF: RtlRaiseException(x)+B3↓p
                                        ; RtlDispatchException(x,x)+D3↓p ...

var_2F4         = dword ptr -2F4h
var_2F0         = dword ptr -2F0h
var_2EC         = dword ptr -2ECh
var_2E4         = dword ptr -2E4h
Context         = CONTEXT ptr -2D4h
var_4           = dword ptr -4
var_s0          = dword ptr  0
ExceptionRecord = dword ptr  8
arg_4           = byte ptr  0Ch

                push    ebp
                mov     ebp, esp
                pushf
                sub     esp, 2D0h
                mov     [ebp+Context._Eax], eax
                mov     [ebp+Context._Ecx], ecx
                mov     eax, [ebp+ExceptionRecord]
                mov     ecx, [ebp+4]    ; 獲取呼叫該函式的地址
                mov     [eax+_EXCEPTION_RECORD.ExceptionAddress], ecx
                lea     eax, [ebp+Context]
                mov     [eax+_CONTEXT._Eip], ecx
                mov     [eax+_CONTEXT._Ebx], ebx
                mov     [eax+_CONTEXT._Edx], edx
                mov     [eax+_CONTEXT._Esi], esi
                mov     [eax+_CONTEXT._Edi], edi
                lea     ecx, [ebp+0Ch]  ; 呼叫該函式之前的堆疊棧頂
                mov     [eax+_CONTEXT._Esp], ecx
                mov     ecx, [ebp+0]    ; 獲取儲存的 ebp
                mov     [eax+_CONTEXT._Ebp], ecx
                mov     ecx, [ebp-4]    ; 獲取儲存的 eflag
                mov     [eax+_CONTEXT.EFlags], ecx
                mov     word ptr [eax+_CONTEXT.SegCs], cs
                mov     word ptr [eax+_CONTEXT.SegDs], ds
                mov     word ptr [eax+_CONTEXT.SegEs], es
                mov     word ptr [eax+_CONTEXT.SegFs], fs
                mov     word ptr [eax+_CONTEXT.SegGs], gs
                mov     word ptr [eax+_CONTEXT.SegSs], ss
                mov     [eax+_CONTEXT.ContextFlags], 10007h
                push    1               ; SearchFrames
                push    eax             ; Context
                push    [ebp+ExceptionRecord] ; ExceptionRecord
                call    _ZwRaiseException@12 ; ZwRaiseException(x,x,x)
                sub     esp, 20h
                mov     [esp], eax
                mov     dword ptr [esp+4], 1
                mov     dword ptr [esp+10h], 0
                mov     eax, [ebp+ExceptionRecord]
                mov     [esp+8], eax
                mov     eax, esp
                push    eax             ; ExceptionRecord
                call    _RtlRaiseException@4 ; RtlRaiseException(x)
_RtlRaiseException@4 endp

  然後又呼叫ZwRaiseException,通過系統呼叫最終呼叫NtRaiseException實現:

; NTSTATUS __stdcall NtRaiseException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context, BOOLEAN SearchFrames)
_NtRaiseException@12 proc near          ; DATA XREF: .text:0042AE60↑o

var_s0          = dword ptr  0
ExceptionRecord = dword ptr  8
Context         = dword ptr  0Ch
FirstChance     = byte ptr  10h
arg_34          = dword ptr  3Ch

                push    ebp
                mov     ebx, ds:0FFDFF124h
                mov     edx, [ebp+3Ch]
                mov     [ebx+_KTHREAD.TrapFrame], edx
                mov     ebp, esp
                mov     ebx, [ebp+0]
                mov     edx, dword ptr [ebp+FirstChance]
                mov     eax, [ebx+_KTRAP_FRAME.ExceptionList]
                mov     ecx, [ebp+Context]
                mov     ds:0FFDFF000h, eax
                mov     eax, [ebp+ExceptionRecord]
                push    edx             ; FirstChance
                push    ebx             ; TrapFrame
                push    0               ; ExceptionFrame
                push    ecx             ; ContextRecord
                push    eax             ; ExceptionRecord
                call    _KiRaiseException@20 ; KiRaiseException(x,x,x,x,x)
                pop     ebp
                mov     esp, ebp
                or      eax, eax
                jnz     _KiServiceExit
                jmp     _KiServiceExit2
_NtRaiseException@12 endp

  這個函式又會呼叫KiRaiseException實現:

NTSTATUS __stdcall KiRaiseException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord, _KTRAP_FRAME *ExceptionFrame, _KTRAP_FRAME *TrapFrame, BOOLEAN FirstChance)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  ExceptionRecord_1 = ExceptionRecord;
  ContextFrame = ContextRecord;
  ExceptionFrame_1 = ExceptionFrame;
  TrapFrame_1 = TrapFrame;
  ms_exc.registration.TryLevel = 0;
  currentThread = KeGetCurrentThread();
  LOBYTE(PreviousMode) = currentThread->PreviousMode;
  if ( !PreviousMode )
  {
LABEL_19:
    ms_exc.registration.TryLevel = -1;
    KeContextToKframes(TrapFrame_1, ExceptionFrame_1, ContextFrame, ContextFrame->ContextFlags, PreviousMode);
    HIBYTE(ExceptionRecord_1->ExceptionCode) &= 0xEFu;
    KiDispatchException(ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, PreviousMode, FirstChance);
    goto LABEL_20;
  }
  if ( (ContextRecord & 3) != 0 )
    ExRaiseDatatypeMisalignment();
  if ( ContextFrame >= MmUserProbeAddress )
    *MmUserProbeAddress = 0;
  if ( (ExceptionRecord & 3) != 0 )
    ExRaiseDatatypeMisalignment();
  if ( ExceptionRecord >= MmUserProbeAddress )
    *MmUserProbeAddress = 0;
  NumberParameters = ExceptionRecord->NumberParameters;
  NumberParameters_1 = NumberParameters;
  if ( NumberParameters <= 0xF )
  {
    v13 = 4 * NumberParameters + 20;
    if ( 4 * NumberParameters != 4294967276 )
    {
      if ( (ExceptionRecord & 3) != 0 )
        ExRaiseDatatypeMisalignment();
      v7 = &ExceptionRecord->ExceptionInformation[NumberParameters];
      if ( v7 < ExceptionRecord || v7 > MmUserProbeAddress )
        ExRaiseAccessViolation();
    }
    qmemcpy(&context, ContextFrame, sizeof(context));
    qmemcpy(&exRecord, ExceptionRecord, v13);
    ContextFrame = &context;
    ExceptionRecord_1 = &exRecord;
    v10 = &exRecord;
    exRecord.NumberParameters = NumberParameters;
    goto LABEL_19;
  }
  ms_exc.registration.TryLevel = -1;
LABEL_20:
  xHalReferenceHandler(v19);
  return result;
}

  這個函式呼叫KiDispatchException進行異常派發。軟體丟擲模擬的異常記錄流程就到此結束了。

小結

  最後我們簡單的用流程圖表示一下CPU異常和軟體模擬異常的記錄流程:

graph TD 1[CPU 異常] --> 2[查詢 IDT , 生成 ExceptionRecord 記錄] --> 3[CommonExceptionDispatch] --> 4[KiDispatchException] a[軟體模擬異常] --> b[CxxThrowException] --> c[kernel32.RaiseException] --> d[Ntdll.RtlRaiseException] --> e[Ntdll.ZwRaiseException] --> f[Nt.NtRaiseException] --> g[Nt.KiRaiseException] --> 4

下一篇

  異常篇——異常處理

相關文章