看了xjun师傅关于反调试的教程后,自己进行调试了下异常分发流程,在此记录一下:
测试的代码,分别在VEHandler,SEHandler,TopLevelExceptionFilter这三个中打印信息。
#include<windows.h>
#include<stdio.h>
LONG NTAPI VEH(
struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0xC0000005)
{
printf("this is veh handler\n");
}
return EXCEPTION_CONTINUE_SEARCH;
}
EXCEPTION_DISPOSITION
__cdecl SEhandle(struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext)
{
if (ExceptionRecord->ExceptionCode == 0xC0000005)
{
printf("this is seh handler\n");
}
return ExceptionContinueSearch;
}
LONG MyTopLevelExceptionFilter(
__in struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0xC0000005)
{
printf("this is LastExceptionFilter\n");
}
return EXCEPTION_CONTINUE_EXECUTION;
}
void main()
{
AddVectoredExceptionHandler(0,VEH);
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)MyTopLevelExceptionFilter);
__asm
{
push SEhandle
push fs:[0]
mov fs:[0],esp
mov eax,0
mov dword ptr [eax],1
pop fs:[0]
add esp,4
}
RemoveVectoredExceptionHandler(VEH);
}
首先用Windbg_x64对ntdll(x64)的KiUserExceptionDispatcher下断点:
再对32位dll的KiUserExceptionDispatcher下断点:
然后运行程序,发生异常,触发了断点:
这里的je判断当前是64位的异常还是32位程序的异常,这里是32位程序的异常,所以不跳,执行call wow64!Wow64PrepareForException,然后执行call ntdll!RtlDispatchException将异常传递给32位处理:
这个时候我们在32位的ntdll下的断点已经段下来了,这里就换用x32dbg来调试,windbg这个UI不想吐槽。。虽然功能强大。
这里执行过来先将异常的两个参数ExceptionContext和ExceptionRecord 压栈,然后调用RtlDispatchException。
两个结构体的结构:
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
Context结构太多了不放上来,就是一堆寄存器。
跟进RtlDispatchException:
首先看到压入了ExceptionContext和ExceptionRecord然后执行VEH,跟进RtlpCallVecotredHandlers看见先对VEHAddress进行Decode,然后调用VEH处理
执行完后就已经打印出来了“this is veh handler”。
之后返回之前的CALL RtlpCallVecotredHandlers后面:
判断是否已经处理好了异常:
把在这里Hook掉修改Context和返回的eax就能够返回你想要的EIP。
在继续跟RtlpCallVecotredHandlers后面:
这里获取Sehandler的地址,然后检查handler是否合法::
然后调用RtlpExecuteHandlerForException进行调用SEHandler,跟进去就能看到SEH的调用:
这个call就是调用SEH_handler,之后就返回判断是否已经处理:
这里也可以是一个Hook点进行修改EIP来跳转
没有处理的话,就一直循环SEH链,直到倒数第二个SEH进行内部调用call UnhandledExceptionFilter进行处理,如果还不能处理就停止运行:
跟进这个SEH:
这里才是真的调用UnhandledExceptionFilter:
跟进去发现调用了一次NtQueryInformationProcess查询是否有DebugPort,如果有调试器则把异常给调试器处理,否则则执行TopLevelExceptionHandle进行处理:
这里就是调用TopLevelExceptionHandle,然后再进行判断是否处理,再进行结束进程或者继续运行,如果这里修改context的EIP和返回的值,那么应该也可以实现转移EIP:
Right:,能够跳转
最后返回最开始层:在KiUserExceptionDispatcher中,调用完异常处理后如果处理成功就会调用后面的ZwContinue进行返回
总结一下:
64位系统下windows的异常首先是在ntdll(64位)!KiUserExceptionDispatcher中判断是64位异常还是32位异常
| |
是32位异常 | 64位异常 |
| |
wow64!Wow64PrepareForException-> (x64)ntdll!RtlDispatchException
(x86)ntdll!RtlDispatchException将异常传递给32位处理:
|
|
|
VEH链处理
|
|
|
SEH链处理
|
|
|
倒数第二个SEH CALL UnHandledExceptionFliter (会调用NtQueryInformationProcess查询DebugPort),
然后调用TopLevelExceptionFilter()处理
|
|
|
os最后的异常处理终止进程