VEH

开发人员主要使用两种异常处理技术,一种是 SEH (结构化异常处理),另一种是 VEH (向量化异常处理,XP 以上)

VEH全称Vectored Exception Handling ,VEH

向量化异常处理的基本理念与VEH相似,也是注册一个回调函数,当发生异常时会被系统的异常处理过程调用

通过API函数AddVectioredExceptionHandler注册VEH回调函数,其原型如下

WINBASEAPI PVOID WINAPI AddVectoredExceptionHandler{
   	ULONG FirstHandler,
    PVECTORED_EXCEPTION_HANDLER VectoredHandler //回调函数地址
}

VEH回调函数也形成了一个链表,若参数FirstHandler的值为0,则回调函数位于VEH链表的尾部

若该值非0,则置于VEH链表的最前端,当有多个VEH回调函数存在时

这个值会营销回调函数被调用的顺序,应该将函数的返回值保存下来,用来卸载回调函数

VEH回调函数所在的模块被卸载之后,系统不能自动地将回调函数地址从VEH链表上移除,需要程序在退出前自己完成卸载工作,可以用如下API实现

ULONG RemoveVectoredExceptionHandler{
    PVOID VectoredHandlerHnalde
};

只有一个参数即VectoredHnalderHandle就是前面保存的AddVectoredExcpetionHandler的返回值

回调函数的参数PEXCEPTION_PRIONERS与在顶层异常处理回调函数中用到的参数相同,即EXCEPTION_POINTERS结构的指针。回调函数合理的返回值有2个,分别是EXCEPTION_CONTINUE_EXECUTIONEXCEPTION_CONTINUE_SEARCH,意义与SEH回调函数一致

VEH和SEH的异同

在初识SEH中,VEH只发生在用户层的线程

并且当异常发生时VEH会优先与SEH获得控制权

如果有调试器,调试器会优先于VEH回调函数

系统会自动调用AddVectoredExceptionHandler注册的VEH回调函数

如果回调函数修复了异常

则返回EXCEPTION_CONTINUE_EXECUTION

在异常发生时以CONTEXT指定的线程环境继续运行,此时SEH处理过程将被跳过

如果回调函数不能处理异常,回到函数应返回EXCEPTION_CONTINUE_SEARCH系统会采取与SEH大致相同的策略遍历VEH链表。

如果整个链表搜索完毕,没有回调函数对异常进行处理,就将控制权转移给系统,由系统继续遍历SEH链表上注册的回调函数,然后就是SEH机制

区别

  • 注册机制不同,SEH的相关信息主要保存在栈中,而且后注册的回调函数总是处于SEH链的前端,也就是说,当异常发生时,异常总是由内层回调函数优先处理,只有在内层回调不处理异常时,外层回调才有机会获得控制权。而VEH不同,它的相关信息保存在独立的链表(实际存储在ntdll.dll),在注册VEH时可以指定回调函数是位于VEH链表的前端和后端,这就解决了我们系统在SEH中获得优先处理权的问题

  • 优先级不同

    VEH先于SEH被调用

    如果VEH表明自己已经 处理了异常,那么SEH就没机会再处理异常

  • 作用范围不同

    SEH机制基于线程

    作用范围也是基于同一个线程之间

    而VEH在整个进程范围内都是有效的

    可以捕获和处理所有线程产生的异常

  • VEH不需要栈展开,有序SEH的注册和使用一来于函数调用的栈帧,在嗲用SEH回调函数会涉及栈展开的问题,这样SEH就有两次被调用的机会

    而VEH的实现不一来栈,所以在调用VEH回调函数前不需要进行栈展开

    只有一次被调用的机会

VCH

Windows Vista开始,微软又为VEH处理增加了新的内容-VCH

与VEH类似,它使用一下两个API进行注册和注销

image-20240722150044621

需要注意的是VCH回调函数的调用时机

分析ntdll!RtlDispatchException的代码可知

VCH会在两种情况下被调用

  • 当SEH机制无法正常运行的情况(相关数据结构被破坏,未通过SafeSEH或者SEHOP验证)SEH分发将被跳过

  • 当SEH回调函数能够返回ntdll!RtlDispatchException函数时,因为SEH处理程序具有特殊性,在执行SEH回调函数的时候,不仅有可能直接跳转到其他位置执行(例如EXCEPTION_EXECUTE_HANDLER的情况,会跳转的Try的结束处)也有可能不在返回,所有,只有当SEH回调函数返回了ExceptinContinueExecutinon或者UEF函数修复了异常,或者UEF因为调试器的存在在其终结处理被跳过的情况,SEH才会返回ntdll!RtlDispatchException函数,此时才有机会执行VCH回调函数

  • 总的来说,VCH的调用时机是在SEH处理之后,返回值可以忽略掉