??此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
??看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。
?
?? 华丽的分割线 ??
?
??异常产生后,首先是要记录异常信息。异常分为CPU产生的异常和软件模拟产生的异常。废话不多说,下面我们开始介绍:
??当出现CPU异常时,比如除以零的操作。我们就以该异常(中断号为0)为例,出现除零异常之后,CPU就会在IDT查找,执行中断处理函数,那么是如何记录异常的呢?下面我们来简单通过反汇编了解一下:
_KiTrap00 proc near ; DATA XREF: INIT:_IDT↓ovar_2 = word ptr -2arg_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_aloc_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_aloc_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_467235isKernelMode: ; 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_467227isVM8086: ; 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 -50hvar_4C = dword ptr -4Chvar_48 = dword ptr -48hvar_44 = dword ptr -44hvar_40 = dword ptr -40hvar_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], ediloc_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 ebp00401011 mov ebp,esp00401013 sub esp,40h00401016 push ebx00401017 push esi00401018 push edi00401019 lea edi,[ebp-40h]0040101C mov ecx,10h00401021 mov eax,0CCCCCCCCh00401026 rep stos dword ptr [edi] throw;00401028 push 00040102A push 00040102C call __CxxThrowException@8 (00401090) return 0;}00401031 pop edi00401032 pop esi00401033 pop ebx00401034 add esp,40h00401037 cmp ebp,esp00401039 call __chkesp (00401050)0040103E mov esp,ebp00401040 pop ebp00401041 ret??可以发现,抛出一个异常是用CxxThrowException函数实现的,我们查看它的反汇编:
__CxxThrowException@8:00401090 push ebp00401091 mov ebp,esp00401093 sub esp,20h00401096 push esi00401097 push edi00401098 mov ecx,80040109D 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],eax004010AD mov ecx,dword ptr [ebp+0Ch]004010B0 mov dword ptr [ebp-4],ecx004010B3 lea edx,[ebp-0Ch]004010B6 push edx004010B7 mov eax,dword ptr [ebp-10h]004010BA push eax004010BB mov ecx,dword ptr [ebp-1Ch]004010BE push ecx004010BF mov edx,dword ptr [ebp-20h]004010C2 push edx004010C3 call dword ptr [__imp__RaiseException@16 (0042a154)]004010C9 pop edi004010CA pop esi004010CB mov esp,ebp004010CD pop ebp004010CE 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 -2F4hvar_2F0 = dword ptr -2F0hvar_2EC = dword ptr -2EChvar_2E4 = dword ptr -2E4hContext = CONTEXT ptr -2D4hvar_4 = dword ptr -4var_s0 = dword ptr 0ExceptionRecord = dword ptr 8arg_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↑ovar_s0 = dword ptr 0ExceptionRecord = dword ptr 8Context = dword ptr 0ChFirstChance = byte ptr 10harg_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异常和软件模拟异常的记录流程:
??异常篇——异常处理
