静态反调试技术
PEB

BeingDebugged
IsDebuggerPresent函数根据PEB中的BeingDebugged来判断进程是否被调试。实例代码如下:
cpp
#include "Windows.h"
int main(int argc, char const *argv[])
{
if (IsDebuggerPresent())
{
MessageBoxW(NULL, L"DebuggerPresent!", L"INFO", MB_OK);
}
else
{
MessageBoxW(NULL, L"Good Job!", L"INFO", MB_OK);
}
return 0;
}
当有调试器时:
txt
0:000> dt _PEB 0000005c`33d6d000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 '' // 该值为1,TRUE
破解方法:
修改IsDebuggerPresent的返回值或者修改BeingDebugged字段为0即可:
txt
0:000> r $peb // 获取PEB的地址
$peb=0000005c33d6d000
0:000> db 0000005c33d6d000 // 查看PEB内存块
0000005c`33d6d000 00 00 01 04 00 00 00 00-ff ff ff ff ff ff ff ff ................
0000005c`33d6d010 00 00 96 00 f7 7f 00 00-20 29 91 d4 fb 7f 00 00 ........ )......
0000005c`33d6d020 b0 59 15 cb d6 01 00 00-00 00 00 00 00 00 00 00 .Y..............
0000005c`33d6d030 00 00 15 cb d6 01 00 00-80 27 91 d4 fb 7f 00 00 .........'......
0000005c`33d6d040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000005c`33d6d050 03 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000005c`33d6d060 00 00 00 00 00 00 00 00-00 00 fb ca d6 01 00 00 ................
0000005c`33d6d070 00 00 00 00 00 00 00 00-20 e2 90 d4 fb 7f 00 00 ........ .......
0:000> ? 0000005c33d6d000+0x2 // 定位到BeingDebugged
Evaluate expression: 396006707202 = 0000005c`33d6d002
0:000> db 0000005c`33d6d002
0000005c`33d6d002 01 04 00 00 00 00 ff ff-ff ff ff ff ff ff 00 00 ................
0000005c`33d6d012 96 00 f7 7f 00 00 20 29-91 d4 fb 7f 00 00 b0 59 ...... ).......Y
0000005c`33d6d022 15 cb d6 01 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000005c`33d6d032 15 cb d6 01 00 00 80 27-91 d4 fb 7f 00 00 00 00 .......'........
0000005c`33d6d042 00 00 00 00 00 00 00 00-00 00 00 00 00 00 03 00 ................
0000005c`33d6d052 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000005c`33d6d062 00 00 00 00 00 00 00 00-fb ca d6 01 00 00 00 00 ................
0000005c`33d6d072 00 00 00 00 00 00 20 e2-90 d4 fb 7f 00 00 01 00 ...... .........
0:000> eb 0000005c`33d6d002 0 // 修改为0
0:000> db 0000005c`33d6d002 // 确定修改
0000005c`33d6d002 00 04 00 00 00 00 ff ff-ff ff ff ff ff ff 00 00 ................
0000005c`33d6d012 96 00 f7 7f 00 00 20 29-91 d4 fb 7f 00 00 b0 59 ...... ).......Y
0000005c`33d6d022 15 cb d6 01 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000005c`33d6d032 15 cb d6 01 00 00 80 27-91 d4 fb 7f 00 00 00 00 .......'........
0000005c`33d6d042 00 00 00 00 00 00 00 00-00 00 00 00 00 00 03 00 ................
0000005c`33d6d052 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000005c`33d6d062 00 00 00 00 00 00 00 00-fb ca d6 01 00 00 00 00 ................
0000005c`33d6d072 00 00 00 00 00 00 20 e2-90 d4 fb 7f 00 00 01 00 ...... .........
Ldr
《逆向工程核心原理》中写道:
调试进程时,其堆内存中会出现一些特殊标识,表示它正处于被调试状态。其中最醒目的是未使用的堆内存中充斥着0xFEEEFEEE。Ldr指向一个结构体,该结构就是在堆中创建的,扫描该区域并检查其中是否存在0xFEEEFEEE,就可以检测进程是否被调试。
这个方法仅在Windows XP中有效,在更高版本的Windows系统中,这个值有其它含义。
0xFEEEFEEE(俗称"Fee Fee"或"No Man's Land"标记)确实是 Windows 堆管理器使用的特殊填充值,但它与进程是否被调试器附加没有必然的因果关系。它的出现是为了帮助检测堆内存错误,而不是作为调试器存在的标志。
这个魔数主要用于堆内存的"未分配"或"已释放"区域,目的是在调试阶段暴露程序逻辑错误。
| 场景 | 填充值 | 含义 |
|---|---|---|
| 未分配堆块 | 0xFEEEFEEE |
堆管理器用此值填充未分配的内存,标记为"未初始化"。 |
| 已释放堆块 | 0xFEEEFEEE |
释放后的内存通常被填充此值,标记为"已回收"。 |
| 堆块间隙 | 0xFDFDFDFD |
用于堆块之间的"No Man's Land"保护带,防止越界。 |
触发机制 :如果你在代码中读取到 0xFEEEFEEE,通常意味着你正在访问一块未初始化的堆内存 或已释放的指针(Dangling Pointer)。这是堆管理器给你的"错误提示",而不是调试器留下的"足迹"。
NtGlobalFlag
当进程被调试时,该字段为:
txt
+0x0bc NtGlobalFlag : 0x70
cpp
#include "Windows.h"
#include "winternl.h"
#define OFFSET_NTGLOBALFLAG 0xbc
#define DEBUGGED_FLAG 0x70
int main(int argc, char const *argv[])
{
PTEB pTEB = NtCurrentTeb();
PPEB pPEB = pTEB->ProcessEnvironmentBlock;
PBYTE pNtGlobalFlag = reinterpret_cast<PBYTE>(pPEB) + OFFSET_NTGLOBALFLAG;
DWORD dwNtGlobalFlag = *(reinterpret_cast<DWORD *>(pNtGlobalFlag));
if (dwNtGlobalFlag == DEBUGGED_FLAG)
{
MessageBoxW(NULL, L"Debugged!!!", L"INFO", MB_OK);
}
else
{
MessageBoxW(NULL, L"Good Job!!!", L"INFO", MB_OK);
}
return 0;
}
修改该字段为0即可过反调试。
txt
0:000> .process
Implicit process is now 0000000c`efa09000
0:000> r $peb
$peb=0000000cefa09000
0:000> dt _PEB 0000000c`efa09000 NtGlobalFlag
ntdll!_PEB
+0x0bc NtGlobalFlag : 0x70
0:000> ? 0000000c`efa09000+0x0bc
Evaluate expression: 55559884988 = 0000000c`efa090bc
0:000> db 0000000c`efa090bc
0000000c`efa090bc 70 00 00 00 00 80 9b 07-6d e8 ff ff 00 00 10 00 p.......m.......
0000000c`efa090cc 00 00 00 00 00 20 00 00-00 00 00 00 00 00 01 00 ..... ..........
0000000c`efa090dc 00 00 00 00 00 10 00 00-00 00 00 00 01 00 00 00 ................
0000000c`efa090ec 10 00 00 00 40 3f 0b a3-f8 7f 00 00 00 00 00 00 ....@?..........
0000000c`efa090fc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000000c`efa0910c 00 00 00 00 98 c8 0a a3-f8 7f 00 00 0a 00 00 00 ................
0000000c`efa0911c 00 00 00 00 58 66 00 00-02 00 00 00 03 00 00 00 ....Xf..........
0000000c`efa0912c 06 00 00 00 00 00 00 00-00 00 00 00 ff 00 00 00 ................
0:000> ed 0000000c`efa090bc 0
0:000> db 0000000c`efa090bc
0000000c`efa090bc 00 00 00 00 00 80 9b 07-6d e8 ff ff 00 00 10 00 ........m.......
0000000c`efa090cc 00 00 00 00 00 20 00 00-00 00 00 00 00 00 01 00 ..... ..........
0000000c`efa090dc 00 00 00 00 00 10 00 00-00 00 00 00 01 00 00 00 ................
0000000c`efa090ec 10 00 00 00 40 3f 0b a3-f8 7f 00 00 00 00 00 00 ....@?..........
0000000c`efa090fc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000000c`efa0910c 00 00 00 00 98 c8 0a a3-f8 7f 00 00 0a 00 00 00 ................
0000000c`efa0911c 00 00 00 00 58 66 00 00-02 00 00 00 03 00 00 00 ....Xf..........
0000000c`efa0912c 06 00 00 00 00 00 00 00-00 00 00 00 ff 00 00 00 ................
NtQueryInformationProcess

使用该函数可以获取各种与进程相关的调试信息。第二个参数是需要获取的信息,并在第三个参数返回。
cpp
typedef enum _PROCESS_INFORMATION_CLASS {
ProcessMemoryPriority,
ProcessMemoryExhaustionInfo,
ProcessAppMemoryInfo,
ProcessInPrivateInfo,
ProcessPowerThrottling,
ProcessReservedValue1,
ProcessTelemetryCoverageInfo,
ProcessProtectionLevelInfo,
ProcessLeapSecondInfo,
ProcessMachineTypeInfo,
ProcessOverrideSubsequentPrefetchParameter,
ProcessMaxOverridePrefetchParameter,
ProcessInformationClassMax
} PROCESS_INFORMATION_CLASS;

ProcessDebugPort

CheckRemoteDebuggerPresent函数在内部调用了NtQueryInformationProcess函数,可以用来检测进程是否被调试。
ProcessDebugObject-Handle

ProcessDebugFlags

对于使用NtQueryInformationProcess函数反调试的函数,有多种方法可以绕过:
- 修改函数的返回值
- Hook该函数
NtQuerySystemInformation
使用该函数可以检测系统是否在调试模式下运行。


NtQueryObject
系统中某个调试器在调试进程时,会创建一个调试对象类型的内核对象。通过查询这个对象是否存在即可判断是否有进程被调试。
cpp
__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryObject(
[in, optional] HANDLE Handle,
[in] OBJECT_INFORMATION_CLASS ObjectInformationClass,
[out, optional] PVOID ObjectInformation,
[in] ULONG ObjectInformationLength,
[out, optional] PULONG ReturnLength
);
第二个参数为需要查询的类型,通过第三个参数返回:
cpp
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation = 0,
ObjectTypeInformation = 2
} OBJECT_INFORMATION_CLASS;
绕过方法同上。
ZwSetInformationThread
使用该函数可以将调试者与被调试者强行分离开来:
cpp
NTSYSAPI NTSTATUS ZwSetInformationThread(
[in] HANDLE ThreadHandle,
[in] THREADINFOCLASS ThreadInformationClass,
[in] PVOID ThreadInformation,
[in] ULONG ThreadInformationLength
);


TLS回调函数
TLS回调函数会先于EP执行,所以可以在TLS回调函数中执行IsDebuggerPresent来判断进程是否被调试。
ETC
除了检测进程本身是否被调试,还可以检测调试器是否存在,比如可以使用FindWindow函数来检测OD或者windbg是否运行,使用快照检测调试器是否运行等等。