• 异常篇——异常记录


    写在前面

      此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

    你如果是从中间插过来看的,请仔细阅读 羽夏看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异常和软件模拟异常的记录流程:

    CPU 异常
    查找 IDT , 生成 ExceptionRecord 记录
    CommonExceptionDispatch
    KiDispatchException
    软件模拟异常
    CxxThrowException
    kernel32.RaiseException
    Ntdll.RtlRaiseException
    Ntdll.ZwRaiseException
    Nt.NtRaiseException
    Nt.KiRaiseException

    下一篇

      异常篇——异常处理

  • 相关阅读:
    Pandas导出美化技巧,让你的Excel更出众
    基于vue项目的代码优化
    SPARKSQL3.0-DataFrameAPI与spark.sql()区别源码分析
    一篇文章说清 webpack、vite、vue-cli、create-vue 的区别
    【Git】Git基础命令操作速记
    rabbitmq入门、springboot集成rabbitmq
    Json“牵手”易贝商品详情数据方法,易贝商品详情API接口,易贝API申请指南
    CVE-2022-30190 Follina Office RCE分析【附自定义word钓鱼模板POC】
    如今的入职背调到底有多刺激?
    C/C++图书管理系统
  • 原文地址:https://www.cnblogs.com/wingsummer/p/15940667.html