自己用过很多次setUnhandleExceptionFilter出过题目了,题目的思路通常都是突然来一个异常,使得程序最终跳转到setUnhandleExceptionFilter的自定义异常函数中。当然,在调试状态下,如果在自定义异常函数中下断是没法直接到达的,原始是存在系统反调试。
对于怎么绕过,自己一直都没有亲自去调试,只依稀有个印象-程序存在系统反调试。
下面我们就来探究一下怎么绕过系统反调试,到达setUnhandleExceptionFilter的自定义异常函数。实验代码如下,实验工具x64dbg,如果没有某个库的符号,需要进行下载,便于动态调试。
1 | LONG _stdcall MyUnhandledExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo) |
在将要产生异常的地方下断,便于我们调试。
同时将seh链的所有异常处理函数下断,我们已经只有在执行了seh链异常处理函数以后才会执行setUnhandledExceptionFilter注册的函数。
异常处理函数从上到下执行,顺序依次是0x00FB6140->0x00FB3D70->0x77CF9FD0。
发生异常以后,我们来到第一处seh异常处理函数0x00FB6140。
实现Jmp以后,我们继续F8执行,来到如下图的位置:
程序首先执行RtlIsValidHandler判断我们的下一出seh是否合法,然后调用RtlExecuteHandlerForException跳转到我们下一出seh函数地址,其中的第5个参数就是我们的下一处异常处理函数地址0x00FB3D70。我们接着进入RtlExecuteHandlerForException,看看他是怎么实现跳转的。
继续执行。
通过mov ecx,dword ptr ss:[ebp+18]将第五个参数即我们下一处seh函数地址给了ecx,然后调用。
我们来到第二处异常函数地址0x00FB3D70处,第二处进入第三处的调用过程基本一致。
我们单步到达上一次到达的RtlIsValidHandler处:
同理到达第三处,进入except_handler4_common:
进入CallFilterFunc函数:
进入第一个call ecx:
在上图中,将RtlpUnhandleExceptionFilter带入函数RtlDecodePointer中解析,得到了UnhandlerExceptionFilter的函数地址,最后赋值给了esi,然后调用。
在UnhandlerExceptionFilter函数中,我们通过单步过掉不重要的函数以后,发现了一个可疑函数BasepIsDebugPortPresent,进入,找到了关键的反调试点,如下图:
如果处于调试中,esi的值会自加1,然后赋值给eax,返回。我们只需要将返回值改为0即可。
emmmm,然后我们就能顺利到达最后我们自己创建的那个异常处理函数了。
如上图,以后遇到类似的反调试,只需要在NtQueryInformationProcess函数下断就好了。如果写插件绕过,我们可以通过hook BasepIsDebugPortPresent(kernelbase.dll)函数,让其直接返回0即可。