windows内核研究(异常-CPU异常记录)
异常
CPU异常记录
CPU的异常基本围绕这几个点展开
- 异常记录
- 异常分发
- 异常处理
异常产生后,首先要记录异常信息
(异常的类型、异常发生的位置等),然后要寻找异常的处理函数,我们称为异常的分发
,最后找到异常处理函数并调用,我们称为异常处理
CPU异常的产生
CPU检测到异常 -> 查IDT表执行中断处理函数 -> CommonDispatchException -> KiDispatchException
异常的分类
- CPU产生的异常(除零异常)
- 软件模拟产生的异常(由高级语言C++,JAVA等throw抛出的异常)
Windows的异常代码
Windows 常见的 IDT 相关异常
中断号 | 名称 | 触发原因 |
---|---|---|
0x00 | #DE (Divide Error) | 除零错误 |
0x06 | #UD (Invalid Opcode) | 非法指令 |
0x0D | #GP (General Protection Fault) | 内存访问越权 |
0x0E | #PF (Page Fault) | 页错误(访问无效内存) |
0x2D | #BP (Breakpoint) | int 3 断点 |
但是处理异常并不是_KiTrap00这个函数去处理的,而是由CommonDispatchException(我这里是KiDispatchTrapException表示这个异常是由硬件触发的)处理
CommonDispatchException结构体
type struct _EXCEPTION_RECORD{DWORD ExceptionCode, // 异常代码DWORD ExceptionFlags, // 异常状态struct _EXCEPTION_RECORD *ExceptionRecord, // 下一个异常PVOID ExceptionAddress, // 发生异常的地址DWORD NumberParameters, // 附加参数个数ULONG _PTR ExceptionInformation,[EXCEPTION_MAXIMUM_PARAMETERS] // 附加参数指针
}
模拟异常记录
模拟代码
#include <iostream>void test() {// 模拟抛出异常throw 1;
}int main() {test();system("pause");return 0;
}
当我们使用软件模拟去调用异常时的调用链
CxxThrowException -> RaiseException(Kernel32.dll) -> NTDLL.DLL!RtlRaiseException() -> NTDLL!NtRaiseException -> NT!KiRaiseException
软件模拟异常和CPU异常的不同点
- 在CPU异常中都有对应的一个值(异常代码),而在软件模拟异常中这个值和当前的编译环境有关(在当前的编译环境中这个值是固定的)
- ExceptionAddress中存储的值是_RaiseException的地址,而CPU异常中存储的是异常的地址
总结:
- CPU异常
- CPU检测到错误
- 查IDT表,执行中断处理函数
- CommonDispatchException(填充ExceptionRecord结构体)
- KiDispatchException
- 模拟异常
- throw关键字(依赖编译器)
- CxxThrowException
- NTDLL.DLL!RtlRaiseException(填充ExceptionRecord结构体)
- NTDLL!NtRaiseException
- NT!KiRaiseException
- KiDispatchException
内核异常的处理流程
用户异常与内核异常
发生在用户空间的就是用户异常,发生在内核空间的就是内核异常
无论是CPU异常还是模拟异常,是用户层还是内核层异常,都要通过KiDispatchException
函数进行分发,理解好这个函数是学好异常的关键
KiDispatchException执行流程
- _KeContextFromKframes 将Trap_frame备份到context为返回3环做准备
- 判断先前模式 0内核调用,1用户层调用
- 判断是否是第一次调用
- 判断是否有内核调式器
- 如果没有或者内核调试器不处理
- 调用RtlDispatchException
- 如果返回FALSE
- 再次判断是否有内核调试器,没有和有调用不处理就直接蓝屏
用户异常的分发
异常如果发生在内核层,处理就比较简单,因为异常处理函数也在0环,不用切勿堆栈,但是如果异常发生在3环,就意味着必须要切换堆栈,回到3环执行处理函数
KiDispatchException执行流程
- _KeContextFromKframes 将Trap_frame备份到context为返回3环做准备
- 判断先前模式 0是内核调用,1是用户层调用
- 判断是否是次一次调用
- 判断是否有内核调式器
- 发送给3环调试器
- 如果3环调式器没有处理这个异常 修正EIP为KiUserExceptionDispatcher
- KiDispatchException函数执行结束
- CPU异常:CPU检测到异常 -> 查IDT执行处理函数 -> CommonDispatchException -> KiDispatchException通过IRETD返回3环
- 模拟异常:CxxThrowException -> RaiseException -> RtlRaiseException -> NT!NtRaiseException -> NT!KiRaiseException -> KiDispatchException 通过系统调用返回3环
- 无论通过哪种方式,线程再次回到3环时,将执行KiUserExceptionDispatcher