Files

191 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <Windows.h>
// 实际 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 行为无法覆盖 | 无法还原算法 | 输出"该目标超出当前工具链全自动范畴",转人工分析 |