setUnhandleExceptionFilter-exploration

​ 自己用过很多次setUnhandleExceptionFilter出过题目了,题目的思路通常都是突然来一个异常,使得程序最终跳转到setUnhandleExceptionFilter的自定义异常函数中。当然,在调试状态下,如果在自定义异常函数中下断是没法直接到达的,原始是存在系统反调试。

​ 对于怎么绕过,自己一直都没有亲自去调试,只依稀有个印象-程序存在系统反调试。

​ 下面我们就来探究一下怎么绕过系统反调试,到达setUnhandleExceptionFilter的自定义异常函数。实验代码如下,实验工具x64dbg,如果没有某个库的符号,需要进行下载,便于动态调试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LONG _stdcall MyUnhandledExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo)
{
MessageBox(NULL, L"UnhandleExceptionFileter",L"exception", MB_OK);
ExitProcess(0);
}

int main()
{
printf("Hello World!\n");
int a = 0, b = 100, c;
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
c = a + b;
c /= a;
return 0;
}

​ 在将要产生异常的地方下断,便于我们调试。

exception1

​ 同时将seh链的所有异常处理函数下断,我们已经只有在执行了seh链异常处理函数以后才会执行setUnhandledExceptionFilter注册的函数。

exception2

​ 异常处理函数从上到下执行,顺序依次是0x00FB6140->0x00FB3D70->0x77CF9FD0。

​ 发生异常以后,我们来到第一处seh异常处理函数0x00FB6140。

exception3

​ 实现Jmp以后,我们继续F8执行,来到如下图的位置:

exception4

​ 程序首先执行RtlIsValidHandler判断我们的下一出seh是否合法,然后调用RtlExecuteHandlerForException跳转到我们下一出seh函数地址,其中的第5个参数就是我们的下一处异常处理函数地址0x00FB3D70。我们接着进入RtlExecuteHandlerForException,看看他是怎么实现跳转的。

exception5

​ 继续执行。

exception6

​ 通过mov ecx,dword ptr ss:[ebp+18]将第五个参数即我们下一处seh函数地址给了ecx,然后调用。

​ 我们来到第二处异常函数地址0x00FB3D70处,第二处进入第三处的调用过程基本一致。

exception7

​ 我们单步到达上一次到达的RtlIsValidHandler处:

exception8

​ 同理到达第三处,进入except_handler4_common:

exception9

​ 进入CallFilterFunc函数:

exception10

​ 进入第一个call ecx:

exception11

exception12

​ 在上图中,将RtlpUnhandleExceptionFilter带入函数RtlDecodePointer中解析,得到了UnhandlerExceptionFilter的函数地址,最后赋值给了esi,然后调用。

​ 在UnhandlerExceptionFilter函数中,我们通过单步过掉不重要的函数以后,发现了一个可疑函数BasepIsDebugPortPresent,进入,找到了关键的反调试点,如下图:

exception13

​ 如果处于调试中,esi的值会自加1,然后赋值给eax,返回。我们只需要将返回值改为0即可。

​ emmmm,然后我们就能顺利到达最后我们自己创建的那个异常处理函数了。

exception14

exception15

​ 如上图,以后遇到类似的反调试,只需要在NtQueryInformationProcess函数下断就好了。如果写插件绕过,我们可以通过hook BasepIsDebugPortPresent(kernelbase.dll)函数,让其直接返回0即可。