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 0x00400000 // kernel mode only
#define FLG_ENABLE_EXCEPTION_LOGGING 0x00800000 // 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的返回值disposition为ExceptionContinueExecution(继续执行)并且与当初引发异常时的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,但暂时还不清楚这样的目的。