深入理解SEH

深入理解SEHKiDispatchEx 我们已经知晓 其内核态的异常分发主要靠检测 KiDebugRouti 是否拥有调试器来实现 然后根据 RtlDispatchE 来调用内核的 SEH 来处理 对于 PreviousMode 为 userMode 而言 其 SEH 和 VEH 链最后是由 KiUserDispat 来处理的 所以我们这次深入看看 KiUserDispat

KiDispatchException我们已经知晓,其内核态的异常分发主要靠检测“KiDebugRoutine”,是否拥有调试器来实现,然后根据RtlDispatchException来调用内核的SEH来处理。对于PreviousMode为userMode而言,其SEH和VEH链最后是由KiUserDispatchException来处理的,所以我们这次深入看看KiUserDispatchException到底做了什么。

在这里插入图片描述无奈没有源码,只好照搬这张看雪加密与解密的这张图了。可以看出其实KiUserDispatchException也只是一个框架,主要处理函数仍然是在RtlDispatchException。但我们先看一下KiUserDispatchException的处理流程。首先调用RtlDispatchException,检测能否被处理,若能被处理,则会调用NtContinue函数,不会再返回。若RtlDispatchException返回了false,则调用NtRaiseException,同时最后传进去false用于初始化将FirstChance赋值为0,这样就相当于第二次处理异常,同时该函数不会返回。如果第二次仍然不行,则会将该异常的flags值标记为EXECUTION_NONCONTINUABLE,然后结束分发。
最终不管是KernelMode还是UserMode最终又指向了RtlDispatchException。所以我们来看看RtlDispatchException的源码

//Exception Flags #define EXCEPTION_NONCONTINUABLE 0x1 // Noncontinuable exception #define EXCEPTION_UNWINDING 0x2 // Unwind is in progress #define EXCEPTION_EXIT_UNWIND 0x4 // Exit unwind is in progress #define EXCEPTION_STACK_INVALID 0x8 // Stack out of limits or unaligned #define EXCEPTION_NESTED_CALL 0x10 // Nested exception handler call #define EXCEPTION_TARGET_UNWIND 0x20 // Target unwind in progress #define EXCEPTION_COLLIDED_UNWIND 0x40 // Collided exception handler call //MmExecutionFlags on Win7 #define MEM_EXECUTE_OPTION_DISABLE 0x1  #define MEM_EXECUTE_OPTION_ENABLE 0x2 #define MEM_EXECUTE_OPTION_DISABLE_THUNK_EMULATION 0x4 #define MEM_EXECUTE_OPTION_PERMANENT 0x8 #define MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE 0x10 #define MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE 0x20 #define MEM_EXECUTE_OPTION_DISABLE_EXCEPTIONCHAIN_VALIDATION 0x40 #define MEM_EXECUTE_OPTION_VALID_FLAGS 0x7f //NtGlobalFlag #define FLG_ENABLE_CLOSE_EXCEPTIONS 0x00 // kernel mode only #define FLG_ENABLE_EXCEPTION_LOGGING 0x00 // kernel mode only //Part of ProcessInformationClass #define ProcessExecuteFlags 34 typedef struct _DISPATCHER_CONTEXT { 
    PEXCEPTION_REGISTRATION_RECORD RegistrationPointer; } DISPATCHER_CONTEXT; // // Execute handler for exception function prototype. // EXCEPTION_DISPOSITION RtlpExecuteHandlerForException ( IN PEXCEPTION_RECORD ExceptionRecord, IN PVOID EstablisherFrame, IN OUT PCONTEXT ContextRecord, IN OUT PVOID DispatcherContext, IN PEXCEPTION_ROUTINE ExceptionRoutine ); VOID RtlpGetStackLimits ( OUT PULONG LowLimit, OUT PULONG HighLimit ); EXCEPTION_DISPOSITION RtlCallVectoredExceptionHandlers ( IN PEXCEPTION_RECORD ExceptionRecord, IN OUT PCONTEXT ContextRecord ); EXCEPTION_DISPOSITION RtlCallVectoredContinueHandlers ( IN PEXCEPTION_RECORD ExceptionRecord, IN OUT PCONTEXT ContextRecord ); PEXCEPTION_REGISTRATION_RECORD RtlpGetRegistrationHead ( VOID ); BOOLEAN RtlIsValidHandler ( IN PEXCEPTION_ROUTINE Handler, IN ULONG ProcessExecuteFlag ); BOOLEAN __stdcall RtlDispatchException(PEXCEPTION_RECORD pExcptRec, CONTEXT *pContext) { 
    BOOLEAN Completion; PEXCEPTION_RECORD pExcptRec; EXCEPTION_REGISTRATION_RECORD *RegistrationPointerForCheck; EXCEPTION_REGISTRATION_RECORD *RegistrationPointer; EXCEPTION_REGISTRATION_RECORD *NestedRegistration; EXCEPTION_DISPOSITION Disposition; EXCEPTION_RECORD ExceptionRecord1; DISPATCHER_CONTEXT DispatcherContext; ULONG ProcessExecuteOption; ULONG StackBase,StackLimit; BOOLEAN IsSEHOPEnable; NTSTATUS status; Completion = FALSE; // 首先调用VEH异常处理例程,其返回值包括EXCEPTION_CONTINUE_EXECUTION (0xffffffff)和EXCEPTION_CONTINUE_SEARCH (0x0)两种情况 // 这是从Windows XP开始加入的新的异常处理方式 // 返回值不是EXCEPTION_CONTINUE_SEARCH,那么就结束异常分发过程 if (RtlCallVectoredExceptionHandlers(pExcptRec, pContext) != EXCEPTION_CONTINUE_SEARCH ) { 
    Completion = TRUE; } else { 
    // 获取栈的内存范围 RtlpGetStackLimits(&StackLimit, &StackBase); ProcessExecuteOption = 0; // 从fs:[0]获取SEH链的头节点 即mov eax, fs:[0] RegistrationPointerForCheck = RtlpGetRegistrationHead(); // 默认假设SEHOP机制已经启用,这是一种对SEH链的安全性进行增强验证的机制,Structured Exception Handler Overwrite Protection,我们先不管 IsSEHOPEnable = TRUE; // 查询进程的ProcessExecuteFlags标志,决定是否进行SEHOP验证 status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessExecuteFlags, &ProcessExecuteOption, sizeof(ULONG), NULL) ; // 在查询失败,或者没有设置标志位时,进行SEHOP增强验证 // 也就是说,只有在明确查询到禁用了SEHOP时才不会进行增强验证 if ( NT_SUCCESS(status) && (ProcessExecuteOption & MEM_EXECUTE_OPTION_DISABLE_EXCEPTIONCHAIN_VALIDATION) ) { 
    // 若确实未开启SEHOP增强校验机制,设置此标志 此时 status == 0, IsSEHOPEnable = FALSE; } else { 
    // 否则,进行开始SEHOP验证 如果头结点是0xffffffff,表明已经没有注册的SEH,break if ( RegistrationPointerForCheck == -1 ) break; //验证SEH链中各个结点的有效性并遍历至最后一个结点 do { 
    // 若发生以下情况,认为栈无效,此时不再执行基于栈的SEH处理 // 1.SEH节点不在栈中 if ( (ULONG)RegistrationPointerForCheck < StackLimit || (ULONG)RegistrationPointerForCheck + 8 > StackBase // 2.SEH节点的位置没有按ULONG对齐 || (ULONG)RegistrationPointerForCheck & 3 // 3.Handler在栈中 || ((ULONG)RegistrationPointerForCheck->Handler < StackLimit || (ULONG)RegistrationPointerForCheck->Handler >= StackBase) ) { 
    pExcptRec->ExceptionFlags |= EXCEPTION_STACK_INVALID; goto DispatchExit; } // 取SEH链的下一个结点 RegistrationPointerForCheck = RegistrationPointerForCheck->Next; } while ( RegistrationPointerForCheck != -1 ); // 此时RegistrationPointerForCheck指向最后一个节点 // 如果TEB->SameTebFlags中的RtlExceptionAttached位(第9位)被设置,但最后一个结点的Handler却不是预设的安全SEH,那么SEHOP校验不通过,不再执行任何SEHHandler if ((NtCurrentTeb()->SameTebFlags & 0x200) && RegistrationPointerForCheck->Handler != FinalExceptionHandler) { 
    goto DispatchExit; } } // 从fs:[0]获取SEH链的头节点 RegistrationPointer = RtlpGetRegistrationHead(); NestedRegistration = NULL; // 遍历SEH链表执行Handler while ( TRUE ) { 
    if ( RegistrationPointer == -1 ) //-1表示SEH链的结束 goto DispatchExit; // 若SEHOP机制未开启,则这里必须进行校验,反之则不需要,因为SEHOP机制已经验证过了 if ( !IsSEHOPEnable ) { 
    if ( (ULONG)RegistrationPointer < StackLimit || (ULONG)RegistrationPointer + 8 > StackBase || (ULONG)RegistrationPointer & 3 || ((ULONG)RegistrationPointer->Handler >= StackLimit && (ULONG)RegistrationPointer->Handler <= StackBase) ) { 
    pExcptRec->ExceptionFlags |= EXCEPTION_STACK_INVALID; goto DispatchExit; } } // 调用RtlIsValidHandler对Handler进行增强验证,也就是SafeSEH机制 if (!RtlIsValidHandler(RegistrationPointer->Handler, ProcessExecuteOption)) { 
    pExcptRec->ExceptionFlags |= EXCEPTION_STACK_INVALID; goto DispatchExit; } // 执行SEHHandler Disposition = RtlpExecuteHandlerForException(pExcptRec, RegistrationPointer, pContext, &DispatcherContext, RegistrationPointer->Handler); if ( NestedRegistration == RegistrationPointer ) { 
    pExcptRec->ExceptionFlags &= (~EXCEPTION_NESTED_CALL); NestedRegistration = NULL; } // 检查SEHHandler的执行结果 switch(Disposition) { 
    case ExceptionContinueExecution : //如果返回ExceptionContinueExecution,表示处理程序认为可以继续执行,但是发生异常时候的ERR的flags里为EXCEPTION_NONCONTINUABLE,则发生冲突,引发一个不可以继续的异常。 if ((ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) != 0) { 
    ExceptionRecord1.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord1.ExceptionRecord = ExceptionRecord; ExceptionRecord1.NumberParameters = 0; RtlRaiseException(&ExceptionRecord1); } else { 
   //Completion设置为true,表明完成了处理 Completion = TRUE; goto DispatchExit; } case ExceptionContinueSearch ://若ERR的异常标志位表明栈无效,则直接退出 if (ExceptionRecord->ExceptionFlags & EXCEPTION_STACK_INVALID) goto DispatchExit; break; case ExceptionNestedException : ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL; if (DispatcherContext.RegistrationPointer > NestedRegistration) { 
    NestedRegistration = DispatcherContext.RegistrationPointer; } break; default ://若以上选项都不是,表明返回的值是个错误的值,引发一个STATUS_INVALID_DISPOSITION无效选项的异常,并且flag设置为不可继续执行标记 ExceptionRecord1.ExceptionCode = STATUS_INVALID_DISPOSITION; ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord1.ExceptionRecord = ExceptionRecord; ExceptionRecord1.NumberParameters = 0; RtlRaiseException(&ExceptionRecord1); break; } // 取SEH链的下一个结点 RegistrationPointer = RegistrationPointer->Next; // Next } } DispatchExit: // 调用VEH的ContinueHandler // 只要RtlDispatchException函数正常返回,那么ContinueHandler总会在SEH执行完毕后被调用 RtlCallVectoredContinueHandlers(pExcptRec, pContext); return Completion; } 

整理一下思路,Completion作为返回值被送回到KiUserDispatchException,Completion只会有两种控制流会被设置为true。

  • 调用RtlCallVectoredExceptionHandlers,若处理得到的返回值不是EXCEPTION_CONTINUE_SEARCH,即表明找到了对应的处理函数,则VEH处理了该异常
  • 在SEHOP没问题的前提下,遍历SEH链表,当RtlpExecuteHandlerForException的返回值dispositionExceptionContinueExecution(继续执行)并且与当初引发异常时的ExceptionFlags不冲突,那么则会将Completion设置为true,表明成功处理了异常。

接下来再理一遍RtlDispatchException的过程,加深印象。
首先分发给RtlCallVectoredExceptionHandlers处理,若成功处理,则结束成功返回。
不然就会进行SEH的遍历。因为SEH是基于栈的,所以SEHOP(结构化Overwrite Protextion)就应运而生,为了防止利用者篡改SEH链而进行的设置,从xp后开始加上了这个设置。默认是SEHOP是开启的。SEHOP的标志位可由ZwQueryInformationProcess获得,通过检验MEM_EXECUTE_OPTION_DISABLE_EXCEPTIONCHAIN_VALIDATION标志位,确认确实没开启SEHOP选项,那么就不需要SEHOP验证。否则进入验证的循环。通过遍历SEH链,对每一个结点都要进行check。check主要进行以下的操作




  • 检验该ERR处于的地址是否在栈中,栈的base和limit在初期通过fs的TIB获取,不记得的再找下TIB的数据结构看看。注意此时的limit在低地址,base在高地址,因为栈是向低地址增长的。
  • 并且栈是按4字节对齐,所以对齐操作也会check。
  • 并且还要检测handler的地址,handler的地址应该位于代码段,而不是布置到栈上,若布置到栈上,很有可能就是利用者将数据放到栈上了。
  • 对于最后一个结点也会做check,主要是判断TEB->SameTebFlags 和 FinalExceptionFilter是否一致,确保最后一个结点的handler未被篡改

若在上述过程中,有任一结点不满足要求,都会不在进行基于栈的SEH操作,直接返回。

到这里,开了SEHOP的check就完成了。接着会再次遍历该SEH链,对于未开启SEHOP的程序,RtlDispatchException仍会先检测结点的合法性RtlIsValidHandler会对每一个结点强制进行增强验证,通过后会调用RtlpExecuteHandlerForException,传递参数并且调用注册的SEH函数,其返回值作为判别的标准。对于返回值为ExceptionContinueExecution的,再检测完确实不是一个不可继续执行的异常后,就表明成功处理了,否则会引发一个不可执行的异常。对于返回值是ExceptionContinueSearch,则会继续遍历下一个结点。若返回值是一个异常数值,则会调用一个STATUS_INVALID_DISPOSITION异常。最终函数返回前会调用RtlCallVectoredContinueHandlers,但暂时还不清楚这样的目的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

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

(0)
上一篇 2026年3月19日 下午6:42
下一篇 2026年3月19日 下午6:42


相关推荐

  • android toast全屏,Android Toast实现全屏显示

    android toast全屏,Android Toast实现全屏显示本文为大家分享了AndroidToast全屏显示的具体代码,供大家参考,具体内容如下废话不说,直接上代码:privatevoidtoastFullScreen(){Toasttoast=Toast.makeText(this,null,Toast.LENGTH_LONG*10*1000);toast.setGravity(Gravity.CENTER,0,0);Line…

    2025年11月8日
    5
  • ORA-00937:不是单组分组函数[通俗易懂]

    ORA-00937:不是单组分组函数[通俗易懂]例子:SELECTCOUNT(*)BZC144,NVL(SUM(BCF125),0)BZC145,CF11.AAA020FROMCF11,CF12WHERECF11.BCF110=CF12.BCF110ANDCF11.AAE100=’1′;在Oracle中PLSQL执行上面的语句就会出现,ORA-00937:不是单组分组函数.原因是:数据中有这么一…

    2022年6月26日
    83
  • ServBay + n8n,5分钟打造自动化工作流,告别重复劳动

    ServBay + n8n,5分钟打造自动化工作流,告别重复劳动

    2026年3月15日
    1
  • php vld_phpenv使用教程

    php vld_phpenv使用教程1、查看本地是否已经安装了vld扩展,如果没有任何输出,那就准备安装吧![root@taiwu~]#/home/work/lib/php5.6.7/bin/php-m|grepvld2、去官方网站下载vld(http://pecl.php.net/package/vld),找到最新版本,右键复制地址链接就能得到下载地址,通过浏览器直接下载也行。我是通过wget命令在centos的命…

    2025年6月26日
    5
  • dede数据库内容替换,去掉文章内容中的img标签

    dede数据库内容替换,去掉文章内容中的img标签

    2022年2月23日
    46
  • kali如何安装蚁剑

    kali如何安装蚁剑文章目录 1 已有蚁剑源码安装 2 没有蚁剑源码安装 3 蚁剑源码下载 1 已有蚁剑源码安装从 v2 0 0 beta 版本开始 用户 开发者只需要下载对应平台的加载器 无需安装额外的环境 即可对源代码进行编辑 执行 调试等操作 可直接运行当前最新的开发版和发行版源代码 如果已经有蚁剑源码了 就只需要下载加载器即可使用蚁剑了新建一个文件夹 文件夹中不能包含中文在我们新创建的 antsword 目录下下载加载器加载器下载地址 https github com AntSwordProj AntS

    2026年3月16日
    2

发表回复

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

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