Windows XP 异常处理(一)异常采集

ʕ •̀ o •́ ʔ

1 再谈中断异常

1.1 中断异常区别

Intel 提供的clisti指令:

cli:将EFLAGS寄存器的IF = 0,屏蔽可屏蔽的硬件中断

sti:将EFLAGS寄存器的IF = 1,响应可屏蔽的硬件中断。

Intel手册3A 2.3:控制寄存器CR4中的CPL、IOPL和VME标志决定着IF标志是可否可以由指令CLI、STTI、POPF、POPFD和IRET修改(可修改IF位的指令)。

58.png

中断和异常的根本差异:异常来自于 CPU 本身,是 CPU 主动产生的。而中断来自于外部设备,是中断源发起的,CPU 是被动的

《Windows内核原理与实现》中说硬件也会产生异常,但是我们还是说异常都是CPU产生的,在《软件调试2 卷1-3.1.2》有这样的说明:

对于机器检查异常,虽然有时是因为外部设备通过设置 CPU 的特殊管脚(BUSCHK#或BINIT#和 MCERR#)触发的,但是从产生角度来看,仍然是 CPU 检测到管脚信号然后产生异常的,所以机器检查异常仍然是在 CPU 内部产生的

INT N指令产生的是异常不是中断(不叫软件中断)。

1.2 异常的分类

异常分类 是否可恢复 EIP 进入异常时保存的地址 举例
错误类 导致错误类异常的情况通常可以被纠正,而且一旦纠正后,程序可以无损失地恢复执行 产生错误异常的地址,异常处理后急促执行该地址的指令。 缺页异常
陷阱类 异常处理后可恢复,CPU报告异常时,导致异常的指令已经执行完毕 产生异常的一下条即将要执行的指令 int 3
中止类 不允许恢复 不确定 蓝屏

1.png

手册2A,int 3指令可为:0xcc/0xcd 3

2.png

1.3 中断和异常列表

向量号 助记符 类型 描述 来源
0 #DE 错误 除零错误 DIV 和 IDIV指令
1 #DB 错误/陷阱 调试异常,用于软件调试 任何代码或数据引用
2 / 中断 NMI中断 不可屏蔽的外部中断
3 #BP 陷阱 断点 INT 3 指令
4 #OF 陷阱 溢出 INTO 指令
5 #BR 错误 数组越界 BOUND 指令
6 #UD 错误 无效指令(没有定义的指令) UD2 指令(奔腾 Pro CPU 引入此指令)或任何保留的指令
7 #NM 错误 数学协处理器不存在或不可用 浮点或 WAIT/FWAIT 指令
8 #DF 中止 双重错误 (Double Fault) 任何可能产生异常的指令、不可屏蔽中断或可屏蔽中断
9 #MF 错误 向协处理器传送操作数时检测到页错误(Page Faults)或段不存在,自从 486 把数学协处理器集成到 CPU 内部后,本异常便保留不用 浮点指令
10 #TS 错误 无效 TSS 任务切换或访问 TSS
11 #NP 错误 段不存在 加载段寄存器或访问系统段
12 #SS 错误 栈段错误 栈操作或加载 SS 寄存器
13 #GP 错误 通用保护(GP)异常,如果一个操作违反了保护模式下的规定,而且该情况不属于其他异常,则 CPU 便产生通用保护异常,很多时候也被翻译为一般保护异常 任何内存引用和保护性检查
14 #PF 错误 页错误 任何内存引用
15 保留 / / /
16 #MF 错误 浮点错误 浮点或 WAIT/FWAIT 指令
17 #AC 错误 对齐检查 对内存中数据的引用(486CPU 引入)
18 #MC 中止 机器检查(Machine Check) 错误代码和来源与型号有关(奔腾CPU 引入)
19 #XF 错误 SIMD 浮点异常 SIMD 浮点指令(奔腾I CPU 引入)
20~31 保留 / / /
32~255 用户定义中断 中断 可屏蔽中断 来自INTR的外部中断或 INT n指令

1.4 错误码ErrCode与异常代码

除了缺页异常(《软件调试2 卷1-第二章》)的错误码外,其余错误码(32位)格式如下。

3.png

其各个位域的含义如下:

  • EXT(External Event)[位0]:
    • 1,外部事件导致该异常。
  • IDT(Descriptor Location)[位1]:描述符位置。
    • 1,索引指向的是 IDT 表中的门描述符;
    • 0,索引指向的是 LDT 或 GDT中的描述符。
  • TI(GDT/LDT)[位2]:仅当 IDT 位为0时有效。
    • 0,素引部分指向的 GDT 中的描述符。
    • 1,索引部分指向的LDT 中的段或门描述符。
  • 段选择子索引域:表示与该错误有关的描述符在 IDT、LDT 或 GDT 表中的索引。

函数Ki386CheckDivideByZeroTrap???

⚠️注意:错误码和异常代码是不一样的。

错误码ErrCode,通过分析函数kiTrap00可以知道,仅用了低16位,即低2字节,高16位清零。

异常代码EXCEPTION_RECORD.ExceptionCode,是一个32 位的整数,在 WinBase.h 中进行定义,它的格式是 Windows 系统的状态代码(在NtStatus.h 中定义)的别名(也就是状态码和异常代码是同一套数字)。例如:

1
2
#define EXCEPTION_BREAKPOINT STATUS_BREAKPOINT		//WinBase.h NtStatus.h
#define EXCEPTION_SINGLE_STEP STATUS_SINGLE_STEP

如下为异常代码(来源列括号的数字为中断号):

6.png

1.5 中断/异常的优先级

1.6 KEXCEPTION_FRAME

许多内核函数都会使用到一个PKEXCEPTION_FRAME指针定义的参数,如KiRaiseException参数三:

1
2
3
4
5
6
7
NTSTATUS KiRaiseException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT ContextRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN BOOLEAN FirstChance
);

看一下源码,在x86平台下,实际上就是一个KTRAP_FRAME指针。

1
typedef KTRAP_FRAME *PKEXCEPTION_FRAME;

在《软件调试 P283》中提到:PKEXCEPTION_FRAME 定义的异常结构对于 x86平台总是为空

2 异常记录–CPU异常

异常产生后,首先是要 记录异常信息(异常的类型、异常发生的位置等),然后要寻找异常的处理函数,我们称为 异常的分发,最后找到异常处理函数并调用,我们称为异常处理

处理异常的顺序:异常记录 --> 异常分发 --> 异常处理。

异常的分类

  • CPU产生的异常
  • 软件模拟产生的异常

这两种异常最后都是用统一的方式进行记录分发的。

2.1 CPU产生的异常

处理顺序

① CPU指令检测到异常(例:除0),异常一定是先由CPU发现的。
② 查IDT表,执行中断处理函数,不同的异常调用不同的中断处理函数。
③ CommonDispatchException。
④ KiDispatchException。

本节以IDT的0号中断为例,分析除零异常。(div为无符号除法,idiv为有符号除法)

4.png

如上图,运行代码后报错,错误码为0xC00000094,描述是Integer Divide by Zero。

  1. 查看其反汇编代码:

    5.png

  2. Intel手册2A对该指令的描述第一句为:

    1
    2
    3
    4
    5
    //IDIV-Signed Divide
    IF SRC = 0
    THEN #DE; (* Divide error *)
    FI;
    ......

    也就是当 CPU 在执行 IDIV 指令时,首先会检查源操作数(除数)是否等于零,如果等于零,那么就产生除零异常。#DE 是除零异常的简短记号。

  3. 查看中断和异常列表#DEint 0异常,在IDA中找到对应的ISR为_KiTrap00

2.2 _KiTrap00分析

_KiTrap00函数执行流程:
① 保存现场(将寄存器堆栈位置等信息保存到_Trap_Frame中)
② 调用CommonDispatchException函数(分析参数EAX,EBX中的值)

明明CPU检测到的错误,本身就在内核,怎么还会有 _KTRAP_FRAME 结构?
很简单,用户也会走这个函数,我们在反汇编代码分析时,发现其还有一条路线是处理用户层的,如果是用户层直接进来,当然要进行线程上下文切换。

⚠️注意:异常的分发并不是在_KiTrap00函数里直接处理的。

_KiTrap00分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
*(PUSHORT)(KTRAP_FRAME.ErrCode+2) = 0;	//仅使用低16位

//从 KTRAP_FRAME.ErrCode 开始保存原来3环的环境 (省略)

if(KTRAP_FRAME.Efalgs == 0x20000) //V86模式
{
KTRAP_FRAME.SegFs = KTRAP_FRAME.V86Fs;
KTRAP_FRAME.SegGs = KTRAP_FRAME.V86Gs;
KTRAP_FRAME.SegEs = KTRAP_FRAME.V86Es;
KTRAP_FRAME.SegDs = KTRAP_FRAME.V86Ds;
}
__asm cld; //cld:DF=0,ESI、EDI的地址指针自动增加
//std:DF=1,ESI、EDI的地址指针自动减小

KTRAP_FRAME.DbgArgPointer = edx; //edx指向3环的参数
KTRAP_FRAME.DbgArgMark = 0xBADB0D00;
KTRAP_FRAME.DbgEbp = KTRAP_FRAME._Ebp;
KTRAP_FRAME.DbgEip = KTRAP_FRAME._Eip;
if(KPCR.DebugActive == -1) //DebugActive != -1 表示当前线程正在被调试
{
__asm jmp Dr_kit0_a; //进入调试,以后学了调试再分析
}

if(KTRAP_FRAME.Efalgs == 0x20000) //V86模式
{
Ki386VdmReflectException_A();
... //先不管
}

//3环 CS == 0x23,所以这里是 3环除零异常
if(KTRAP_FRAME.SegCs != 0 && KTRAP_FRAME.SegCs != 0x1B) //PreviousMode != 0
{
if(CurrentThread->ApcState.Process->VdmObjects == 0)
{
__asm
{
sti;
eax = 0xC0000094;
ebx = KTRAP_FRAME._Eip; //错误类异常,此处是发生异常的地址
jmp loc_46B017;
}
}
}else //KTRAP_FRAME.SegCs == 0 || KTRAP_FRAME.SegCs != 0x1B,0环除零异常
{
__asm sti;
Ki386CheckDivideByZeroTrap(&KTRAP_FRAME); //判断是否是除零异常,如果是就获取异常码
//eax = 异常码 = 0xC0000094
__asm ebx = KTRAP_FRAME._Eip; //中断门由CPU自动保存
__asm jmp loc_46B017;
}

/*

jmp loc_46B017前相关寄存器的值:
eax = 0xC0000094
ebx = KTRAP_FRAME._Eip
esp = ebp 指向 &KTRAP_FRAME
ecx = 附加参数个数 = 此处为0

*/

40.png

观察loc_46B017

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:0046B017 loc_46B017:                             
.text:0046B017
.text:0046B017 xor ecx, ecx //ecx:表示参数的个数
.text:0046B019 call CommonDispatchException
.text:0046B01E loc_46B01E: // CODE XREF: _KiTrap07+1C0↓j
.text:0046B01E // _KiTrap07+1DB↓j ...
.text:0046B01E xor edx, edx
.text:0046B020 mov ecx, 1
.text:0046B025 call CommonDispatchException
.text:0046B02A loc_46B02A: // CODE XREF: _KiTrap07+1CA↓j
.text:0046B02A // _KiTrap07+2E8↓j ...
.text:0046B02A xor edx, edx
.text:0046B02C
.text:0046B02C loc_46B02C: // CODE XREF: _KiTrap0C+AC↓j
.text:0046B02C // _KiTrap0C+10D↓j ...
.text:0046B02C mov ecx, 2
.text:0046B031 call CommonDispatchException
.text:0046B036 mov edi, edi

可以看到,这里即将要去调用函数CommonDispatchException

这里有个问题需要注意:从_KiTrap00函数jmp loc_46B017,在loc_46B017中是call CommonDispatchException,也就是说明函数处理完异常后应该是是去执行KTRAP_FRAME._Eip,而不是返回到这里继续执行,不然单看这里的代码返回来继续执行的话逻辑上根本不对。

2.3 _EXCEPTION_RECORD结构

CommonDispatchException 函数在自己的函数堆栈上构造一个 _EXCEPTION_RECORD 结构体初始化赋值。

Windows 系统使用 _EXCEPTION_RECORD 结构来描述(记录)异常。

_EXCEPTION_RECORD结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct _EXCEPTION_RECORD {
NTSTATUS ExceptionCode;
ULONG ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
ULONG NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

kd> dt _EXCEPTION_RECORD -v
nt!_EXCEPTION_RECORD
struct _EXCEPTION_RECORD, 6 elements, 0x50 bytes
+0x000 ExceptionCode : Int4B //异常代码
+0x004 ExceptionFlags : Uint4B //异常标志
+0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD //相关的下一个异常
+0x00c ExceptionAddress : Ptr32 Void //异常发生的地址
+0x010 NumberParameters : Uint4B //参数数组中的元素个数
+0x014 ExceptionInformation : [15] Uint4B //参数数组
  • ExceptionCode:异常代码。是一个32 位的整数,其格式是 Windows 系统的状态代码格式,NtStatus.h 中包含了已经定义的所有状态代码,在 WinBase.h 中可以看到异常代码只是状态代码的别名,例如:

    1
    2
    #define EXCEPTION_BREAKPOINT STATUS_BREAKPOINT		//WinBase.h NtStatus.h
    #define EXCEPTION_SINGLE_STEP STATUS_SINGLE_STEP
  • ExceptionFlags:异常标志。用来记录异常标志,它的每一位代表一种标志,目前已经定义的标志位如下。

    • 0,CPU产生的异常
    • EH_NONCONTINUABLE:1,该异常不可恢复继续执行。
    • EH_UNWINDING:2,当因为执行栈展开而调用异常处理函数时,会设置此标志。
    • EH_EXIT_UNWIND:4,也是用于栈展开,较少使用。
    • BH_STACK_INVALID:8,当检测到栈错误时,设置此标志。
    • EH_NESTED_CALL :0x10,嵌套异常(在处理异常时,再次出现异常)(第24章)。
      EH_NONCONTINUABLE 位用来表示该异常是否可以恢复继续执行,如果试图恢复运行一个不可继续的异常,便会导致EXCEPTION_NONCONTINUABLE_EXCEPTION 异常。
  • ExceptionRecord:指针指向与该异常有关的另一个异常记录,如果没有相关的异常,那么这个指针便为空。

  • ExceptionAddress:字段用来记录异常地址,错误类异常( Fault),ExceptionAddress 的值是导致异常的那条指令的地址。陷阱类异常(Trap),ExceptionAddress 的值是导致异常指令的下一条指令的地址。

  • NumberParameters:是附加参数的个数,即 ExceptionInformation 数组中包含的有效参数个数,该结构最多允许存储 15 个附加参数。

2.4 CommonDispatchException

对于 CPU 异常,KiTrapxx 例程在完成针对本异常的特别动作后,通常会调用 ConmonDispatchException 函数,并通过寄存器将如下信息传递给这个函数。

  • 将唯—标识该异常的一个异常代码(表 11-2)放入 EAX 寄存器。
  • 将导致异常的指令地址放入 EBX 寄存器。
  • 将其他信息作为附带参数(最多3个)分别放入 EDX(参数1)、ESI(参数 2)和 EDI(参数3)寄存器,并将参数个数放入 ECX 寄存器。

ComnenDispetchException 流程:它会在栈中分配一个 EXCEPTION_RECORD 结构,并把以上异常信息存储到该结构中。在准备好这个结构后,它会调用内核中的 KiDispatchException 函数来分发异常。

ComnenDispetchException 函数分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
eax = 0xC0000094
ebx = KTRAP_FRAME._Eip
esp = ebp 指向 &KTRAP_FRAME
ecx = 参数个数 = 此处为0
*/
EXCEPTION_RECORD ExcepRecord = {0};
ExcepRecord.ExceptionCode = eax; //0xC0000094
ExcepRecord.ExceptionFlags = 0; //此处是0,CPU产生的异常
ExcepRecord.ExceptionRecord = 0;
ExcepRecord.ExceptionAddress = ebx; //KTRAP_FRAME._Eip,产生异常的地址
ExcepRecord.NumberParameters = ecx; //此处是0,CommonDispatchException参数个数

if(ExcepRecord.NumberParameters == 0)
{
if(KTRAP_FRAME.EFlags == 0x20000) //V86
{
__asm mov eax, 0xffff;
}else
{
__asm mov eax, KTRAP_FRAME.SegCs;
}

__asm and eax, 1; //保护模式:eax == 1/0(用户/内核)。V86:eax == 0x1
KiDispatchException(&ExcepRecord,0,&KTRAP_FRAME,eax,1); //可以看到 eax 是先前模式,V86先前模式为1

__asm
{
mov esp, ebp; //esp = ebp 指向&KTRAP_FRAME
jmp Kei386EoiHelper@0;
}
}

/*
VOID __stdcall KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)
*/

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
text:0046B038 // =============== S U B R O U T I N E =======================================
.text:0046B038
.text:0046B038 // ======
.text:0046B038 // CommonDispatchException
.text:0046B038 //
.text:0046B038 // ebp、esp 指向TrapFrame
.text:0046B038 // eax:ExceptionCode
.text:0046B038 // ebx:ExceptionAddress
.text:0046B038 // ecx:0
.text:0046B038 // edi:KTRAP_FRAME._Eip
.text:0046B038 //
.text:0046B038 // kd> dt _EXCEPTION_RECORD
.text:0046B038 // nt!_EXCEPTION_RECORD
.text:0046B038 // +0x000 ExceptionCode : Int4B
.text:0046B038 // +0x004 ExceptionFlags : Uint4B
.text:0046B038 // +0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD
.text:0046B038 // +0x00c ExceptionAddress : Ptr32 Void
.text:0046B038 // +0x010 NumberParameters : Uint4B
.text:0046B038 // +0x014 ExceptionInformation : [15] Uint4B
.text:0046B038 // ======
.text:0046B038 //
.text:0046B038
.text:0046B038 CommonDispatchException proc near // CODE XREF: _KiTrap00-187↑p
.text:0046B038 // _KiTrap00-17B↑p ...
.text:0046B038
.text:0046B038 ExceptionCode = dword ptr -50h
.text:0046B038 ExceptionFlags = dword ptr -4Ch
.text:0046B038 ExceptionRecord = dword ptr -48h
.text:0046B038 ExceptionAddress= dword ptr -44h
.text:0046B038 NumberParameters= dword ptr -40h
.text:0046B038 ExceptionInformation= byte ptr -3Ch
.text:0046B038
.text:0046B038 sub esp, 50h
.text:0046B03B mov [esp+50h+ExceptionCode], eax // size(_EXCEPTION_RECORD) == 0x50
.text:0046B03E xor eax, eax
.text:0046B040 mov [esp+50h+ExceptionFlags], eax
.text:0046B044 mov [esp+50h+ExceptionRecord], eax
.text:0046B048 mov [esp+50h+ExceptionAddress], ebx
.text:0046B04C mov [esp+50h+NumberParameters], ecx // ecx == 0
.text:0046B050 cmp ecx, 0
.text:0046B053 jz short loc_46B061
.text:0046B055 lea ebx, [esp+50h+ExceptionInformation]
.text:0046B059 mov [ebx], edx // edx指向3环的参数
.text:0046B05B mov [ebx+4], esi // esi值不知道是啥
.text:0046B05E mov [ebx+8], edi // edi:KTRAP_FRAME._Eip
.text:0046B061
.text:0046B061 loc_46B061: // CODE XREF: CommonDispatchException+1B↑j
.text:0046B061 mov ecx, esp
.text:0046B063 test [ebp+_KTRAP_FRAME.EFlags], 20000h
.text:0046B06A jz short loc_46B073 // 非V86模式
.text:0046B06C mov eax, 0FFFFh
.text:0046B071 jmp short loc_46B076
.text:0046B073 // ---------------------------------------------------------------------------
.text:0046B073
.text:0046B073 loc_46B073: // CODE XREF: CommonDispatchException+32↑j
.text:0046B073 mov eax, [ebp+_KTRAP_FRAME.SegCs] // 非V86模式
.text:0046B076
.text:0046B076 loc_46B076: // CODE XREF: CommonDispatchException+39↑j
.text:0046B076 and eax, 1
.text:0046B079 push 1 // char
.text:0046B07B push eax // int
.text:0046B07C push ebp // BugCheckParameter3
.text:0046B07D push 0 // int
.text:0046B07F push ecx // ExceptionRecord
.text:0046B080 call _KiDispatchException@20 // KiDispatchException(x,x,x,x,x)
.text:0046B085 mov esp, ebp
.text:0046B087 jmp Kei386EoiHelper@0 // Kei386EoiHelper()
.text:0046B087 CommonDispatchException endp

3 异常记录–软件模拟异常

在应用层,在代码中可以抛出异常,不同的语言、不同的编译器抛出的异常的方式不同。但是都会调用Kernel32.dll模块的RaiseException()函数。

软件异常产生的异常代码是和编译器相关的,如:

  • Visual C++ 程序抛出的异常:Exceptioncode 参数固定为 0xe06d7363(对应的 ASCI 码为.msc)。
  • .NET 程序抛出的异常(CLR异常):异常代码固定为 0xe0434f4d(对应的 ASCI 码为.COM)。

在内核函数 KiRaiseException函数中,会把软件异常相关 ExceptionRecord 记录的异常代码的最高位清0,以便把软件产生的异常与 CPU 异常区分开来。

3.1 模拟软件产生软件异常

使用VC 6++编译器编译运行如下代码:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main()
{
throw 1;

getchar();
return 0;
}

程序会产生一个异常,然后调用编译器提供的函数 __CxxThrowException,该函数又会调用 Kernel32.dll RaiseException 函数。

函数 __CxxThrowException 是编译器提供的,因为我在 Kernel32.dllntdll.dll 中都没有找到它。

该函数主要功能
① 该函数实参一为关键字throw抛出的值1,实参二为一个地址。但是在本函数中都没有用到。
② 在函数栈里面开辟了32字节空间。
③ 将数值 0xE06D7363、0x1、0x0、0x0、0x3、0x19930520、0x0、0x0 共32字节复制到栈里面。
④ 调用 RaiseException 函数。

7.png

3.2 RaiseException分析

函数 RaiseException:在调用线程中引发异常。该函数是 Kernel32.dll 的导出函数。

1
2
3
4
5
6
void RaiseException(
[in] DWORD dwExceptionCode, //异常代码
[in] DWORD dwExceptionFlags, //异常标志,零表示连续异常
[in] DWORD nNumberOfArguments, //lpArguments数组中的参数个数
[in] const ULONG_PTR *lpArguments //保存参数的数组
);

该函数的功能很简单,这里就直接贴源码了。

主要功能:

① 在函数堆栈里创建一个 EXCEPTION_RECORD 结构变量,并给结构成员赋值。

  • 异常标志的处理:

    1
    2
    //#define EXCEPTION_NONCONTINUABLE 0x1
    ExceptionRecord.ExceptionFlags = dwExceptionFlags & EXCEPTION_NONCONTINUABLE;
  • 参数个数大于 15时,将参数个数设置为15。

  • 异常地址的处理:为函数 RaiseException 的地址,但是该值进入函数RtlRaiseException后还会更改。

    1
    ExceptionRecord.ExceptionAddress = (PVOID)RaiseException;

② 调用 RtlRaiseException 函数。

函数源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//#define APIENTRY _stdcall
VOID APIENTRY RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
CONST ULONG_PTR *lpArguments
)
/*++
Routine Description:
Raising an exception causes the exception dispatcher to go through
its search for an exception handler. This includes debugger
notification, frame based handler searching, and system default
actions.
Arguments:
dwExceptionCode - Supplies the exception code of the exception being
raised. This value may be obtained in exception filters and in
exception handlers by calling GetExceptionCode.
dwExceptionFlags - Supplies a set of flags associated with the exception.
dwExceptionFlags Flags:
EXCEPTION_NONCONTINUABLE - The exception is non-continuable.
Returning EXCEPTION_CONTINUE_EXECUTION from an exception
marked in this way causes the STATUS_NONCONTINUABLE_EXCEPTION exception.
nNumberOfArguments - Supplies the number of arguments associated
with the exception. This value may not exceed
EXCEPTION_MAXIMUM_PARAMETERS. This parameter is ignored if
lpArguments is NULL.
lpArguments - An optional parameter, that if present supplies the arguments for the exception.
Return Value: None.
--*/
{
EXCEPTION_RECORD ExceptionRecord;
ULONG n;
PULONG_PTR s,d;
ExceptionRecord.ExceptionCode = (DWORD)dwExceptionCode;
ExceptionRecord.ExceptionFlags = dwExceptionFlags & EXCEPTION_NONCONTINUABLE;
ExceptionRecord.ExceptionRecord = NULL;
ExceptionRecord.ExceptionAddress = (PVOID)RaiseException;
if ( ARGUMENT_PRESENT(lpArguments) ) {
n = nNumberOfArguments;
if ( n > EXCEPTION_MAXIMUM_PARAMETERS ) {
n = EXCEPTION_MAXIMUM_PARAMETERS;
}
ExceptionRecord.NumberParameters = n;
s = (PULONG_PTR)lpArguments;
d = ExceptionRecord.ExceptionInformation;
while(n--){
*d++ = *s++;
}
}
else {
ExceptionRecord.NumberParameters = 0;
}
RtlRaiseException(&ExceptionRecord);
}

3.3 RtlRaiseException分析

在本函数中会在函数堆栈上构造一个 CONTEXT 结构,然后将相关寄存器的值保存起来。

特别需要注意的是:在本函数中会将 EXCEPTION_RECORD 结构的成员 ExceptionAddress 修改为返回到函数 RaiseException 的地址。即:

  • 在函数RaiseException 中:EXCEPTION_RECORD.ExceptionAddress = RaiseException
  • 在函数RtlRaiseException中:EXCEPTION_RECORD.ExceptionAddress = RaiseException返回地址

8_1.png

9.png

3.4 NtRaiseException

在3环,RtlRaiseException 函数调用3环的 NtRaiseException 然后通过快速调用进入到0环,接着执行0环的 NtRaiseException 函数。

函数原型如下:

1
2
3
4
5
NTSTATUS NTAPI NtRaiseException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT ContextRecord,
IN BOOLEAN FirstChance
);

NtRaiseException函数功能:

  1. 设置CurrentThread->TrapFrame

    1
    2
    .text:0046DEBC                 mov     edx, [ebp+_KTRAP_FRAME._Edx]
    .text:0046DEBF mov [ebx+_KTHREAD.TrapFrame], edx
  2. 设置KPCR.ExceptionList = KTRAP_FRAME.ExceptionList

    1
    2
    .text:0046DECD                 mov     eax, [ebx+_KTRAP_FRAME.ExceptionList]
    .text:0046DED3 mov large fs:_KPCR, eax
  3. 调用函数KiRaiseException

调用函数KiRaiseException,实参传递如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS NTAPI NtRaiseException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT ContextRecord,
IN BOOLEAN FirstChance
);

push FirstChance; //FirstChance,NtRaiseException的参数3
push &TrapFrame;
push 0; //ExceptionFrame
push &Context; //3环的Context地址,NtRaiseException的参数2
push &ExceptionRecord; //3环的EXCEPTION_RECORD地址,NtRaiseException的参数1

call KiRaiseException(x,x,x,x,x);

3.5 KiRaiseException

函数KiRaiseException主要功能:

  1. 如果先前模式为UserMode,则会判断参数ContextRecordExceptionFrame(这两个参数的地址一直都是3环空间的地址)地址是否已经4字节对齐及使用ProbeForRead判断是否可读。
  2. 如果ExceptionRecord->NumberParameters > 0xF就返回STATUS_INVALID_PARAMETER
  3. 将3环的CONTEXTEXCEPTION_RECORD数据拷贝到当前函数的堆栈
  4. 调用函数KeContextToKframes,使用当前堆栈中CONTEXT,根据CONTEXT.ContextFlags将相关寄存器复制到TrapFrame
  5. ExceptionCode最高位清零,以标志是从软件异常来的,不是CPU异常
  6. 调用函数KiDispatchException(x,x,x,x,x)

KiRaiseException函数逆向分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
CONTEXT ContextRecord2;
EXCEPTION_RECORD ExceptionRecord2;

var_R3_R0_Context = &Context; //&Context 为3环地址
var_ExceptionFrame = &ExceptionFrame; //ExceptionFrame == 0
Var_TrapFrame = &TrapFrame; //&TrapFrame 为3环地址

try
{
if(CurrentThread == UserMode)
{
if((&Context & 0x3) && (&EXCEPTION_RECORD & 0x3)) //Context、EXCEPTION_RECORD地址是否4字节对齐
{
if((&Context < MmUserProbeAddress) && (&EXCEPTION_RECORD < MmUserProbeAddress)//Context地址是否小于MmUserProbeAddress
{
if(EXCEPTION_RECORD.NumberParameters <= 0xF)
{
__asm lea ecx, ds:14h[NumberParameters*4]; //lea ecx,[eax*4+EXCEPTION_RECORD.ExceptionInformation]
ULONG Lenth = ecx;
ULONG dwProb = (ULONG)&EXCEPTION_RECORD + Lenth;
if(ecx == 0 || (dwProb >= (ULONG)&EXCEPTION_RECORD && dwProb < MmUserProbeAddress))
{
goto ToDoneBus;
}
}else goto except; //NumberParameters > 0xF
}else MmUserProbeAddress = 0; //触发异常
}else ExRaiseDatatypeMisalignment();

}
}

ToDoneBus:
RtlCopyMemory(&ContextRecord2, ContextRecord, sizeof(CONTEXT)); //将3环的CONTEXT拷贝到0环
RtlCopyMemory(&ExceptionRecord2, ExceptionRecord, NumberParameters*4); //将3环的EXCEPTION_RECORD拷贝到0环
ContextRecord = &ContextRecord2; //更新地址,后面的函数都将会使用0环地址,不再使用3环地址
ExceptionRecord = &ExceptionRecord2;
KeContextToKframes(&TrapFrame,0,&ContextRecord2,PreviousMode);

((EXCEPTION_RECORD *)(&ExceptionRecord2))->ExceptionCode &= 0xEFFFFFFF; //将ExceptionCode最高位清零

KiDispatchException(&ExceptionRecord2,0,&TrapFrame,PreviousMode,FirstChance);
...
return STATUS_SUCCESS;

except(-1)
{
...
return 0xC000000D; //STATUS_INVALID_PARAMETER
}

11.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
NTSTATUS __stdcall KiRaiseException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT ContextRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN BOOLEAN FirstChance
)
/*++
Routine Description:
This function is called to raise an exception. The exception can be raised as a first or second chance exception.
Arguments:
ExceptionRecord - Supplies a pointer to an exception record.
ContextRecord - Supplies a pointer to a context record.
ExceptionFrame - Supplies a pointer to an exception frame.
TrapFrame - Supplies a pointer to a trap frame.
FirstChance - Supplies a boolean value that specifies whether this is
the first (TRUE) or second (FALSE) chance for the exception.
Return Value:
STATUS_ACCESS_VIOLATION is returned if either the exception or the context record is not readable from user mode.
STATUS_DATATYPE_MISALIGNMENT is returned if the exception record or the context record are not properly aligned.
STATUS_INVALID_PARAMETER is returned if the number of exception parameters
is greater than the maximum allowable number of exception parameters.
STATUS_SUCCESS is returned if the exception is dispatched and handled.
--*/
{
CONTEXT ContextRecord2;
EXCEPTION_RECORD ExceptionRecord2;
ULONG Length;
ULONG Params;
KPROCESSOR_MODE PreviousMode;

// Establish an exception handler and probe the specified exception and
// context records for read accessibility. If the probe fails, then
// return the exception code as the service status. Else call the exception
// dispatcher to dispatch the exception.
try {

// Get the previous processor mode. If the previous processor mode
// is user, then probe and copy the specified exception and context records.
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
ProbeForReadSmallStructure(ContextRecord, sizeof(CONTEXT), CONTEXT_ALIGN);
ProbeForReadSmallStructure(ExceptionRecord,
FIELD_OFFSET (EXCEPTION_RECORD, NumberParameters) +
sizeof (ExceptionRecord->NumberParameters), sizeof(ULONG));
Params = ExceptionRecord->NumberParameters;
if (Params > EXCEPTION_MAXIMUM_PARAMETERS) {
return STATUS_INVALID_PARAMETER;
}

// The exception record structure is defined unlike others with trailing
// information as being its maximum size rather than just a single trailing element.
//
Length = (sizeof(EXCEPTION_RECORD) -
((EXCEPTION_MAXIMUM_PARAMETERS - Params) *
sizeof(ExceptionRecord->ExceptionInformation[0])));

//
// The structure is currently less that 64k so we don't really need this probe.
ProbeForRead(ExceptionRecord, Length, sizeof(ULONG));

// Copy the exception and context record to local storage so an
// access violation cannot occur during exception dispatching.
RtlCopyMemory(&ContextRecord2, ContextRecord, sizeof(CONTEXT));
RtlCopyMemory(&ExceptionRecord2, ExceptionRecord, Length);
ContextRecord = &ContextRecord2;
ExceptionRecord = &ExceptionRecord2;

// The number of parameters might have changed after we validated but before we
// copied the structure. Fix this up as lower levels might not like this.
ExceptionRecord->NumberParameters = Params;
}

// If an exception occurs during the probe of the exception or context
// record, then always handle the exception and return the exception code as the status value.
} except(EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}

// Move information from the context record to the exception and trap frames.
KeContextToKframes(TrapFrame,
ExceptionFrame,
ContextRecord,
ContextRecord->ContextFlags,
PreviousMode);

// Make sure the reserved bit is clear in the exception code and
// perform exception dispatching.
//
// N.B. The reserved bit is used to differentiate internally gerarated
// codes from codes generated by application programs.
ExceptionRecord->ExceptionCode &= 0xefffffff;
KiDispatchException(ExceptionRecord,
ExceptionFrame,
TrapFrame,
PreviousMode,
FirstChance);

return STATUS_SUCCESS;
}

3.6 总结

10.png

_CONTEXT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
kd> dt _CONTEXT -v
nt!_CONTEXT
struct _CONTEXT, 25 elements, 0x2cc bytes
+0x000 ContextFlags : Uint4B

//调试寄存器组,ContextFlags 设置为 CONTEXT_DEBUG_REGISTERS
//注意:CONTEXT_DEBUG_REGISTERS 没有被包含在 CONTEXT_FULL 中。
+0x004 Dr0 : Uint4B
+0x008 Dr1 : Uint4B
+0x00c Dr2 : Uint4B
+0x010 Dr3 : Uint4B
+0x014 Dr6 : Uint4B
+0x018 Dr7 : Uint4B

//浮点寄存器组,ContextFlags 设置为 CONTEXT_FLOATING_POINT
+0x01c FloatSave : struct _FLOATING_SAVE_AREA, 9 elements, 0x70 bytes

//段寄存器组,ContextFlags 字包含 CONTEXT_SEGMENTS
+0x08c SegGs : Uint4B
+0x090 SegFs : Uint4B
+0x094 SegEs : Uint4B
+0x098 SegDs : Uint4B

//通用数据寄存器(整型寄存器)组,ContextFlags 字包含 CONTEXT_INTEGER
+0x09c Edi : Uint4B
+0x0a0 Esi : Uint4B
+0x0a4 Ebx : Uint4B
+0x0a8 Edx : Uint4B
+0x0ac Ecx : Uint4B
+0x0b0 Eax : Uint4B

//控制寄存器组,ContextFlags 字包含 CONTEXT_CONTROL
+0x0b4 Ebp : Uint4B
+0x0b8 Eip : Uint4B
+0x0bc SegCs : Uint4B
+0x0c0 EFlags : Uint4B
+0x0c4 Esp : Uint4B
+0x0c8 SegSs : Uint4B

//拓展寄存器组,ContextFlags 字包含 CONTEXT_EXTENDED_REGISTERS
+0x0cc ExtendedRegisters : [512] UChar

_KTRAP_FRAME

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
kd> dt _KTrap_Frame -v
nt!_KTRAP_FRAME
struct _KTRAP_FRAME, 35 elements, 0x8c bytes
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint4B
+0x014 TempEsp : Uint4B
+0x018 Dr0 : Uint4B
+0x01c Dr1 : Uint4B
+0x020 Dr2 : Uint4B
+0x024 Dr3 : Uint4B
+0x028 Dr6 : Uint4B
+0x02c Dr7 : Uint4B
+0x030 SegGs : Uint4B
+0x034 SegEs : Uint4B
+0x038 SegDs : Uint4B
+0x03c Edx : Uint4B
+0x040 Ecx : Uint4B
+0x044 Eax : Uint4B
+0x048 PreviousPreviousMode : Uint4B
+0x04c ExceptionList : Ptr32 to struct _EXCEPTION_REGISTRATION_RECORD, 2 elements, 0x8 bytes
+0x050 SegFs : Uint4B
+0x054 Edi : Uint4B
+0x058 Esi : Uint4B
+0x05c Ebx : Uint4B
+0x060 Ebp : Uint4B
+0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B
+0x06c SegCs : Uint4B
+0x070 EFlags : Uint4B
+0x074 HardwareEsp : Uint4B
+0x078 HardwareSegSs : Uint4B
+0x07c V86Es : Uint4B
+0x080 V86Ds : Uint4B
+0x084 V86Fs : Uint4B
+0x088 V86Gs : Uint4B

ProbeForRead

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
NTKERNELAPI VOID NTAPI ProbeForRead(
IN CONST VOID *Address,
IN SIZE_T Length,
IN ULONG Alignment
)
/*++
Routine Description:
This function probes a structure for read accessibility and ensures
correct alignment of the structure. If the structure is not accessible
or has incorrect alignment, then an exception is raised.
Arguments:
Address - Supplies a pointer to the structure to be probed.
Length - Supplies the length of the structure.
Alignment - Supplies the required alignment of the structure expressed
as the number of bytes in the primitive datatype (e.g., 1 for char,
2 for short, 4 for long, and 8 for quad).
Return Value: None.
--*/
{
PAGED_CODE();

ASSERT(((Alignment) == 1) || ((Alignment) == 2) ||
((Alignment) == 4) || ((Alignment) == 8) ||
((Alignment) == 16));

if ((Length) != 0) {
if (((ULONG_PTR)(Address) & ((Alignment) - 1)) != 0) {
ExRaiseDatatypeMisalignment();

} else if ((((ULONG_PTR)(Address) + (Length)) < (ULONG_PTR)(Address)) ||
(((ULONG_PTR)(Address) + (Length)) > (ULONG_PTR)MM_USER_PROBE_ADDRESS)) {
ExRaiseAccessViolation();
}
}
}

ProbeForWrite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
VOID ProbeForWrite (
IN PVOID Address,
IN SIZE_T Length,
IN ULONG Alignment
)
/*++
Routine Description:
This function probes a structure for write accessibility and ensures
correct alignment of the structure. If the structure is not accessible
or has incorrect alignment, then an exception is raised.
Arguments:
Address - Supplies a pointer to the structure to be probed.
Length - Supplies the length of the structure.
Alignment - Supplies the required alignment of the structure expressed
as the number of bytes in the primitive datatype (e.g., 1 for char,
2 for short, 4 for long, and 8 for quad).
Return Value: None.
--*/

{
ULONG_PTR EndAddress;
ULONG_PTR StartAddress;
#if defined(_WIN64)
ULONG_PTR PageSize;
#else
#define PageSize PAGE_SIZE
#endif

// If the structure has zero length, then do not probe the structure for write accessibility or alignment.
if (Length != 0) {

// If the structure is not properly aligned, then raise a data misalignment exception.
ASSERT((Alignment == 1) || (Alignment == 2) ||
(Alignment == 4) || (Alignment == 8) ||
(Alignment == 16));

StartAddress = (ULONG_PTR)Address;
if ((StartAddress & (Alignment - 1)) == 0) {

// Compute the ending address of the structure and probe for
// write accessibility.
EndAddress = StartAddress + Length - 1;
if ((StartAddress <= EndAddress) &&
(EndAddress < MM_USER_PROBE_ADDRESS)) {

// N.B. Only the contents of the buffer may be probed.
// Therefore the starting byte is probed for the
// first page, and then the first byte in the page
// for each succeeding page.

#if defined(_WIN64)
//
// If this is a Wow64 process, then the native page is 4K, which
// could be smaller than the native page size/
if (PsGetCurrentProcess()->Wow64Process != NULL) {
PageSize = PAGE_SIZE_X86NT;
} else {
PageSize = PAGE_SIZE;
}
#endif

EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize;
do {
*(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;

StartAddress = (StartAddress & ~(PageSize - 1)) + PageSize;
} while (StartAddress != EndAddress);

return;

} else {
ExRaiseAccessViolation();
}

} else {
ExRaiseDatatypeMisalignment();
}
}

return;
}

(书已经看完)

RtlDispatchException函数分析:
① 调用RtlpGetStackLimits函数获取当前线程的KPCR.NtTib.StackLimit栈大小和KPCR.NtTib.StackBase栈底。
② 调用函数RtlpGetRegistrationHead函数获取FS:[0]异常链表指针ExceptionList
开始一个大循环,若满足(StackLimit <= ExceptionList) && (ExceptionList->next <= StackBase) && (ExceptionList & 0x3 == 0)则调用函数RtlIsValidHandler(ExceptionList->Handler)
RtlIsValidHandler函数会在RtlLookupFunctionTable函数中使用PsLoadedModuleList全局指针指向的模块链中搜索Handler所在模块。如果在某个模块中,则RtlLookupFunctionTable函数会返回Handler对应的函数地址,否则返回NULL
如果RtlLookupFunctionTable函数返回地址有效则RtlIsValidHandler函数返回0;如果RtlLookupFunctionTable函数返回NULL,则RtlIsValidHandler函数返回1(也就是Handler对应的函数地址不在某个模块中,是VEH不是SEH)。
⑤ 如果RtlIsValidHandler返回值不等于NULL,则调用函数RtlpExecuteHandlerForException执行异常函数。如果RtlIsValidHandler返回值为NULL,则从RtlDispatchException函数返回(处理VEH,不处理SEH)。
⑥ 根据RtlpExecuteHandlerForException函数的执行结果返回值Disposition,构造了一个switch-case,根据其值来判断是否异常已经处理,还是要继续循环ExceptionList链表,或是其他情况。
⑦ 遍历异常链表,调用异常处理函数,如果异常被正确处理了,该函数返回1。如果当前异常处理函数不能处理该异常,那么调用下一个,以此类推。如果到最好也没有人处理这个异常,返回0。

《软件调试 P285》 264

《Windows内核原理与实现 P340》

《内核情景分析 第八章》

函数源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
VOID __stdcall KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)
/*++
Routine Description:
This function is called to dispatch an exception to the proper mode and
to cause the exception dispatcher to be called. If the previous mode is
kernel, then the exception dispatcher is called directly to process the
exception. Otherwise the exception record, exception frame, and trap
frame contents are copied to the user mode stack. The contents of the
exception frame and trap are then modified such that when control is
returned, execution will commense in user mode in a routine which will call the exception dispatcher.
Arguments:
ExceptionRecord - Supplies a pointer to an exception record.
ExceptionFrame - Supplies a pointer to an exception frame. For NT386, this should be NULL.
TrapFrame - Supplies a pointer to a trap frame.
PreviousMode - Supplies the previous processor mode.
FirstChance - Supplies a boolean value that specifies whether this is
the first (TRUE) or second (FALSE) chance for the exception.
Return Value: None.
--*/
{
CONTEXT ContextFrame;
EXCEPTION_RECORD ExceptionRecord1, ExceptionRecord2;
LONG Length;
ULONG UserStack1;
ULONG UserStack2;

// Move machine state from trap and exception frames to a context frame,
// and increment the number of exceptions dispatched.
KeGetCurrentPrcb()->KeExceptionDispatchCount += 1;
ContextFrame.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
if (PreviousMode == UserMode) {

// For usermode exceptions always try to dispatch the floating
// point state. This allows expection handlers & debuggers to
// examine/edit the npx context if required. Plus it allows
// exception handlers to use fp instructions without detroying
// the npx state at the time of the exception.
//
// Note: If there's no 80387, ContextTo/FromKFrames will use the
// emulator's current state. If the emulator can not give the
// current state, then the context_floating_point bit will be
// turned off by ContextFromKFrames.
ContextFrame.ContextFlags |= CONTEXT_FLOATING_POINT;
if (KeI386XMMIPresent) {
ContextFrame.ContextFlags |= CONTEXT_EXTENDED_REGISTERS;
}
}

KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextFrame);

// if it is BREAK_POINT exception, we subtract 1 from EIP and report
// the updated EIP to user. This is because Cruiser requires EIP
// points to the int 3 instruction (not the instruction following int 3).
// In this case, BreakPoint exception is fatal. Otherwise we will step
// on the int 3 over and over again, if user does not handle it
//
// if the BREAK_POINT occured in V86 mode, the debugger running in the
// VDM will expect CS:EIP to point after the exception (the way the
// processor left it. this is also true for protected mode dos
// app debuggers. We will need a way to detect this.

// if ((ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) &&
// !(ContextFrame.EFlags & EFLAGS_V86_MASK)) {

switch (ExceptionRecord->ExceptionCode) {
case STATUS_BREAKPOINT:
ContextFrame.Eip--;
break;
}

// Select the method of handling the exception based on the previous mode.
ASSERT ((
!((PreviousMode == KernelMode) &&
(ContextFrame.EFlags & EFLAGS_V86_MASK))
));

if (PreviousMode == KernelMode) {

// Previous mode was kernel.
//
// If the kernel debugger is active, then give the kernel debugger the
// first chance to handle the exception. If the kernel debugger handles
// the exception, then continue execution. Else attempt to dispatch the
// exception to a frame based handler. If a frame based handler handles
// the exception, then continue execution.
//
// If a frame based handler does not handle the exception,
// give the kernel debugger a second chance, if it's present.
//
// If the exception is still unhandled, call KeBugCheck().

if (FirstChance == TRUE) {

if ((KiDebugRoutine != NULL) &&
(((KiDebugRoutine) (TrapFrame,
ExceptionFrame,
ExceptionRecord,
&ContextFrame,
PreviousMode,
FALSE)) != FALSE)) {

goto Handled1;
}

// Kernel debugger didn't handle exception.
if (RtlDispatchException(ExceptionRecord, &ContextFrame) == TRUE) {
goto Handled1;
}
}

// This is the second chance to handle the exception.
if ((KiDebugRoutine != NULL) &&
(((KiDebugRoutine) (TrapFrame,
ExceptionFrame,
ExceptionRecord,
&ContextFrame,
PreviousMode,
TRUE)) != FALSE)) {

goto Handled1;
}

KeBugCheckEx(
KERNEL_MODE_EXCEPTION_NOT_HANDLED,
ExceptionRecord->ExceptionCode,
(ULONG)ExceptionRecord->ExceptionAddress,
(ULONG)TrapFrame,
0);

} else {

// Previous mode was user.
//
// If this is the first chance and the current process has a debugger
// port, then send a message to the debugger port and wait for a reply.
// If the debugger handles the exception, then continue execution. Else
// transfer the exception information to the user stack, transition to
// user mode, and attempt to dispatch the exception to a frame based
// handler. If a frame based handler handles the exception, then continue
// execution with the continue system service. Else execute the
// NtRaiseException system service with FirstChance == FALSE, which
// will call this routine a second time to process the exception.
//
// If this is the second chance and the current process has a debugger
// port, then send a message to the debugger port and wait for a reply.
// If the debugger handles the exception, then continue execution. Else
// if the current process has a subsystem port, then send a message to
// the subsystem port and wait for a reply. If the subsystem handles the
// exception, then continue execution. Else terminate the thread.

if (FirstChance == TRUE) {

// This is the first chance to handle the exception.
if ((KiDebugRoutine != NULL) &&
((PsGetCurrentProcess()->DebugPort == NULL) ||
(KdIsThisAKdTrap(ExceptionRecord, &ContextFrame, UserMode))))
{
// Now dispatch the fault to the kernel debugger.
if ((((KiDebugRoutine) (TrapFrame,
ExceptionFrame,
ExceptionRecord,
&ContextFrame,
PreviousMode,
FALSE)) != FALSE)) {

goto Handled1;
}
}

if (DbgkForwardException(ExceptionRecord, TRUE, FALSE)) {
goto Handled2;
}

// Transfer exception information to the user stack, transition
// to user mode, and attempt to dispatch the exception to a frame based handler.
repeat:
try {

// If the SS segment is not 32 bit flat, there is no point
// to dispatch exception to frame based exception handler.
if (TrapFrame->HardwareSegSs != (KGDT_R3_DATA | RPL_MASK) ||
TrapFrame->EFlags & EFLAGS_V86_MASK ) {
ExceptionRecord2.ExceptionCode = STATUS_ACCESS_VIOLATION;
ExceptionRecord2.ExceptionFlags = 0;
ExceptionRecord2.NumberParameters = 0;
ExRaiseException(&ExceptionRecord2);
}

// Compute length of context record and new aligned user stack pointer.
Length = (sizeof(CONTEXT) + CONTEXT_ROUND) & ~CONTEXT_ROUND;
UserStack1 = (ContextFrame.Esp & ~CONTEXT_ROUND) - Length;

// Probe user stack area for writability and then transfer the
// context record to the user stack.
ProbeForWrite((PCHAR)UserStack1, Length, CONTEXT_ALIGN);
RtlCopyMemory((PULONG)UserStack1, &ContextFrame, sizeof(CONTEXT));

// Compute length of exception record and new aligned stack address.
Length = (sizeof(EXCEPTION_RECORD) - (EXCEPTION_MAXIMUM_PARAMETERS -
ExceptionRecord->NumberParameters) * sizeof(ULONG) +3) &
(~3);
UserStack2 = UserStack1 - Length;

// Probe user stack area for writeability and then transfer the
// context record to the user stack area.
// N.B. The probing length is Length+8 because there are two arguments
//need to be pushed to user stack later.
ProbeForWrite((PCHAR)(UserStack2 - 8), Length + 8, sizeof(ULONG));
RtlCopyMemory((PULONG)UserStack2, ExceptionRecord, Length);

// Push address of exception record, context record to the
// user stack. They are the two parameters required by _KiUserExceptionDispatch.
*(PULONG)(UserStack2 - sizeof(ULONG)) = UserStack1;
*(PULONG)(UserStack2 - 2*sizeof(ULONG)) = UserStack2;

// Set new stack pointer to the trap frame.
KiSegSsToTrapFrame(TrapFrame, KGDT_R3_DATA);
KiEspToTrapFrame(TrapFrame, (UserStack2 - sizeof(ULONG)*2));

// Force correct R3 selectors into TrapFrame.
TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, PreviousMode);
TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, PreviousMode);
TrapFrame->SegGs = 0;

// Set the address of the exception routine that will call the
// exception dispatcher and then return to the trap handler.
// The trap handler will restore the exception and trap frame
// context and continue execution in the routine that will call the exception dispatcher.
TrapFrame->Eip = (ULONG)KeUserExceptionDispatcher;
return;

} except (KiCopyInformation(&ExceptionRecord1,
(GetExceptionInformation())->ExceptionRecord)) {

// If the exception is a stack overflow, then attempt
// to raise the stack overflow exception. Otherwise,
// the user's stack is not accessible, or is misaligned, and second chance processing is performed.
if (ExceptionRecord1.ExceptionCode == STATUS_STACK_OVERFLOW) {
ExceptionRecord1.ExceptionAddress = ExceptionRecord->ExceptionAddress;
RtlCopyMemory((PVOID)ExceptionRecord,
&ExceptionRecord1, sizeof(EXCEPTION_RECORD));
goto repeat;
}
}
}

// This is the second chance to handle the exception.
if (DbgkForwardException(ExceptionRecord, TRUE, TRUE)) {
goto Handled2;
} else if (DbgkForwardException(ExceptionRecord, FALSE, TRUE)) {
goto Handled2;
} else {
ZwTerminateThread(NtCurrentThread(), ExceptionRecord->ExceptionCode);
KeBugCheckEx(
KERNEL_MODE_EXCEPTION_NOT_HANDLED,
ExceptionRecord->ExceptionCode,
(ULONG)ExceptionRecord->ExceptionAddress,
(ULONG)TrapFrame,
0);
}
}

// Move machine state from context frame to trap and exception frames and
// then return to continue execution with the restored state.
Handled1:

KeContextToKframes(TrapFrame, ExceptionFrame, &ContextFrame,
ContextFrame.ContextFlags, PreviousMode);

// Exception was handled by the debugger or the associated subsystem
// and state was modified, if necessary, using the get state and set
// state capabilities. Therefore the context frame does not need to
// be transfered to the trap and exception frames.
Handled2:
return;
}

X86汇编 x64汇编
X86汇编使用、逻揭、盾环—-C++汇编,类-this指针,虛西数.虚西数表
PE文件一-变形壳.压缩壳.内存加裁PE Cexe. dl) DLL注入
Win32开发,窗口、消息处理-开发窗口程序的小工具–Crakeme破解
硬编码-编译器小工具开发
×86段页保护模式-Intel开发王朋
驱动开发
系统调用
进程线程
句柄表
同步互斥
APC
DPC
时钟中断处理
2022-7-20
异常 0day内核–驱动开发–驱动通信——-相关CVE
调试 PE复习整理–内存加裁DLL和驱动(驱动签名机制及绕过)
内存管理 win xp UAC机制(内核情景分析?) Token ACL怎么回事
3环Win32开发,消息机制—win32k回调—相关CVE
Windows回调机制–进程、线程、句柄、注册表、系统回调 (软件调试0环调用3环?)

VT
硬编码 x64汇编
x64内核机制 VM