Windows异常学习笔记(三)—— VEH&SEH

(35) 2024-09-22 15:01:01

Windows异常学习笔记(三)—— VEH&SEH

    • 要点回顾
      • 分析 KiUserExceptionDispatcher
      • 分析 _RtlDispatchException
      • _RtlCallVectoredExceptionHandlers
    • VEH(向量化异常处理)
      • 实验:自定义VEH
      • 总结:VEH异常处理流程
    • SEH(结构化异常处理)
      • 分析 RtlDispatchException
      • 实验:构造自定义SEH
      • 总结:SEH异常处理流程

要点回顾

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

分析 KiUserExceptionDispatcher

位置:ntdll.dll
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第1张

分析 _RtlDispatchException

3环调用了RtlCallVectoredExceptionHandlers
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第2张0环中没有调用
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第3张

_RtlCallVectoredExceptionHandlers

作用

  1. 查找VEH链表(全局链表),如果有则调用
  2. 查找SEH链表(局部链表,在堆栈中),如果有则调用

VEH(向量化异常处理)

全称:Vectored Exception Handler
描述:全局异常链表,不同的线程共用一个

实验:自定义VEH

编译并运行以下代码

#include <stdio.h> #include <windows.h> typedef PVOID (NTAPI *FnAddVectoredExceptionHandler)(ULONG, _EXCEPTION_POINTERS *); FnAddVectoredExceptionHandler MyAddVectoredExceptionHandler; // VEH异常处理只能返回2个值 // EXCEPTION_CONTINUE_EXECUTION 已处理 // EXCEPTION_CONTINUE_SEARCH 未处理 LONG NTAPI VectExcepHandler( PEXCEPTION_POINTERS pExcepInfo) { 
    // pExcepInfo->ContextRecord 保存进入异常处理前的线程信息 // pExcepInfo->ExceptionRecord 保存异常信息 if( pExcepInfo->ExceptionRecord->ExceptionCode == 0xC0000094 ) { 
    ::MessageBoxA(NULL, "VEH除0异常触发了", "VEH异常", MB_OK); //修改完EIP之后并不是异常处理结束后直接返回,而是通过ZwContinue进行修正 pExcepInfo->ContextRecord->Eip = pExcepInfo->ContextRecord->Eip+2; //pExcepInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } int main() { 
    //1. 动态获取AddVectoredExceptionHandler函数地址 HMODULE hMyModule = GetModuleHandle("kernel32.dll"); MyAddVectoredExceptionHandler = (FnAddVectoredExceptionHandler)GetProcAddress(hMyModule, "AddVectoredExceptionHandler"); //2. 参数1表示插入到VEH头部,0表示插入到VEH尾部 MyAddVectoredExceptionHandler(0, (_EXCEPTION_POINTERS *)&VectExcepHandler); //3. 构造除0异常 __asm { 
    xor edx, edx xor ecx, ecx mov eax, 0x10 idiv ecx //EDX:EAX 除以 ECX } //4. 产生异常,从这里继续 printf("代码从这里继续执行\n"); return 0; } 

执行结果
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第4张

Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第5张

总结:VEH异常处理流程

  1. CPU捕获异常信息
  2. 通过KiDispatchException进行分发(EIP=KiUserExceptionDispatcher)
  3. KiUserExceptionDispatcher调用RtlDispatchException
  4. RtlDispatchException查找VEH处理函数链表 并调用相关处理函数
  5. 代码返回到KiUserExceptionDispatcher
  6. 调用ZwContinue再次进入0环(ZwContinue调用NtContinue,主要作用就是恢复TRAP_FRAME 然后通过KiServiceExit返回到3环)
  7. 线程再次返回3环后,从修正后的位置开始执行

SEH(结构化异常处理)

全称:Structured Exception Handling
描述:局部异常链表,线程相关,位于当前线程的堆栈当中,不同线程不同堆栈
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第6张注意

  1. 在0环时,FS指向KPCR,在3环时,FS指向TEB,这两个结构体的第一个成员都指向NT_TIB,NT_TIB第一个成员为ExceptionList,即异常处理链表
  2. 在加入VEH时,只需调用系统提供的API即可,但想要构造SEH的话,必须手动在当前线程当中加入这样一个结构,在A线程中加入的SEH不会对B线程产生影响

思考:当将SEH放入堆栈当中之后,什么时候会进行调用?
答案:需要分析RtlDispatchException

分析 RtlDispatchException

Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第7张
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第8张Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第9张Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第10张

EXCEPTION_DISPOSITION __cdecl MyExceptionHandler( struct _EXCEPTION_RECORD *ExceptionRecord, //存储异常信息:类型、产生位置 void * EstablisherFrame, //MyException结构体地址 struct _CONTEXT *ContextRecord, //结构体,异常发生时各种寄存器的值,堆栈位置等 void * Dispatchercontext) 

实验:构造自定义SEH

编译并运行以下代码

#include <stdio.h> #include <windows.h> /* //0环异常处理时讲过这个结构体 typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEPtiON_ROUTINE Handler; } */ //定义时结构体名字可以不同,但必须遵守这个格式 struct MyException { 
    struct MyException *prev; DWORD handler; }; EXCEPTION_DISPOSITION __cdecl MyExceptionHandler( struct _EXCEPTION_RECORD *ExceptionRecord, //存储异常信息:类型、产生位置 void * EstablisherFrame, //MyException结构体地址 struct _CONTEXT *ContextRecord, //结构体,异常发生时各种寄存器的值,堆栈位置等 void * Dispatchercontext) { 
    if( ExceptionRecord->ExceptionCode == 0xC0000094 ) { 
    MessageBoxA(NULL, "SEH除0异常触发了", "SEH异常", MB_OK); ContextRecord->Eip = ContextRecord->Eip+2; //pExcepInfo->ContextRecord->Ecx = 1; return ExceptionContinueExecution; } return ExceptionContinueSearch; } void TestException() { 
    DWORD temp; //插入异常,必须在当前线程的堆栈当中 //若定义成全局变量则无效 MyException myException; __asm { 
    mov eax, FS:[0] mov temp, eax lea ecx, myException mov FS:[0], ecx } //原来的异常链表中也许有值,因此需要挂上 myException.prev = (MyException*)temp; myException.handler = (DWORD)&MyExceptionHandler; //构造除0异常 __asm { 
    xor edx, edx xor ecx, ecx mov eax, 0x10 idiv ecx //EDX:EAX 除以 ECX } //处理完成,摘掉异常 __asm { 
    mov eax, temp mov FS:[0], eax } printf("函数执行完毕\n"); } int main() { 
    TestException(); return 0; } 

运行结果
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第11张
Windows异常学习笔记(三)—— VEH&SEH (https://mushiming.com/)  第12张

总结:SEH异常处理流程

  1. FS:[0]指向SEH链表的第一个成员
  2. SEH的目标结构体必须在当前线程的堆栈中
  3. 只有当VEH中的异常处理函数不存在或者不处理才会到SEH链表中查找
THE END

发表回复