# MANUAL — 反调试对抗与强反调试绕过 (Anti-Debug Bypass SOP) > 本文件是当 Agent 在 G0/G1 阶段检测到目标存在**反调试或强对抗机制**时的标准化处置手册。 > 覆盖 Windows 用户态全量反调试技法、通用补丁模板与代理 DLL 框架接入指引。 > > 读取顺序:先通过 `seep_auto_triage` 识别壳类型 → 与本文档对比签名 → 选择对应打桩策略。 --- ## 一、 快速诊断表:常见反调试信号 → 处置路由 | 检测到的信号 | 最可能机制 | 首选处置策略 | |---|---|---| | `IsDebuggerPresent` 出现在导入表 | PEB.BeingDebugged | NOP / 硬编码返回 0 | | `NtQueryInformationProcess` + 0x7 | ProcessDebugPort | Syscall Stub 替换 | | `CheckRemoteDebuggerPresent` | 同上 | Hook 返回 FALSE | | `GetTickCount` / `QueryPerformanceCounter` 频繁调用 | 时序反调试 | 钩子固定返回值 | | `CreateToolhelp32Snapshot` + 进程名比对 | 调试器进程名检测 | Hook 过滤快照列表 | | `OutputDebugString` 后检测错误码 | ODS 陷阱 | 无操作(正常运行不受影响) | | `NtSetInformationThread` 0x11 | ThreadHideFromDebugger | Hook 丢弃该调用 | | 注册表 `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug` | AeDebug 检测 | 值打补丁 / Hook RegQueryValueEx | | 断点扫描 / 自校验 CRC | 内存完整性自检 | 修正 CRC 或 NOP 校验逻辑 | | 驱动 `\Device\DebuggerDetect` / ObRegisterCallbacks | 内核级反调试 | → 参考第五节:内核层对抗 | --- ## 二、 用户态 PEB 系列反调试 —— 最常见(通用补丁) ### 2.1 `IsDebuggerPresent` → 返回值强制为 0 **原理**:`kernel32!IsDebuggerPresent` 本质只读 `PEB.BeingDebugged (offset 0x2)`。 **代理 DLL / Frida 修补(推荐)**: ```javascript // Frida 方案 Interceptor.attach(Module.getExportByName('kernel32.dll', 'IsDebuggerPresent'), { onLeave: function(retval) { retval.replace(0); } }); ``` ```cpp // 内联汇编补丁(静态,适用于代理 DLL) // 定位 IsDebuggerPresent 首地址,写入: // XOR EAX, EAX // RET // 原始字节(x64)通常是: 65 48 8B 04 25 60 00 00 00 → 替换前 3 字节 BYTE patch[] = { 0x33, 0xC0, 0xC3 }; // xor eax,eax; ret WriteProcessMemory(hProc, pIsDebuggerPresent, patch, sizeof(patch), nullptr); ``` ### 2.2 `NtQueryInformationProcess` 信息类 7 (ProcessDebugPort) / 30 (ProcessDebugObjectHandle) **原理**:成功连接调试器时 Port != 0。 ```javascript // Frida 方案 const NtQIP = Module.getExportByName('ntdll.dll', 'NtQueryInformationProcess'); Interceptor.attach(NtQIP, { onEnter(args) { this.cls = args[1].toInt32(); this.out = args[2]; }, onLeave(retval) { if (this.cls === 7 || this.cls === 30) { this.out.writePointer(ptr(0)); // 清零 DebugPort / DebugObject } } }); ``` ### 2.3 `NtSetInformationThread` → ThreadHideFromDebugger (0x11) 调用此系统调用后,调试器事件将对该线程不可见,导致断点失效。 ```javascript const NtSIT = Module.getExportByName('ntdll.dll', 'NtSetInformationThread'); Interceptor.attach(NtSIT, { onEnter(args) { if (args[1].toInt32() === 0x11) { args[1] = ptr(0); // 替换为无害的 ThreadBasicInformation } } }); ``` --- ## 三、 时序反调试 —— 高频坑(修复固定返回值) 调试状态下,`GetTickCount` / `QueryPerformanceCounter` 的推进速度远慢于正常运行,常被用来识别单步调试。 ```javascript // Frida 方案:固定时钟 let baseTick = Date.now(); Interceptor.replace(Module.getExportByName('kernel32.dll', 'GetTickCount'), new NativeCallback(() => { baseTick += 15; // 模拟正常 15ms 系统时钟步进 return baseTick; }, 'uint32', []) ); ``` --- ## 四、 进程名 / 窗口名 / 文件路径特征检测 —— 改名或 Hook 快照 目标程序扫描进程快照(`CreateToolhelp32Snapshot + Process32Next`)比对调试器进程名: `x64dbg.exe`、`ollydbg.exe`、`ida.exe`、`ida64.exe`、`cheatengine.exe` 等。 **最简单处置**:把调试器改名为 `explorer2.exe`、`chrome_x.exe` 等。 **进阶 Hook 方案**: ```javascript // 过滤 Process32Next 快照,抹去调试器进程条目 const blacklist = ['x64dbg.exe', 'ida64.exe', 'ollydbg.exe', 'wireshark.exe']; const Process32Next = Module.getExportByName('kernel32.dll', 'Process32NextW'); Interceptor.attach(Process32Next, { onLeave(retval) { if (retval.toInt32() !== 0) { const entry = this.context.rdx; // PROCESSENTRY32W 结构 const exeName = entry.add(44).readUtf16String(); if (blacklist.some(b => exeName && exeName.toLowerCase().includes(b))) { // 递归调用直到返回非黑名单进程(简化处理:置为无害进程名) entry.add(44).writeUtf16String('System'); } } } }); ``` --- ## 五、 内核级反调试对抗(Ring0 / 驱动层) > **适用场景**:游戏反作弊(ACE/EAC/BattlEye)、强保护商业软件、带 Ring0 驱动的许可校验模块。 > **注意**:内核对抗超出 Agent 全自动范畴,以下为指导性 SOP,需结合 WinDbg + 内核调试器手工处理。 ### G0 阶段主动识别信号: - `triage` 输出中存在 `.sys` 驱动签名或服务注册(HKLM\SYSTEM\CurrentControlSet\Services) - 导入表出现 `NtLoadDriver`、`ZwCreateSection` 等内核接口 - 运行时观察到 `ObRegisterCallbacks` / `PsSetCreateProcessNotifyRoutine` 回调注册 ### 处置策略(按难度升序): | 方案 | 适用条件 | 操作要点 | |---|---|---| | **Kernel Patch Guard 绕过** | 测试用 VM / Hyper-V | 关闭 Driver Signature Enforcement,加载调试补丁驱动 | | **VirtualBox + DKOM** | 沙箱分析 | 修改 `EPROCESS.DebugPort = NULL` 绕过内核调试器检测 | | **全量内存 Dump + 用户态重放** | 驱动已运行 | 在内核加载完毕后 dump 进程内存,在用户态 Loader 中重放 | | **Emulation (Unidbg/QEMU)** | 需脱离真实 OS | 在模拟环境中重跑关键算法,完全规避内核对抗 | --- ## 六、 代理 DLL 注入框架模板(统一打桩方式) > 适用于不能直接修改宿主 EXE(如有数字签名)的场景。 所有上述 Hook 均可集成在以下代理 DLL 框架中,加载即自动注入,宿主签名完好: ```cpp // anti_debug_patch.cpp — 代理 DLL 打桩框架(以 version.dll 为例) #include // 实际 version.dll 导出函数转发(保留原功能) #pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA") // ... 其他导出 void ApplyAntiDebugPatches() { // 1. 清除 PEB.BeingDebugged __asm { mov eax, fs:[30h] // PEB mov byte ptr [eax+2], 0 // BeingDebugged = 0 } // 2. Hook NtQueryInformationProcess (如上 Frida 方案的 C++ 等效) // ... } BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpvReserved) { if (reason == DLL_PROCESS_ATTACH) ApplyAntiDebugPatches(); return TRUE; } ``` --- ## 七、 两击熔断纪律 —— 反调试对抗的止损规则 | 情形 | 触发条件 | 处置 | |---|---|---| | 用户态 Hook 无效(程序仍检测到调试器)| 同构打桩 2 次失败 | 停止,升级为内核层分析 | | 内核层方案部署失败 | 同构 2 次失败 | 停止,改用 Emulation(Unidbg/QEMU)路线 | | Emulation 行为无法覆盖 | 无法还原算法 | 输出"该目标超出当前工具链全自动范畴",转人工分析 |