### Windows 下有哪些常用的反调试技术

1. 基于 Windows API 的反调试

这是最常见、最容易实现的反调试技术，利用 Windows 系统提供的特定函数来查询进程状态

- `IsDebuggerPresent`： 这是最直接、最经典的 API。它位于 `kernel32.dll` 中，会检查 进程环境块 (PEB) 中的 `IsDebugged` 标志位。如果该位被设置为 `1`，函数就返回 `TRUE`。攻击者只需要简单地调用这个函数，然后根据返回值决定是否执行恶意代码
- `CheckRemoteDebuggerPresent`： 这个 API 用来检查另一个进程是否正在被调试。它通常用于一个父进程检查其子进程是否被调试。攻击者可以启动一个子进程，然后父进程不断调用此 API 来监视子进程的状态
- `OutputDebugString`： 这个函数原本用于向调试器输出调试信息。如果程序在没有调试器附加的情况下调用此函数，并随后调用 `GetLastError`，返回的错误码通常是 `ERROR_NOT_ENOUGH_MEMORY`（`0x08`）。如果返回其他值，则很可能存在调试器
- `NtQueryInformationProcess`： 这是一个更底层的、功能强大的未公开（undocumented）API。通过查询 `ProcessDebugPort`、`ProcessDebugObject` 或 `ProcessDebugFlags` 等信息，可以精确地判断程序是否被调试。这是许多高级反调试技术的基石，因为不像 `IsDebuggerPresent`，它更难被简单地 Hook 或篡改

2. 基于异常和 SEH（结构化异常处理） 的反调试

调试器在处理异常时与正常程序有不同的行为，这为反调试提供了可乘之机

- `INT 3` 断点： 调试器通常通过 `INT 3` (opcode `0xCC`) 指令来设置软件断点。攻击者可以在代码中故意插入一个 `INT 3` 指令，并设置自己的异常处理器。如果程序在执行 `INT 3` 后进入了预设的异常处理器，说明没有调试器存在（因为调试器会捕获 `INT 3` 并暂停程序）。如果程序没有进入异常处理器，就说明有调试器存在
- `Trap Flag (TF)` 检测： 当调试器设置了硬件断点或启用单步执行时，CPU 的 EFLAGS 寄存器中的 `Trap Flag` 会被设置。攻击者可以通过内联汇编代码来检查这个标志位，判断是否正在进行单步调试
- 利用 `VEH`（向量化异常处理）： `VEH` 是比 `SEH` 更早被调用的异常处理机制。攻击者可以在 `VEH` 中设置反调试逻辑，因为它更难被调试器忽略或绕过

3. 基于时间差和指令计数的反调试

调试器在执行单步调试或设置断点时，会引入额外的延迟。正常程序可以利用这个时间差来检测调试器的存在

- `RDTSC` (Read Time-Stamp Counter)： 这是一个 CPU 指令，用于读取 CPU 的时间戳计数器。攻击者可以在代码中的两个点调用 `RDTSC`，并计算两次调用之间的时间差。如果这个时间差异常地长，很可能是因为调试器在单步执行或处理断点
- `QueryPerformanceCounter`： 这是一个高精度的 Windows API。与 `RDTSC` 类似，攻击者可以调用两次这个 API 并计算时间差。如果时间差超过一个阈值，则认为存在调试器
- 线程休眠检测： 当程序进入休眠状态时，调试器通常会唤醒它以便继续执行。攻击者可以调用 `Sleep()` 函数，然后检查实际休眠的时间是否与期望的时间相符。如果实际休眠时间比期望的短，说明调试器可能干预了线程的运行

4. 基于进程和线程状态的反调试

调试器通常会改变被调试进程或线程的某些状态

- 父进程检测： 正常程序通常由 `explorer.exe` 或 `cmd.exe` 等合法进程启动。而调试器会成为被调试进程的父进程。攻击者可以调用 `NtQueryInformationProcess` 或 `CreateToolhelp32Snapshot` 等 API，检查父进程 ID (`PPID`) 是否是已知的调试器进程 ID，或者干脆检查 `PPID` 是否不等于 `explorer.exe` 的 `PPID`
- 线程上下文检测： 调试器会修改被调试线程的寄存器和线程上下文。攻击者可以检查 `TEB` (Thread Environment Block) 中与调试相关的字段，或者检查线程的上下文信息是否被篡改

5. 其他高级反调试技术

- 调试器检测点： 在代码中故意创建多个 `INT 3` 断点或 `CALL` 调试 API 的分支，但让程序在没有调试器的情况下跳过这些分支。当调试器附加时，这些分支被执行，从而暴露调试器的存在
- 自修改代码： 在程序运行时修改自己的代码，例如用 `NOP` 指令覆盖反调试代码，或用 `jmp` 指令跳转到真正的逻辑代码。调试器很难追踪和分析这种行为，因为其静态分析视图与运行时视图不符
- 反汇编器检测： 有些技术不仅针对调试器，还针对 IDA Pro 等反汇编工具。例如，在代码中插入一些特殊指令序列，这些序列在 CPU 上执行正常，但在反汇编器中会被错误地解析，导致代码流被混淆