SEH(结构化异常处理)

内容回顾:

当用户异常产生后,内核函数KiDispatchException并不是像处理内核异常那样在0环直接处理,而是修正3环EIP为KiUSerExceptionDispatcher函数后就结束了。
这样,当线程再次回到3环时,将会从KiUserExceptionDispatcher函数开始执行。

KiUserExceptionDispatcher会调用RtlDispatchException函数来查找并调用异常处理函数,查找的顺序:

  1. 先查全局链表:VEH
  2. 再查句柄链表:SEH

在这里插入图片描述
它是与线程有关的,存储在当前线程的堆栈中。

3环的FS寄存器指向TEB
在这里插入图片描述
在这里插入图片描述
下图中两个call,第一个是VEH,第二个是SEH
在这里插入图片描述
然后进入后查看第一批代码:
在这里插入图片描述

首先取出fs+8,和fs+4位置的参数
在这里插入图片描述

当前堆栈的界面和起始位置,利用这两个值,进行如下代码检测:

在这里插入图片描述

看看堆栈是否属于当前线程(也就是说异常处理函数必须位于当前线程堆栈)

继续看,fs指向Teb,fs:0指向SEH:
在这里插入图片描述

扫描二维码关注公众号,回复: 12581152 查看本文章

真正开始调用异常处理的函数如下(也就是说所写异常处理函数必须符合调用约定,不能随便写):
在这里插入图片描述
由内核发起调用的函数都一样,都是必须符合调用约定,写成规范形式。

总结:

  1. fs:[0]指向的是SEH链表的第一个成员
  2. SEH的异常处理函数必须在当前线程的堆栈中
  3. 只有在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

  1. 把Trap_frame(当前线程3环进入0环时,那些寄存区环境,也就是eip运行地方那些值)备份到context里(为返回3环做准备)(也就是把3环代码eip保存,不能破坏原来的执行流程
  2. 第二步:判断先前模式,0是内核调用,1是用户调用,用户层异常呢,紧接着就是跳转
  3. 把KeUserExceptionDispatcher里面的值覆盖到Eip
  4. 回到三环的KiUserExceptionDispatcher会调用RtlDispatchException函数来查找并调用异常处理函数,查找的顺序:先查全局链表:VEH; 再查句柄链表:SEH
  5. 找到解决之后呢,调用ZwContine再次进入0环,在这里插入图片描述
    主要作用就是恢复_TRAP_FRAME(也就是第一步中的EIP恢复原来执行流程)然后通过_KiServiceExit返回到3环。

在跳转CommonDispatchException之前,还传了两个参数

 if (ExceptionRecord->ExceptionCode == 0xC0000094) {
    
    
        ContextRecord->Eip = ContextRecord->Eip + 2;
        // ContextRecord->Ecx=1;
        return ExceptionContinueExecution;
    }

记住,这里的返回并非直接回到下面这行代码:

  printf("函数正常执行了");

猜你喜欢

转载自blog.csdn.net/CSNN2019/article/details/113839673