该函数的唯一一个参数为异常处理函数指针。当程序发生异常是,且程序不处于调试模式(在VS或者其他调试器里运行)则首先调用该异常处理函数。因此,程序可以主动抛出一个异常来判断当前程序是否正在被调试,这是该程序安装的其中一个异常处理函数,当有异常发生并且当前程序没有被调试的情况下,该异常处理函数将得以执行。
我们回到OD中,看到SetUnhandledExceptionFilter的调用处。
进程处于调试状态时,系统会为它分配一个调试端口,第2个参数传7时,NtQueryInformationProcess()就能获取调试端口的值,调试状态该值为0xFFFFFFFF,正常运行时该值为0。同样,有一个API也可以检测调试端口的值:CheckRemoteDebuggerPresent()。CTRL+G定位到该函数实现部分,发现该函数其实就是内部调用了一下第2个参数为7的NtQueryInformationProcess:(实际调用的Zw版的函数,功能相同)
进程被调试时会生成调试对象,当函数的第2个参数值为0x1E(ProcessDebugObjectHandle)时就能获取调试对象句柄,正常运行时该值为0,调试运行时该值为非0值。
破解方法:如果被调试程序只是调用几次API,而不是在多个地方反复调用,我们可以在调用的时候手动修改特定参数下的调用结果:修改返回值或函数参数指向的内存。像上面的例子一样,我们可以在该函数调用结束的后手动修改参数指向的内存,修改调用结果。当函数调用传参为7/0x1E的时候,修改其第3个参数获取的值:
如果该函数在多个地方被反复调用,那么我们可以采取API HOOK的技术,修改函数内部执行代码的流程。
这里选择将两个反调试全部改成nop(发觉还是不行,后面还有调试)所以这里选择使用od自带的反调试插件。这里还是不行,直接将409150处改成retn
14.还是手动绕过吧
将0x409150函数改成retn]
位置0x4072a3在进入
位置:0x4072c9处nop
位置:0x407437处nop
位置:0x407463处nop
15.进入之后0x40b970处为输入的数据。取出第一个数据放入cl,取出第7个数据放入dl,然后将第一个数据和第七个数据放入0x40b990处
16.0x407488处又取出第七个数据,将数据和0x36异或之后放入到0x40c450处
GetThreadContext这个API。GetThreadContext的原型如下:
BOOL WINAPI GetThreadContext(
__in HANDLE hThread,
__inout LPCONTEXT lpContext
)
typedef struct DECLSPEC_ALIGN(16) _CONTEXT {
...
// Debug registers
//
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7
...
} CONTEXT;
1.peid查壳,没有找到相关信息,od载入出现熟悉的加壳程序提示框。先运行程序,然后搜索字符串。发现关键字符串,但是程序一运行就终止了,下面开始单步分析程序,因为我暂时也没啥办法。
找到关键call
单步到步过这个call的时候程序跑飞,进入这个call
2.这里会对代码自身进行解密操作:地址为从0x407077~0x40715f,将对应的代码进行异或0x34操作
3.下面继续运行,遇到一堆的call我也不知道是干什么的,只知道遇到call就f8步过,遇到向上的跳转就在跳转下面f4,一直单步调试,很顺利的来到了,0x4071bd处,发现这里有个大跳转,可以猜测为跳转到oep.使用ollydump脱壳后发现程序能够正常运行。
peid查壳显示为:
4.od载入程序,搜索字符串,在关键call处下断点
5.od直接运行程序,发现程序直接退出了,可以猜测可能有反调试函数,那就单步运行函数看看反调试函数在哪个位置。在0x40162b处程序跑飞
6.跟进去发现了一个反调试函数,SetUnhandleExceptionFilter函数。
该函数的唯一一个参数为异常处理函数指针。当程序发生异常是,且程序不处于调试模式(在VS或者其他调试器里运行)则首先调用该异常处理函数。因此,程序可以主动抛出一个异常来判断当前程序是否正在被调试,这是该程序安装的其中一个异常处理函数,当有异常发生并且当前程序没有被调试的情况下,该异常处理函数将得以执行。
我们回到OD中,看到SetUnhandledExceptionFilter的调用处。
通过调试可以发现0x401140处为异常处理函数,0x401297处的call eax,eax里面保存的是SetUnhandledExceptionFilter函数的地址。现在我们在0x401297处下断点。
调试状态异常处理的过程:
程序有异常发生,并且当前程序正在被调试,所以,并不会首先调用程序之前设置的入口为0x401140的异常处理函数,而异常转交给调试器处理了,而调试器也无法处理该异常,所以最终调用系统默认的异常处理函数UnhandledExceptionFilter来处理。 所以我们在系统默认的UnhandledExceptionFilter函数处下断点。
7.发现程序没有按照预定的进行运行,发现函数0x401160全为nop
8.执行到 0x40129b处出现错误,由于这里edx的值为全0,所以出现错误,这里将他nop掉就可以,这里参考了别人的writeup.
9.运行到0x4012b4处程序就正常运行起来了。运行到0x4012c6处的时候程序又直接跑飞了。下面对这个函数进行分析。
10跟踪到这程序又崩溃了,这里不能nop掉,因为nop掉之后后面全部都是00了,这里不知道怎么做,再次参考别人的writeup,才知道,原来0x40720D处本来是有一段代码的,只不过每次OlleyDump的时候都会清零,所以只能每次都手动脱壳一次,由于程序会对自身代码在运行的时候进行解密操作,所以就不能用软件断点,这个时候就要考虑到硬件断点的作用啦。
11.在动态调试的时候就可以对程序进行正常的分析了0x40b970存放的是输入的key,这里将输入的key保存到ebp-0xc+eax处,最多为0xa个
12下面继续运行发现一个反调试函数ZwQueryInformationProcess()当然了这里只是保存字符串
运行完call函数之后,eax保存的就是函数地址.
发现运行到这会进入一个死循环,这是别人说是对系统的检查,需要在32位系统上运行。
13这里开始继续分析,在virtualbox里面安装了win32位,程序能够正常运行。发现在32位系统中运行的时候程序之前的那些需要在0x40129b处进行nop操作的地方也没有执行了。在32位系统中运行的时候就能直接跳过第12步的那个死循环。
14.下面会调用
ZwQueryInformationProcess()
反调试函数。调用了两次
该反调试函数简单的介绍:
NTSTATUS WINAPI ZwQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_ PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);
其中第1个参数是要查询的进程的句柄值,第2个参数是要查询进程的哪些信息,该参数是一个枚举类型的值,可以查询的信息如下:
第2个参数不同的值对应于第3个参数指向不同类型的结构体,比如第2个参数填0(ProcessBasicInformation),就表示你想获取PEB的信息,然后就可以判断BeingDebugged,NtGlobalFlag的值了,这里我们主要使用该函数查询2个值:ProcessDebugPort、ProcessDebugObjectHandle,
ProcessDebugFlags对应的枚举值为7和0x1E,0x1F。
进程处于调试状态时,系统会为它分配一个调试端口,第2个参数传7时,NtQueryInformationProcess()就能获取调试端口的值,调试状态该值为0xFFFFFFFF,正常运行时该值为0。同样,有一个API也可以检测调试端口的值:CheckRemoteDebuggerPresent()。CTRL+G定位到该函数实现部分,发现该函数其实就是内部调用了一下第2个参数为7的NtQueryInformationProcess:(实际调用的Zw版的函数,功能相同)
进程被调试时会生成调试对象,当函数的第2个参数值为0x1E(ProcessDebugObjectHandle)时就能获取调试对象句柄,正常运行时该值为0,调试运行时该值为非0值。
破解方法:如果被调试程序只是调用几次API,而不是在多个地方反复调用,我们可以在调用的时候手动修改特定参数下的调用结果:修改返回值或函数参数指向的内存。像上面的例子一样,我们可以在该函数调用结束的后手动修改参数指向的内存,修改调用结果。当函数调用传参为7/0x1E的时候,修改其第3个参数获取的值:
如果该函数在多个地方被反复调用,那么我们可以采取API HOOK的技术,修改函数内部执行代码的流程。
这里选择将两个反调试全部改成nop(发觉还是不行,后面还有调试)所以这里选择使用od自带的反调试插件。这里还是不行,直接将409150处改成retn
14.还是手动绕过吧
将0x409150函数改成retn]
位置0x4072a3在进入
位置:0x4072c9处nop
位置:0x407437处nop
位置:0x407463处nop
15.进入之后0x40b970处为输入的数据。取出第一个数据放入cl,取出第7个数据放入dl,然后将第一个数据和第七个数据放入0x40b990处
16.0x407488处又取出第七个数据,将数据和0x36异或之后放入到0x40c450处
17.ox40757a处通过GetThreadContext进行反调试
GetThreadContext这个API。GetThreadContext的原型如下:
BOOL WINAPI GetThreadContext(
__in HANDLE hThread,
__inout LPCONTEXT lpContext
)
传入目标线程的句柄,就能得到上下文。
简单版本
typedef struct DECLSPEC_ALIGN(16) _CONTEXT {
...
// Debug registers
//
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7
...
} CONTEXT;
Drx ,就是硬件调试寄存器,其中Dr0-3,就是对应OD里看到的四个硬件断
点,而 Dr7 则是状态控制寄存器。简单来说,如果用 OD 下了硬件断点,那么这五个寄存器
的值就至少有两个不为0。
这里应该将0x407587处的给nop掉,不然程序又会崩溃(看来得好好研究一下反调试技术)
18.这里遇到向上的跳转就需要nop掉,不然程序有会崩溃
19.这里用0x40c450处的值和0x36进行比较,应该相等,0x40c450=input[6]^0x36;说明输入的字符的第七位应该为0。key长度为6位
20.下面取出输入的第一个字符:0x40b990处存放的是输入的第一个字符
0x407614处的call要跟进去,下面遇到的call都最好跟进去(这里都是短跳转)
1)ror al,0x6,然后将al放入[0x40b000]处
然后rol al,0x4,将al 放入[0x40b001]处
xor al,0x34,将al放入[0x40b002]处
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-1-17 19:52
被wwzzww编辑
,原因: