内容回顾:
当用户异常产生后,内核函数KiDispatchException并不是像处理内核异常那样在0环直接处理,而是修正3环EIP为KiUSerExceptionDispatcher函数后就结束了。
这样,当线程再次回到3环时,将会从KiUserExceptionDispatcher函数开始执行。
KiUserExceptionDispatcher会调用RtlDispatchException函数来查找并调用异常处理函数,查找的顺序:
- 先查全局链表:VEH
- 再查句柄链表:SEH
它是与线程有关的,存储在当前线程的堆栈中。
3环的FS寄存器指向TEB
下图中两个call,第一个是VEH,第二个是SEH
然后进入后查看第一批代码:
首先取出fs+8,和fs+4位置的参数
当前堆栈的界面和起始位置,利用这两个值,进行如下代码检测:
看看堆栈是否属于当前线程(也就是说异常处理函数必须位于当前线程堆栈)
继续看,fs指向Teb,fs:0指向SEH:
扫描二维码关注公众号,回复:
12581152 查看本文章
真正开始调用异常处理的函数如下(也就是说所写异常处理函数必须符合调用约定,不能随便写):
由内核发起调用的函数都一样,都是必须符合调用约定,写成规范形式。
总结:
- fs:[0]指向的是SEH链表的第一个成员
- SEH的异常处理函数必须在当前线程的堆栈中
- 只有在VEH中的处理函数不存在或者不处理才会到SEH链表中查找
程序代码
#include<Windows.h>
#include <iostream>
//0环异常处理时讲过这个结构体
/*
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD* next;
PEXCEPTION_ROUTINE Handler;
};
*/
struct MyException {
struct MyException* prev;
DWORD handle;
};
EXCEPTION_DISPOSITION _cdecl MyException_handler(
struct _EXCEPTION_RECORD* ExceptionRecord,//ExceptionRecord存储异常信息,什么类型,异常产生位置
void* EstablishFrame,//MyException结构体地址(指向堆栈中的结构体)
struct _CONTEXT* ContextRecord,//Context结构体,存储异常发生时的各种寄存器值,堆栈位置等
void* DispatcherContext
) {
::MessageBoxA(NULL, "SEH异常处理函数执行了", "SEH异常", MB_OK);
if (ExceptionRecord->ExceptionCode == 0xC0000094) {
ContextRecord->Eip = ContextRecord->Eip + 2;
// ContextRecord->Ecx=1;
return ExceptionContinueExecution;
}
return ExceptionContinueSearch;
}
int main()
{
DWORD temp;
//插入异常处理函数,必须在当前线程的堆栈中;
MyException myException;//(只能在堆栈中创建,不能定义为全局变量)
__asm {
mov eax,fs:[0]
mov temp,eax
lea ecx,myException
mov fs:[0],ecx
}
myException.prev = (MyException*)temp;
myException.handle = (DWORD)&MyException_handler;
//创造异常
__asm{
xor edx,edx
xor ecx,ecx
mov eax,0x10
idiv ecx //EDX:EAX除以ECX
}
//摘掉异常处理函数
__asm{
mov eax,temp
mov fs:[0],eax
}
printf("函数正常执行了");
}
实现截图
具体流程
idiv ecx //EDX:EAX除以ECX
产生异常:
CPU指令检测到异常(例:除0)------>查IDT表,执行中断处理函数--------->CommonDispatchException
然后异常分发:KiDispatchException
- 把Trap_frame(当前线程3环进入0环时,那些寄存区环境,也就是eip运行地方那些值)备份到context里(为返回3环做准备)(也就是把3环代码eip保存,不能破坏原来的执行流程)
- 第二步:判断先前模式,0是内核调用,1是用户调用,用户层异常呢,紧接着就是跳转
- 把KeUserExceptionDispatcher里面的值覆盖到Eip
- 回到三环的KiUserExceptionDispatcher会调用RtlDispatchException函数来查找并调用异常处理函数,查找的顺序:先查全局链表:VEH; 再查句柄链表:SEH
- 找到解决之后呢,调用ZwContine再次进入0环,
主要作用就是恢复_TRAP_FRAME(也就是第一步中的EIP恢复原来执行流程)然后通过_KiServiceExit返回到3环。
在跳转CommonDispatchException之前,还传了两个参数
if (ExceptionRecord->ExceptionCode == 0xC0000094) {
ContextRecord->Eip = ContextRecord->Eip + 2;
// ContextRecord->Ecx=1;
return ExceptionContinueExecution;
}
记住,这里的返回并非直接回到下面这行代码:
printf("函数正常执行了");