VEH和SEH

VEH和SEH文章目录 VEHKiUserExc 函数分析代码实现添加 VEH 异常处理函数 VEH 异常的处理流程 SEHException 函数的执行流程代码实现添加 SEH 异常处理函数 SEH 异常的处理流程 VEH 当用户异常产生后 内核函数 KiDispatchEx 并不是像处理内核异常那样在 0 环直接处理 而是修正 3 环 EIP 为 KiUs

VEH

当用户异常产生后,内核函数KiDispatchException并不是像处理内核异常那样在0环直接处理,而是修正3环EIP为KiUserExceptionDispatcher函数后就结束了

这样,当线程再次回到3环时,将会从KiUserExceptionDispatcher函数开始执行

KiUserExceptionDispatcher函数分析

在ntdll中找到KiUserExceptionDispatcher函数

在这里插入图片描述

首先会调用RtlDispatchException函数,通过这个函数找到当前用户异常的处理函数。如果处理成功了,就会调用ZwContinue重新进入0环。

在这里插入图片描述

那么, 如果RtlDispatchException没有找到当前用户异常的处理函数,就会调用ZwRaiseException对当前的异常进行第二次分发

关键在于对RtlDispatchException函数的理解,这个函数的作用就是找到异常的处理函数,一个一个调用,如果有异常处理函数能够处理当前的异常,就直接调用处理函数,然后返回。

RtlDispatchException函数分析

在这里插入图片描述

这里会调用RtlCallVectoredExceptionHandlers函数,这个函数的作用就是找VEH全局链表,这个链表里存储的是一个一个的异常处理函数。如果找到了函数直接返回

如果没有找到则会继续找SEH

代码实现添加VEH异常处理函数

typedef PVOID(NTAPI *FnAddVectoredExceptionHandler)(ULONG, _EXCEPTION_POINTERS*); FnAddVectoredExceptionHandler MyAddVectoredExceptionHandler; LONG NTAPI VectExcepHandler(PEXCEPTION_POINTERS pExcepInfo) { MessageBox(NULL,L"VEH异常处理函数执行了...",L"VEH异常",MB_OK); if (pExcepInfo->ExceptionRecord->ExceptionCode == 0xC0000094)//除0异常 { //将除数修改为1 pExcepInfo->ContextRecord->Ecx = 1; //修改发生异常的代码的Eip idiv ecx长度2字节 从下一行开始执行 pExcepInfo->ContextRecord->Eip = pExcepInfo->ContextRecord->Eip + 2; return EXCEPTION_CONTINUE_EXECUTION;//已处理 } return EXCEPTION_CONTINUE_SEARCH;//未处理 } int main() { //动态获取AddVectoredExceptionHandler函数地址 HMODULE hModule = GetModuleHandle(L"Kernel32.dll"); MyAddVectoredExceptionHandler = (FnAddVectoredExceptionHandler)::GetProcAddress(hModule,"AddVectoredExceptionHandler"); //参数1表示插入VEH链的头部, 0插入到VEH链的尾部 MyAddVectoredExceptionHandler(0, (_EXCEPTION_POINTERS *)&VectExcepHandler); //构造除0异常 int val = 0; _asm { xor edx, edx xor ecx, ecx mov eax, 100 idiv ecx //edx = eax / ecx mov val, edx } printf("val = %d\n",val); getchar(); } 

代码解释:

首先定义了一个VEH的异常处理函数VectExcepHandler,这个函数只能有两个返回值:

  • EXCEPTION_CONTINUE_EXECUTION 表示异常已处理
  • EXCEPTION_CONTINUE_SEARCH 表示异常未处理

那么我们怎么将异常处理函数插入到链表里呢?这里需要用到一个函数AddVectoredExceptionHandler,这个函数位于Kernel32.dll。这里需要动态获取函数地址。

动态获取到函数地址以后,就可以将异常处理函数写到VEH全局链表里

接着再构造一个除0异常,接着异常触发,执行我们写的异常处理函数

这个异常处理函数的参数是一个结构体指针,有两个成员

typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; 

第一个成员ExceptionRecord是异常发生时的信息,第二个成员ContextRecord是异常发生时的上下文环境。

有了这个参数我们就可以捕获异常发生时的相关信息并且修改异常发生时的寄存器环境。

代码中是先判断异常代码是否为除0异常,然后修改发生异常的Eip和Ecx,接着返回异常已处理。如果不是除0异常就返回异常未处理。

VEH异常的处理流程

  1. CPU捕获异常
  2. 通过KiDispatchException进行分发(3环异常将EIP修改为KiUserExceptionDispatcher)
  3. KiUserExceptionDispatcher调用RtlDispatchException
  4. RtlDispatchException查找VEH处理函数链表,并调用相关处理函数
  5. 代码返回到ZwContinue再次进入0环
  6. 线程再次返回3环后,从修正的位置开始执行

SEH

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

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

ExceptionList

SEH是线程相关的,存储在线程的堆栈中。通过FS:0可以找到这个异常处理函数链表。

3环的FS:0指向的是TEB

kd> dt _TEB ntdll!_TEB +0x000 NtTib : _NT_TIB 

而TEB的第一个成员是一个子结构体_NT_TIB

kd> dt _NT_TIB ntdll!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD 

这个子结构体的第一个成员就是ExceptionList,异常处理链表

typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD* Next; //下一个节点,-1就是没有下一个节点了 PEXCEPTION_ROUTINE Handler; //指向异常处理函数 } EXCEPTION_REGISTRATION_RECORD; 

这个结构体至少包含两个成员。第一个成员指向下一个节点,第二个成员指向SEH处理函数。

这样一个一个的结构体在当前的堆栈中构建了一个局部的链表。如下图:

在这里插入图片描述

RtlDispatchException函数的执行流程

在这里插入图片描述

这个函数首先会查VEH,接着调用RtlpGetStackLimits,我们跟进这个函数

在这里插入图片描述

这个函数会将FS:8和FS:4位置的值取出

ntdll!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void 

这两个值分别是StackBase和StackLimit,当前堆栈的起始位置和界限。取这两个值是为了检测SEH处理函数是否处于当前的堆栈中。

.text:7C9577F0 call _RtlpGetRegistrationHead@0 ; 获取异常链表头 

接着调用RtlpGetRegistrationHead,获取异常链表头

.text:7C push eax .text:7C call _RtlIsValidHandler@4 ; RtlIsValidHandler(x) 

然后检测当前的处理函数是否有效

.text:7C95784F push dword ptr [ebx+4] .text:7C lea eax, [ebp+var_14] .text:7C push eax .text:7C push [ebp+arg_4] .text:7C push ebx .text:7C95785A push esi .text:7C95785B call _RtlpExecuteHandlerForException 

然后调用异常处理函数

代码实现添加SEH异常处理函数

//最原始的 SEH链表结构(这个结构怎么写都行) struct _EXCEPTION { struct _EXCEPTION* Next; DWORD Handler; }; EXCEPTION_DISPOSITION _cdecl MyEexception_handler ( struct _EXCEPTION_RECORD *ExceptionRecord, //异常结构体 PVOID EstablisherFrame, //SEH结构体地址 struct _CONTEXT *ContextRecord, //存储异常发生时的各种寄存器的值 栈位置等 PVOID DispatcherContext ) { MessageBox(NULL,L"SEH异常处理函数执行了...",L"SEH异常",NULL); if (ExceptionRecord->ExceptionCode == 0xC0000094) { ContextRecord->Eip = ContextRecord->Eip + 2; ContextRecord->Ecx = 100; return ExceptionContinueExecution; } return ExceptionContinueSearch; } int main() { DWORD temp; _EXCEPTION Exception;//必须在当前线程的堆栈中 //fs[0]-> Exception _asm { mov eax, fs:[0] mov temp,eax lea ecx,Exception mov fs:[0],ecx } //为SEH成员赋值 Exception.Next = (_EXCEPTION*)temp; Exception.Handler = (DWORD)&MyEexception_handler; //创建异常 int val = 0; _asm { xor edx,edx xor ecx,ecx mov eax,1 idiv ecx //edx = eax /ecx mov val,ecx } //摘除刚插入的SEH _asm { mov eax, temp mov fs:[0],eax } printf("val = %d",val); getchar(); } 

代码解释:

首先需要定义一个和ExceptionList一样的结构体,这个结构体可以随便取名。

由于SEH处理函数是提供给RtlDispatchException调用的,所以需要遵循一定的格式。

EXCEPTION_DISPOSITION _cdecl MyEexception_handler ( struct _EXCEPTION_RECORD *ExceptionRecord, //异常结构体 PVOID EstablisherFrame, //SEH结构体地址 struct _CONTEXT *ContextRecord, //存储异常发生时的各种寄存器的值 栈位置等 PVOID DispatcherContext ) 
  • ExceptionRecord:记录异常产生时的信息结构体
  • EstablisherFrame:指向堆栈中的异常结构体地址
  • ContextRecord:存储异常发生时的各种寄存器的值 栈位置等

代码流程如下:

  1. 在main函数里创建了一个异常结构体,这个结构体必须在当前的堆栈中,
  2. 利用内联汇编的方式让FS:0指向刚刚创建的异常结构体
  3. 对异常结构体进行赋值
  4. 抛出异常
  5. 等异常处理完成以后摘除SEH

SEH异常的处理流程

  1. FS:[0]指向SEH链表的第一个成员
  2. SEH的异常处理函数必须在当前线程的堆栈中
  3. 只有当VEH中的异常处理函数不存在或者不处理才会到SEH链表中查找
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/221863.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午5:07
下一篇 2026年3月17日 下午5:07


相关推荐

  • LaTeX的安装教程(Texlive 2020 + TeX studio)

    LaTeX的安装教程(Texlive 2020 + TeX studio)LaTeX安装步骤1、TexLive安装1.1下载TexLive1.2安装TexLive1、TexLive安装1.1下载TexLive点击TexLive使用清华镜像进行下载。如下图所示。1.2安装TexLive1.2.1打开下载后的.ISO文件,如下图所示。以管理员身份运行install-tl-windows.bat文件。1.2.2运行后的界面如下图所示。软件的安装路径默认为C盘,这里可以修改为其他磁盘。1.2.3修改好软件的安装路径后,点击Advanced,

    2022年6月11日
    54
  • 最小二乘法原理和推导过程「建议收藏」

    最小二乘法原理和推导过程「建议收藏」对于有误差的统计值,我们一般都是采用均值作为使用值。但是这种使用均值代替的方式是不是合理?为什么不用中位数、几何平均数什么的?这需要一个解释。1.什么是二乘?对于一列数字,比如10.1、…

    2022年5月17日
    59
  • VMware虚拟机安装Linux教程(超详细)

    VMware虚拟机安装Linux教程(超详细)一 安装 VMware 下载地址 16pro https www aliyundrive com s FSktJJXsfa8 安装 选一下安装地址 一直下一步即可 可能会要求重启电脑 重启即可 二 安装 Linux 下载地址 CentOS 7 5 提取码 486k 接下来看图操作 2 1 新建虚拟机现在我们就相当于买电脑 先把电脑配置整好 什么 cpu 啊内存条啊硬盘啊什么乱七八糟的 先不着急装系统 这里看你装什么版本的 Linux 了 我装的是 GenOS7 564 位所以选的是 Ge

    2026年3月19日
    2
  • java.lang.integer_java 中 关于java.lang.ArrayStoreException: java.lang.Integer异常,是什么原因?…

    java.lang.integer_java 中 关于java.lang.ArrayStoreException: java.lang.Integer异常,是什么原因?…Value的值是Object型,要装到Object[]数组中,而不是String[]。往数组里装不匹配的类型,就抛这个异常。packagepack.java.demo;importjava.util.HashMap;importjava.util.Map;publicclassTest{/***@paramargs*/publicstaticvoidmain(String[]…

    2022年7月16日
    16
  • Dubbo:RPC原理

    Dubbo:RPC原理1 RPC 原理一次完整的 RPC 调用流程如下 1 服务消费方 client 调用以本地调用方式调用服务 2 clientstub 接收到调用后负责将方法 参数等组装成能够进行网络传输的消息体 3 clientstub 找到服务地址 并将消息发送到服务端 4 serverstub 收到消息后进行解码 5 serverstub 根据解码结果调用本地的服务 6 本地服务执行并将结果返回给 serverstub 7 serverstub 将返回结果打包成消息并发送至消费方 8 clientst

    2026年3月18日
    2
  • ADC RF中频采样 Vivado Verilog 联合 matlab 进行带通滤波器设计与仿真

    ADC RF中频采样 Vivado Verilog 联合 matlab 进行带通滤波器设计与仿真1.滤波器参数计算RF中频信号的频率范围为70MHz±2MHz,采样频率为40.625MHz。采样后信号的频谱是原信号频谱以40.625MHz为周期的频谱搬移,根据奈奎斯特采样定理,40.625MHz采样率的奈奎斯特采样区为[N*20.3125,(N+1)*20.3125]MHz(N为自然数)。频谱搬移在第一奈奎斯特采样区为11.25MHz±2MHz(负频率向右的两次频移)。所以滤波器的通带需要设计为9.25MHz~13.25MHz通过的带通滤波器。2.通过matlab的fdatool工具进行滤波器

    2022年5月30日
    49

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号