第51章 静态反调试技术
本文最后更新于:2022年5月27日 下午
本章我们将学习静态组别中的反调试技术,了解各技术的工作原理,并学习相应的破解之法。
51.1 静态反调试的目的
被调试进程用静态反调试技术来侦测自身是否处于被调试状态,若侦测到处于被调试状态,则执行非常规代码(主要是终止代码)来阻止。具体的实现方法包括调试器检测方法、调试环境检测方法、强制隔离调试器的方法等等。反调试破解方法主要用来从探测代码获取信息,然后修改信息本身使反调试技术失效。
许多静态反调试技术对OS有较强依赖性。这意味着静态反调试技术在Windows XP系统下可以正常使用,而在Windows Vista/7操作系统中可能失效。本章所有练习示例在Windows XP中顺利通过测试。
51.2 PEB
利用PEB结构体信息可以判断当前进程是否处于被调试状态。这些信息值得信赖、使用方便,所以广泛应用于反调试技术。
PEB结构体的成员中与反调试技术密切相关的成员如代码51-2所示。
1 |
|
BeingDebugged成员是一个标志(Flag),用来表示进程是否处于被调试状态。Ldr、ProcessHeap、NtGlobalFlag成员与被调试进程的堆内存特性相关。
接下来分别讲解以上4个PEB成员。
借助FS段寄存器所指的TEB结构体可轻松获取进程的PEB结构体地址。TEB.ProcessEnvironmentBlock成员(偏移为+0x30)指向PEB结构体地址,有以下2种方法可以获取PEB结构体的地址。
(1)直接获取PEB的地址
MOV EAX, DWORD PTR FS: [0x30]; FS[0x30] = address of PEB
(2) 先获取TEB地址,再通过ProcessEnvironmentBlock成员(偏移为+0x30 )获取PEB地址
MOV EAX, DWORD PTR FS: [0x18] ; FS[0x18] = address of TEB
MOV EAX, DWORD PTR DS: [EAX+0x30]; DS[EAX+0x30] = address of PEB
第二种方法其实是第一种方法的展开形式,二者都通过TEB.ProcessEnviromnentBlock成员的值来获取PEB结构体的地址。更详细的说明请参考第46、47章。
51.2.1 BeingDebugged(+0x2)
进程处于调试状态时,PEB.BeingDebugged成员(+0x2)的值被设置为1 (TRUE);进程在非调试状态下运行时,其值被设置为0 ( FALSE )。
lsDebuggerPresent()
IsDebuggerPresent() AIM获取PEB.BeingDebugged的值来判断进程是否处于被调试状态。直 接查看其代码可以更清楚地理解它(我的系统环境中,PEB的起始地址为7FFD9000 ),如图51-1所示。
IsDebuggerPresent() API代码非常简单,先获取TEB结构体的地址(FS:[18]),再通过TEB.ProcessEnvironmentBlock成员(+0x30)获取PEB的地址,然后访问PEB.BeingDebugged成员
(+0x2)。如图51-1所示,PEB的地址为7FFD7000, PEB.BeingDebugged成员的地址为7FFDF002。因当前正用OllyDbg调试进程,故BeingDebugged的值为1 (TRUE)。
-
破解之法
只要借助OllyDbg调试器的编辑功能,将PEB.BeingDebugged的值修改为0 ( FALSE )即可。
51.2.2 Ldr(+0xC)
该方法仅适用于Windows XP系统,而在Windows Vista以后的系统中则无法使用。另外,利用附加功能将运行中的进程附加到调试器时,堆内存中并不出现上述标识。
调试进程时,其堆内存区域中就会岀现一些特殊标识,表示它正处于被调试状态。其中最醒目的是,未使用的堆内存区域全部填充着0xFEEEFEEE,这证明正在调试进程。利用这一特征即可判断进程是否处于被调试状态。
PEB.Ldr成员是一个指结构体的指针,结构体恰好是在堆内存区域中创建的,所以扫描该区域即可轻松查找是否存在0xFEEEFEEE区域(我的系统环境中,PEB的起始地址为7FFD9000 ),如图51-2所示。
进入PEB.Ldr地址(241EA0),向下拖动滑动条,查找0xFEEEFEEE区域,如图51-3所示。
在堆内存中可以看到填充着0xFEEEFEEE的区域。
-
破解之法
只要将填充着0xFEEEFEEE值的区域全部覆写为NULL即可。
51.2.3 Process Heap(+0x18)
该方法仅在WindowsXP系统中有效,Windows7系统不存在以上特征。此外,将运行中的进程附加到调试器时,也不会出现上述特征
PEB.ProcessHeap成员(+0x18)是指向HEAP结构体的指针。
1 |
|
以上列岀了HEAP结构体的部分成员,进程处于被调试状态时,Flags(+0xC)与Force Flags成员(+0x10)被设置为特定值。
GetProcessHeap()
PEB.ProcessHeap成员(+0x18)既可以从PEB结构体直接获取,也可以通过GetProcessHeap()API获取。下面看看GetProcessHeap() API的代码(我的系统环境中,PEB的起始地址为7FFDF000)。
GetProcessHeap() API 的代码基本类似于 IsDebuggerPresent(),按照 TEB → PEB →
PEB.ProcessHeap顺序依次访问。
图51-4中进程HEAP结构体的地址为PEB.ProcessHeap=140000。
Flags(+0xC) & Force Flags(+0x10)
进程正常运行(非调试运行)时,Heap.Flags成员(+0xC)的值为0x2,Heap.ForceFlags成员 (+0x10)的值为0x0。进程处于被调试状态时,这些值也会随之改变(参考图51-5.)。
所以,比较这些值就可以判断进程是否处于被调试状态。
-
破解之法
只要将HEAP.Flags与HEAP.ForceFlags的值重新设置为2与0即可(HEAP.Flags=2,HEAP.ForceFlags=0)。
51.2.4 NtGlobalFlag(+0x68)
调试进程时,PEB.NtGlobalFlag成员(+0x68)的值会被设置为0x70。所以,检测该成员的值即可判断进程是否处于被调试状态(我的系统环境中,PEB的起始地址为7FFD7000),如图51-6所示。
NtGlobalFlag 0x70是下列Flags值进行bit OR (位或)运算的结果。
1 |
|
被调试进程的堆内存中存在(不同于非调试运行进程的)特别标识,因此在PEB.NtGlobalFlag成员中添加了上述标志。
-
破解之法
重设PEB.NtGlobalFlag值为0即可(PEB.NtGlobalFlag=0 )。
将运行中的进程附加到调试器时,NtGlobalFlag的值不变。
51.2.5 练习:StaAD_PEB.exe
下面调试示例文件StaAD_PEB.exe来学习基于PEB的反调试技术,以及相应的破解方法。在OllyDbg中按F9键运行StaAD_PEB.exe文件,如图51-7所示,所有项都显示当前进程处于调试之中,基于PEB的反调试功能工作正常。
51.2.6 破解之法
下面介绍在OllyDbg中破解PEB反调试技术的方法。按Ctrl+F2重启OllyDbg后,直接到main()函数的起始地址处(401000)。
PEB.BeingDebugged
跟踪调试代码,在401036地址处遇到调用IsDebuggerPresent()API的代码,如图51-8所示。使用StepInto(F7)命令跟踪进入API,出现图51-1所示的代码。只要将PEB.BeingDebugged值修改为0,
即可破解基于BeingDebugged检测的反调试技术。
PEB.Ldr
继续调试会遇到PEB.Ldr反调试代码,如图51-9所示。
接下来简单讲解代码。40107B地址处的CALL EAX指令用来调用ntdll.NtCurrentTeb() API,40107D地址处的MOV指令用来将PEB地址保存到EBX寄存器。地址40109040109E间的指令用来将局部变量([EBP-20][EBP-2C])初始化为EEFEEEFE值。而4010A1地址处的MOV指令用来将PEB.Ldr地址存储到ESI寄存器。继续跟踪到4010C7地址处,如图51-10所示。
地址4010B0~4010DA间的代码由循环构成。下面看看4010C7地址处的CMP EDI,DWORD PTR DS:[ECX]
指令。EDI寄存器中存储着从PEB.Ldr地址读取的4个字节值,[ECX]中的值为EEFEEEFE (ECX寄存器中存储着初始化为EEFEEEFE的数组的起始地址)。也就是说,图51-10中的代码用来查找PEB.Ldr中初始化为EEFEEEFE的区域。
该调试探测技术的破解之法是:先转到(4010C5地址的)EDX寄存器所指的PEB.Ldr,然后查找EEFEEEFE区域并用NULL值覆盖。
选中PEB.Ldr下的整个EEFEEEFE区域,在OllyDbg菜单栏中依次选择“Binary - Fill with 00’s”菜单填充(如图51-11所示)。按F2键在4010FB地址处设置断点,然后按F9运行即可安全跳出循环。
PEB.ProcessHeap
继续调试,遇到图51-12所示的代码。
以上代码通过检测 PEB.ProcessHeap.Flags 与 PEB.ProcessHeap.ForceFlags 的值来反调试。401112地址处的MOV指令用来将PEB.ProcessHeap结构体的首地址转移到EDI寄存器(图51 -12内存窗口显示的地址为150000 )。地址401115处的[EDI+C]是PEB.ProcessHeap.Flags值,将该值修改为2。地址40113F处的[EDI+10]是PEB.ProcessHeap.ForceFlags值,将该值修改为0。这样修改就能破解基于PEB.ProcessHeap的反调试代码。
PEB.NtGlobalFlag
继续调试,遇到基于PEB.NtGlobalFlag的反调试代码,如图51-13所示。
地址401168处的[EBX+68]即为PEB.NtGlobalFlag,将其值修改为0即可破解反调试代码
请注意:在Windows XP中使用OllyDbg开始调试程序时,EBX寄存器中存储的是PEB的地址。
51.3 NtQuerylnformationProcess()
下面介绍另外一种利用NtQueryInformationProcess() API探测调试器的技术。通过NtQueryInformationProcess() API可以获取各种与进程调试相关的信息,该函数定义如代码51-4所示。
1 |
|
为 NtQueryInformationProcess()函数的第二个参数 PROCESSINFOCLASS ProcesslnformationClass指定特定值并调用该函数,相关信息就会设置到其第三个参数PVOID Processlnformation。PROCESSINFOCLASS是枚举类型,拥有的值如代码51-5所示。
1 |
|
以上代码中与调试器探测有关的成员为ProcessDebugPort(0x7)、ProcessDebugObject-Handle(0x1E)、ProcessDebugFlags(0x1F)。
51.3.1 ProcessDebugPort(0x7)
进程处于调试状态时,系统就会为它分配1个调试端口(Debug Port )。 ProcessInformationClass参数的值设置为ProcessDebugPort(0x7)时,调用NtQueryInformationProcess()函数就能获取调试端口。若进程处于非调试状态,则变量dwDebugPort的值设置为0;若进程处于调试状态,则变量dwDebugPort的值设置为0xFFFFFFFF (参考代码51-6 )。
1 |
|
CheckRemoteDebuggerPresent()
CheckRemoteDebuggerPresent() API与IsDebuggerPresent() API类似,用来检测进程是否处于调试状态。CheckRemoteDebuggerPresent()函数不仅可以用来检测当前进程,还可以用来检测其他进程是否处于被调试状态。进入CheckRemoteDebuggerPresent() API查看代码,可以看到其调用了NtQuerylnformationProcess(ProcessDebugPort) API (参见图51-14 )。
51.3.2 ProcessDebugObjectHandle(0x1E)
调试进程时会生成调试对象(Debug Object )。函数的第二个参数值为ProcessDebugObjectHandle(0x1E)时,调用函数后通过第三个参数就能获取调试对象句柄。进程处于调试状态时,调试对象句柄的值就存在;若进程处于非调试状态,则调试对象句柄值为NULL。
1 |
|
51.3.3 ProcessDebugFlags(0x1F)
检测Debug Flags (调试标志)的值也可以判断进程是否处于被调试状态。函数的第二个参数设置为ProcessDebugFlags(0x1F)时,调用函数后通过第三个参数即可获取调试标志的值:若为0,则进程处于被调试状态;若为1,则进程处于非调试状态。
1 |
|
51.3.4 练习:StaAD_NtQIP.exe
下面通过StaAD_NtQIP.exe示例程序练习基于NtQueryInformationProcess()函数的反调试。在OllyDbg调试器中运行示例程序后,借助NtQueryInformationProcess()反调试技术显示“探测到调试器”的信息,如图51-15所示。
51.3.5 破解之法
要想破解使用NtQueryInformationProcess() API探测调试器的技术,应当对该函数在特定参数值(ProcessInformationClass )下输出的值(返回-Processlnformation )进行操作(参考代码51-4 )。
特定参数值是前面提过的 ProcessDebugPort ( 0x7 )、ProcessDebugObjectHandle ( 0x1E )、
ProcessDebugFlags ( 0x1F )。
若只是调用几次API,则可以在调试器中手动操作输岀值。相反,若函数被反复调用,则需要使用API钩取技术。在练习中我们将使用OllyDbg的汇编命令手动设置钩取代码。
此处介绍的使用API钩取破解反调试的方法只是为了说明相关概念与原理。实际操作中直接使用相应的调试器插件(如:advanced olly)即可解决问题。每次在插件中启动调试时,都会自动钩取API。
首先重新运行OllyDbg调试器。
确定钩取函数的位置
使用DLL注入技术钩取API时,钩取函数一般位于要注入的DLL文件内部。为了操作方便,我们将钩取代码设置在代码节区中的最后一个NULL Padding区域——407E00地址处,如图51-16所示。
修改原API代码
进入原NtQueryInformationProcess() API代码,如图51 -17所示。
在该处设置一条JMP指令,用来跳转到钩取函数地址处(407E00)。利用OllyDbg的汇编功能将7C93D7EA地址处的代码修改为JMP 00407E00
指令,如图51 -18所示。
该JMP指令为5个字节,可以准确覆写原代码中的CALL DWORD PTR DS:[EDX]
& RETN 14
指令(位于地址77F06092~77F06097)。
钩取API时,一般要在原API起始地址处设置JMP指令。以上面这种情形为例,JMP指令要设置在77F06088地址处,但是我却将JMP命令设置在略微偏下的地址处(7C93D7EA),这是为了回避某些PE保护器的API钩取探测功能。这些PE保护器会检测NtQueryInformationProcess() API起始地址的第一个字节,若非“B8”,则认为该API被钩取,就会执行某些非正常运行的行为(也算是一种调试器探测技术)。当然,如果采用更精巧的API钩取探测技术,那么上面这种回避方法就会失效,必须采用其他更好的方法。
编写钩取函数
在407E00地址处编写钩取函数,如图51-19所示。
地址407E00处的CALL DWORD PTR DS:[EDX]
指令与地址407E3B处的RETN 14
指令都是原NtQuerylnformationProcess() API中的代码,钩取代码就设置在这2条指令之间。
407C03地址之后的CMP/JNZ指令组合类似于C语言中的switch/case多分支选择语句。ProcessInformationClass参数(DWORD PTR SS:[ESP+C])值为0x7、0x1E、0x1F之一时,则将Processlnformation参数(DWORD PTR SS:[ESP+10])地址所指的返回值分别修改为0、0、1。在该状态下(在调试器中)运行进程,即可破解基于NtQueryInformationProcess() API的反调试技术,如图51-20所示。
51.4 NtQuerySystemlnformation()
下面介绍基于调试环境检测的反调试技术。
前面介绍的反调试技术中,我们通过探测调试器来判断自己的进程是否处于被调试状态,这是一种非常直接的调试器探测方法。除此之外,还有间接探测调试器的方法,借助该方法可以检测调试环境,若显露出调试器的端倪,则立刻停止执行程序。运用这种反调试技术可以检测当前OS是否在调试模式下运行
为了使用WinDbg工具调试系统内核(Kernel Debugging ),需要先准备2个系统(Host、Target)并连接(Serial、1394、USB、Direct Cable)。其中,Target 的OS 以调试模式运行,连接到Host系统的WinDbg上后即可调试。
设置调试模式的方法
(1) Windows XP:编辑 “C:\boot.ini” 后重启
1 |
|
(2) Windows 7:使用 bcdedit.exe 实用程序
1 |
|
ntdll!NtQuerySystemInformation()API是一个系统函数,用来获取当前运行的多种OS信息。
1 |
|
SYSTEM_INFORMATION_CLASS SystemlnformationClass参数中指定需要的系统信息类型,将某结构体的地址传递给PVOID Systemlnformation参数,API返回时,该结构体中就填充着相关信息。
SYSTEM_INFORMATION_CLASS是枚举类型,拥有的值如代码51-10所示。
1 |
|
向SystemlnformationClass参数传入SystemKernelDebuggerInformation( 0x23 ),即可判断岀
当前OS是否在调试模式下运行。
51.4.1 SystemKernelDebuggerlnformation(0x23)
查看实际的反调试源代码即可轻松掌握其工作原理。
1 |
|
在上述代码中调用NtQuerySystemInformation() API时,第一个参数(SystemlnformationClass)的值设置为 SystemKernelDebuggerInformation(0x23),第二个参数(Systemlnformation)为SYSTEM_KERNEL_DEBUGGER_INFORMATION结构体的地址。当API返回时,若系统处在调试模式下,则 SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnabled的值设置为 1)SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerNotPresent的值恒为1)。
51.4.2 练习:StaAD_NtQSI.exe
运行练习示例StaAD_NtQSI.exe,如图51 -21所示。
我的测试环境启动时默认处于调试模式,所以运行示例程序后显示“探测到调试环境”的信息。
51.4.3破解之法
在Windows XP系统中编辑boot.ini文件,删除 /debugport=com1 /baudrate=115200 /Debug
值。在Windows 7系统的命令行窗口执行bcdedit/debug off
命令即可。并且,若重启系统则要以正常模式(Normal Mode)启动。
问题:可以Hook吗?
51.5 NtQueryObject()
系统中的某个调试器调试进程时,会创建1个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程正在被调试。
ntdll!NtQueryObject() API用来获取系统各种内核对象的信息,NtQueryObject()函数的定义如下:
1 |
|
调用 NtQueryObject()函数时,先向第二个参数OBJECT_NFORMATION_CLASS ObjectlnformationClass
赋予某个特定值,调用API后,包含相关信息的结构体指针就被返回第三
个参数PVOID Objectlnformation。
OBJECT_INFORMATION_CLASS是枚举类型,其拥有的值如代码51-13所示。
1 |
|
首先使用ObjectAllTypesInformation值获取系统所有对象信息,然后从中检测是否存在调试对象。NtQueryObject() API使用方法略为复杂。
NtQueryObject() API使用方法
(1) 获取内核对象信息链表的大小
1 |
|
(2) 分配内存
1 |
|
(3) 获取内核对象信息链表
1 |
|
调用NtQueryObject()函数后,系统所有对象的信息代码就被存入pBuf,然后将pBuf转换(casting)为POBJECT_ALL_INFORMATION类型。OBJECT_ALL_INFORMATION结构体由OBJECT_TYPE_INFORMATION结构体数组构成。实际内核对象类型的信息就被存储在OBJECT_TYPE_INFORMATION结构体数组中,通过循环检索即可查看是否存在“调试对象”对象类型。
(4)确定“调试对象”对象类型
为便于理解,请先看下面一段代码。
1 |
|
练习:StaAD_NtQO.exe
在OllyDbg调试器中运行示例程序StaAD_NtQO.exe,显示“程序处于调试中”的信息,如图51-22所示。这是因为在NtQueryObject()API中探测到了调试对象。
- 破解之法
按Ctrl+F2键重新运行OllyDbg调试器,在401059地址处按F2键设置断点,然后按F9键运行程序。
位于401059地址处的CALLESI指令用来调用ntdll.ZwQueryObject()API,如图51-23所示。此时查看栈可以发现,第二个参数的值为ObjectAllTypesInformation(3),将该值修改为0后再执行401059地址处的指令,这样就无法探测到调试器的存在了。
当然,直接钩取ntdll.ZwQueryObject() API,输入ObjectAllTypesInformation(3)值或操作结果值,也能不被探测到。
51.6 ZwSetlnformationThread()
下面介绍强制分离(Detach )被调试者和调试器的技术。利用ZwSetInformationThread() API, 被调试者可将自身从调试器中分离出来。
1 |
|
ZwSetInformationThread()函数是一个系统原生API(System Native API),顾名思义,它是用来为线程设置信息的。该函数拥有2个参数,第一个参数ThreadHandle用来接收当前线程的句柄,第二个参数ThreadlnformationClass表示线程信息类型,若其值设置为ThreadHideFromDebugger(0x11),调用该函数后,调试进程就会被分离出来。ZwSetInformationThread() API不会对正常运行的程序(非调试运行)产生任何影响,但若运行的是调试器程序,调用该API将使调试器终止运行,同时终止自身进程。
51.6.1 练习:StaAD_ZwSIT.exe
首先在OllyDbg调试器中打开示例程序StaAD_ZwSIT.exe,然后分别在401027与401029地址处按F2键设置断点,按F9运行程序。
如图51-24所示,调试器在401027地址的断点处暂停,位于该地址处(401027)的CALL ESI指令用来调用ntdll.ZwSetlnformationThread() API。按F9键继续执行401027地址处的指令,这样就会分离出被调试进程并终止运行。而且,OllyDbg调试器将无法正常调试401029地址处的指令,出现运行错误。
51.6.2破解之法
简单的破解思路是:调用401027地址处的ZwSetInformationThread()API前,查找存储在栈中的第二个参数ThreadlnformationClass值,若其值为ThreadHideFromDebugger(0xl 1),则修改为0后继续运行即可。
当然也可以钩取ZwSetInformationThread()API,并以同样方式操作函数的参数。
利用ZwSetInformationThread()进行反调试的工作原理是:将线程隐藏起来,调试器就接收不到信息,从而无法调试。另外,Windows XP以后新增了 DebugActiveProcessStop() API。
1 |
|
DebugActiveProcessStop() API用来分离调试器和被调试进程,从而停止调试。而前面介绍的ZwSetlnformationThread() API则用来隐藏当前线程,使调试器无法再收到该线程的调试事件,最终停止调试(2个API易混淆,需牢记)。
51.7 TLS回调函数
TLS回调函数是反调试技术中常用的函数,像前面介绍的技术一样,如果不明白其工作原理,使用时就会束手无策。
其实,我们并不能将TLS回调本身看作一种反调试技术,但是由于回调函数会先于EP代码执行,所以反调试技术中经常使用它。在TLS回调函数内部使用IsDebuggerPresent() 等函数判断调试与否,然后再决定是否继续运行程序。
第45章中对反调试相关内容与破解之法做了详细讲解,请各位参考。
51.8 ETC
首先,要明白我们应用反调试技术的目的在于防止程序遭受逆向分析。不必非得为此费力判断自身进程是否处于被调试状态。一个更简单、更好的方法是,判断当前系统是否为逆向分析专用系统(非常规系统),若是,则直接停止程序。这样就出现了各种各样的反调试技术,这些技术都能从系统中轻松获取各种信息(进程、文件、窗口、注册表、主机名、计算机名、用户名、环境变量等)。这些反调试技术通常借助Win32 API获取系统信息来具体实现。下面简单介绍几个例子。
(1) 检测OllyDbg窗口 ← FindWindow()。
(2) 检测OllyDbg进程 ← CreateToolhelp32Snapshot()。
(3) 检查计算机名称是否为 “TEST”、“ANALYSIS” 等 ← GetComputerName()。
(4) 检查程序运行路径中是否存在“TEST”、“SAMPLE”等名称 ← GetCommandLine()。
(5) 检测虚拟机是否处于运行状态(查看虚拟机特有的进程名称—VMWareService.exe、VMWareTray.exe、VMWareUser.exe等)。
上述这些反调试技术的破解之法并不难,所以著名的保护器中并不会使用它们(更棒的反调试技术多得是)。但偶尔有一些不怎么出名的保护器/压缩器会使用,恶意代 码中也经常用到。如果平时不在意这些,那么很有可能会被它们“套住”,白白浪费许多时间。
51.8.1 练习:StaAD_FindWindow.exe
首先启动OllyDbg调试器,然后双击运行StaAD_FindWindow.exe程序,命令行窗口中就会显示“探测到调试器”的信息,如图51-25所示。
StaAD_FindWindow.exe代码中调用了FindWindow()与GetWindowText() API,探测是否存在指定名称(OllyDbg、IDA Pro、WinDbg等)的调试器窗口。
51.8.2破解之法
首先在OllyDbg调试器中打开练习文件,然后在401023地址处设置好断点并运行程序,如图51-26所示。
图51-26的代码中共有3处调用FindWindow() API。40101E地址处的PUSH409D10指令中,地 址409D10指向Window Class名称字符串,它是FindWindow() API的第一个参数。转到409D10地址处,使用NULL覆盖Window Class名称字符串缓冲区,那么FindWindow() API将无法探测到相应调试器。
接下来要使GetWindowText() API失效。在401093地址处设置好断点并运行程序,如图51-27所示。
调用GetWindowTextW() API的代码在4010B4地址处。若想正常调用GetWindowTextW() API,就不能执行4010AD地址处的条件跳转指令。要实现这一点,可以直接操作条件跳转语句,也可 以将其上GetDesktopWindow()与GetWindow() API的返回值(EAX寄存器)修改为NULL值。当然,钩取FindWindow() API与GetWindowText() API也是非常棒的方法。
51.9小结
本章讲解了静态反调试的方法。其实,除了本章介绍的方法外,还有很多其他方法,而且调试过程中还会遇到更多,这些反调试方法你可能之前从未见过,只要认真分析、查找相关资料,一般都能找到好的破解之道,这是积累经验、不断进步的必经之路。本章还说明了静态反调试技术的破解之法,这些方法虽然不太难,但若完全不了解,调试时可能遭受很大困难。
反调试技术对OS有很强的依赖性,所以应用某个反调试技术时要事先确认:它是否可以应用到目标操作系统。实际调试中会使用多种调试器插件,借助这些插件可以有效回避反调试技术,使用起来非常方便。但调试器的插件也不是万能的,它们无法破解某些反调试技术。此时,了解这些插件的工作原理、学习基本的破解之法就显得非常有用了。