Windows异常学习笔记(一)—— CPU异常记录
基础知识
一个异常产生后,首先是要记录异常信息(异常的类型、异常发生的位置等),然后要寻找异常的处理函数,称为异常的分发,最后找到异常处理函数并调用,称为异常处理。
异常的分类
- CPU产生的异常
- 软件模拟产生的异常
例一:在C语言中使用除法时,若CPU检测到除数为0,便会抛出异常(CPU产生的异常)
例二:使用代码抛出异常(软件模拟产生异常)
CPU异常
处理流程:
1.CPU指令检测到异常(例:除0),异常一定是先由CPU发现的
2.查IDT表,执行中断处理函数,不同的异常调用不同的中断处理函数
3.CommonDispatchException
4.KiDispatchExceeption
Windows异常代码(出自 张银奎《软件调试》):
分析中断处理函数 _KiTrap00
执行流程:
- 保存现场(将寄存器堆栈位置等信息保存到 _Trap_Frame 中)
- 调用CommonDispatchException函数(分析参数EAX,EBX中的值)
查看IDT表反汇编(通过 ntoskrnl.exe 搜索字符串 _IDT 进行定位):
[ebp+68h]:指向 _Trap_Frame 中的 Eip 的位置,当程序产生异常时,保存进入异常处理前的地址
0xC0000094h:除0异常的异常代码
loc_407399 调用了 CommonDispatchException
总结:
- 异常处理函数中并没有直接对异常进行处理,而是调用了CommonDispatchException
- 这样设计异常的目的是为了程序员有机会对异常进行处理
分析 CommonDispatchException
CommonDispatchException在堆栈上构建了一个结构体,然后把异常的一些相关信息存储到一个结构体里
结构体:
type struct _EXCEPTION_RECORD
{
DWORD ExceptionCode; //异常代码,除0时为0x0C0000094
DWORD ExceptionFlags; //异常状态,0位CPU异常,1位软件模拟异常,堆栈异常为8等等
struct _EXCEPTION_RECORD* ExceptionRecord; //下一个异常,通常为空
PVOID ExceptionAddress; //异常发生地址
DWORD NumberParameters; //附加参数个数
ULONG_PTR ExceptionInformation
[EXCEPTION_MAXIMUM_PARAMETERS]; //附加参数指针
}
总结
CPU异常执行的流程:
1、CPU指令检测到异常
2、查IDT表,执行中断处理函数
3、调用CommonDispatchException(构建EXCEPTION_RECORD)
4、KiDispatchException(分发异常:目的是找到异常的处理函数)
软件模拟异常
模拟异常的产生:
CxxThrowException
↓
(KERNEL32.DLL)RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments)
↓
NTDLL.DLL!RtlRaiseException()
↓
NT!NtRaiseException
↓
NT!KiRaiseException
实验:分析模拟异常
第一步:编译并运行以下代码
#include <stdio.h>
int main()
{
throw 1;
getchar();
return 0;
}
第二步:查看汇编代码
可以看到,当人为抛出一个异常时,实际上就是调用了 __CxxThrowException 这样一个函数
注意:本实验使用的编辑器为vc6.0,为C++环境,在不同的环境中,调用的函数可能是不同的,但是最终调用的系统函数都是相同的
第三步:分析 __CxxThrowException
__CxxThrowException 调用了 Kernel32.dll 中的函数 RaiseException
第四步:分析 RaiseException
- 填充ExceptionRecord结构体
type struct _EXCEPTION_RECORD
{
DWORD ExceptionCode; //异常代码
DWORD ExceptionFlags; //异常状态
struct _EXCEPTION_RECORD* ExceptionRecord; //下一个异常
PVOID ExceptionAddress; //异常发生地址
DWORD NumberParameters; //附加参数个数
ULONG_PTR ExceptionInformation
[EXCEPTION_MAXIMUM_PARAMETERS]; //附加参数指针
}
2. 调用 Ntdll.dll!RtlRaiseException
注意一:当CPU产生异常时会记录一个ErrorCode,通过查表可以查到ErrorCode具体的含义,不同的异常对应不同的错误代码,但是软件抛出的ErrorCode是根据编译环境决定的,如下图的EDX中存储的值即为当前编译环境的ErrorCode
注意二:CPU记录异常的地址是真正发生异常的地址,但软件抛出异常时记录的地址是__RaiseException函数的地址
第五步:分析 KiRaiseException
(IDA代码待补充)
-
EXCEPTION_RECORD.ExceptionCode最高位清零 用于区分CPU异常。
-
调用 KiDispatchException 开始分发异常
总结
异常只有在记录的时候处理方式不同,最终在分发时走向同一个地方