接下来看__C_specific_handler函数中出现的函数。首先是RtlUnwindEx函数。在异常操作中,当找到了愿意处理当前异常的ExceptionHandler 之后就会以此为参数调用RtlUnwindEx 函数,根据X86 学到的知识,RtlUnwindEx 在执行ExceptionHandler之前应该执行展开操作,清理资源,然后再执行ExceptionHandler。
函数原型
VOID
RtlUnwindEx (
IN PVOID TargetFrame OPTIONAL,
IN PVOID TargetIp OPTIONAL,
IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
IN PVOID ReturnValue,
IN PCONTEXT OriginalContext,
IN PUNWIND_HISTORY_TABLE HistoryTable OPTIONAL
);
__C_specific_handler 传递给它的参数为
RtlUnwindEx( TargetFrame, ExceptionHandler, ExceptionRecord, (PVOID)ExceptionRecord->ExceptionCode, Dispatcher_Context->ContextRecord, Dispatcher_Context->HistoryTable);
函数通过调用RtlVirtualUnwind(第一个参数UNW_FLAG_UHANDLER) 进行虚拟展开,我们知道,虚拟展开之后context内的状态就是执行完目标地址所在的函数的状态。
RtlVirtualUnwind 函数会返回一个异常处理函数ExceptionRoutine,如果它不为空的话,代表该函数可以进行目标函数的展开操作。然后我们调用该函数,并进行展开
操作,根据调用结果采取不同的动作,如果当前函数就是ExceptionRoutine 所在的函数的话,设置 ExceptionFlags |= EXCEPTION_TARGET_UNWIND;并执行展开操作。
如果返回值是ExceptionContinueSearch,函数将继续调用RtlVirtualUnwind遍历异常链,执行展开操作,直到找到了包含ExceptionHandler的函数,我们在上面
看到过,此时函数将跳转到ExceptionHandler 执行,并不会返回。
另外,就像我们之前在X86 的SEH 中看到的一样,执行展开的函数同样包含有一个异常处理程序用于捕获在展开操作中产生的异常。但该异常处理函数并不像之前X86一样,判断当前的
操作类型之后再给出返回值,该函数直接给出返回值ExceptionCollidedUnwind。
从上面的截图可以发现,在执行展开操作之前,RtlpExecuteHadnlerForUnwind 会将当前的 DispacherContext 指针放入 RSP+0x20 的位置。而后当出现异常的时候,RtlpExecuteHandlerForUnwind
对应的异常处理函数RtlpUnwindHandler 会将EstablisherFrame + 0x20 即上面的RSP+0x20 取出来,然后将原来DispacherContext 的内容拷贝到当前DispatcherContext内。
然后我们回到RtlDispatchException 查看它是如何操作的。
它跳过了先前的展开过程。
case ExceptionCollidedUnwind:
ControlPc = DispatcherContext.ControlPc;
ImageBase = DispatcherContext.ImageBase;
FunctionEntry = DispatcherContext.FunctionEntry;
EstablisherFrame = DispatcherContext.EstablisherFrame;
RtlpCopyContext(&ContextRecord1,
DispatcherContext.ContextRecord);
ContextRecord1.Rip = ControlPc;
ExceptionRoutine = DispatcherContext.LanguageHandler;
HandlerData = DispatcherContext.HandlerData;
HistoryTable = DispatcherContext.HistoryTable;
ScopeIndex = DispatcherContext.ScopeIndex;
Repeat = TRUE;
设置变量为执行展开操作之前保存的值,然后设置Repeat=TRUE并进入下一层的循环操作,即直接继续执行产生展开异常的异常处理函数,而此时改变的就只有
scopeindex,即越过了导致异常的finally层。详细的执行过程可以参考下面给的链接,展开过程中的异常讲的特别好,就不再重复了。
http://boxcounter.com/technique/2011-11-04-seh-x64/
另外,还有一个种情况是,执行异常处理函数中产生的异常。此种情况上面的描述也十分详细,不再重复了。
到此我们大概分析了SEH X64 的处理过程。
下一篇文章介绍得到X86/X64 调用堆栈的方法。其中一种用到了SEH 中的RtlVirtualUnwind 函数。