DebugBlocker反调试技术
字数 1733 2025-08-06 20:12:33

DebugBlocker反调试技术详解

0x00 技术原理

DebugBlocker是一种硬核的反调试技术,其核心原理是:

  1. 父子进程调试关系:父进程创建并调试子进程,两者通常是同一可执行程序
  2. 代码分流执行:通过IsDebuggerPresent等检测调试的技术使父子进程执行不同代码
  3. 逻辑隐藏:真正的程序逻辑往往在修改后的子进程中,而子进程无法直接附加调试
  4. 技术组合:通常与SMC(自修改代码)技术和异常处理机制结合使用

工作流程:

  • 父进程负责恢复控制流和程序代码
  • 子进程执行真正的程序代码

0x01 关键API和结构体

1. CreateProcessA - 创建新进程及其主线程

BOOL CreateProcessA(
  [in, optional]      LPCSTR               lpApplicationName,
  [in, out, optional] LPSTR                lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                 bInheritHandles,
  [in]                DWORD                dwCreationFlags,
  [in, optional]      LPVOID               lpEnvironment,
  [in, optional]      LPCSTR               lpCurrentDirectory,
  [in]                LPSTARTUPINFOA       lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

关键结构体:

typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;    // 新进程的句柄
  HANDLE hThread;     // 新建进程的主线程的句柄
  DWORD dwProcessId;  // PID
  DWORD dwThreadId;   // TID
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;

关键标志位dwCreationFlags

  • DEBUG_PROCESS (0x00000001):启动并调试新进程,可使用WaitForDebugEvent接收调试事件
  • DEBUG_ONLY_THIS_PROCESS (0x00000002):与DEBUG_PROCESS同时使用时,调用方只能调试该新进程

2. WaitForDebugEvent - 等待调试进程中的调试事件

BOOL WaitForDebugEvent(
  [out] LPDEBUG_EVENT lpDebugEvent,
  [in]  DWORD        dwMilliseconds
);

关键结构体:

typedef struct _DEBUG_EVENT {
  DWORD dwDebugEventCode;
  DWORD dwProcessId;
  DWORD dwThreadId;
  union {
    EXCEPTION_DEBUG_INFO Exception;
    // ... 其他调试信息
  } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

异常记录结构体:

typedef struct _EXCEPTION_RECORD {
  DWORD    ExceptionCode;
  DWORD    ExceptionFlags;
  struct _EXCEPTION_RECORD *ExceptionRecord;
  PVOID    ExceptionAddress;
  DWORD    NumberParameters;
  ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

常见异常类型:

  • 0x80000003:int3断点触发
  • 0xc0000005:访问未分配(不合理)的地址触发

3. Get/SetThreadContext - 获取/设置线程上下文

BOOL GetThreadContext(
  [in]      HANDLE    hThread,    // 线程句柄
  [in, out] LPCONTEXT lpContext   // 上下文结构指针
);

BOOL SetThreadContext(
  HANDLE    hThread,
  CONST CONTEXT *lpContext
);

4. Read/WriteProcessMemory - 进程内存读写

BOOL WriteProcessMemory(
  HANDLE  hProcess,               // 进程句柄
  LPVOID  lpBaseAddress,          // 基址指针
  LPVOID  lpBuffer,               // 要写入数据的指针
  DWORD   nSize,                  // 写入的字节数
  LPDWORD lpNumberOfBytesWritten
);

BOOL ReadProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPCVOID lpBaseAddress,    // 读取内存的基址
  [out] LPVOID  lpBuffer,         // 存放读出数据的缓冲区
  [in]  SIZE_T  nSize,            // 读取字节数
  [out] SIZE_T  *lpNumberOfBytesRead
);

5. ContinueDebugEvent - 继续调试事件

BOOL ContinueDebugEvent(
  [in] DWORD dwProcessId,         // 继续调试的PID
  [in] DWORD dwThreadId,          // 继续调试的TID
  [in] DWORD dwContinueStatus     // 继续调试事件的选项
);

dwContinueStatus通常使用DBG_CONTINUE常量(0x10002),表示异常已处理,继续执行。

0x02 实例分析:2022鹏城杯BUG之眼

程序结构

int __cdecl main(int argc, const char **argv, const char **envp)
{
  if (IsDebuggerPresent())
    sub_1400024B0();  // 被调试时执行的代码
  else
    sub_140002D50();  // 原始进程执行的代码
  return 0;
}

父进程逻辑(sub_140002D50)

  1. 获取当前文件路径
  2. 调用CreateProcessA创建新进程,dwCreationFlags设为1(DEBUG_PROCESS)
  3. 使用WaitForDebugEvent等待调试事件
  4. 处理0x80000003(int3断点)异常:
    • 获取异常地址
    • 使用GetThreadContext获取主线程的RIP
    • WriteProcessMemory将0xCC替换为0xC3(ret指令)
  5. 解密enc_data处的数据:
    • 读取enc_data到缓冲区
    • 按长度分组进行逐字节异或解密
    • 将解密数据写回新进程
  6. 调用ContinueDebugEvent继续调试

子进程逻辑(sub_1400024B0)

  1. 向内存写入0xCC(int3)
  2. 以函数方式调用触发异常,让父进程捕获并修改enc_data
  3. 将0xCC修改为0xC3(ret)后继续执行
  4. 创建并调试孙子进程

孙子进程处理

  1. 第一次在0x141000000触发异常:
    • 子进程将EIP改为0x140001330并继续执行
  2. 0x140001330执行中出现的异常:
    • 使用修改后的enc_data进行处理
    • enc_data[0]:异常处距离0x140001330的偏移
    • enc_data[1]:代码块大小
    • enc_data[2]:用于修复0xCC

手动修复代码

使用IDAPython脚本修复解密后的代码:

import idautils

enc_data = [
  0x0000000000000000, 0x000000000000005F, 0x000000000000008C,
  0x000000000000005F, 0x000000000000001E, 0x0000000000000084,
  # ... 更多数据
]

start = 0x140001330

for p in range(0, len(enc_data), 3):
    offset = enc_data[p]    # 异常地址偏移
    size = enc_data[p+1]    # 代码块大小
    tmp = enc_data[p+2]     # 首字节恢复
    
    adr = start + offset
    k = list(str(adr + 1))
    
    PatchByte(adr, Byte(adr) ^ tmp)
    
    for i in range(1, size):  # 修复代码块大小
        t = Byte(adr + i)
        PatchByte(adr + i, t ^ ord(k[(i-1) % len(k)]))
        if i % len(k) == 0:
            k = list(str(adr + i))
    
    print('ok')

0x03 对抗思路

  1. 绕过调试检测

    • 修改IsDebuggerPresent返回值
    • 使用硬件断点替代软件断点
  2. 获取解密代码

    • 在合适位置patch,使孙子进程不设置调试关系
    • 尝试附加调试孙子进程
  3. API拦截

    • 拦截ContinueDebugEvent等关键API
    • 修改调试事件处理逻辑
  4. 静态分析

    • 通过IDA Python等工具静态修复代码
    • 分析加密算法和密钥生成方式

0x04 总结

DebugBlocker是一种有效的反调试技术,通过:

  1. 父子进程调试关系隐藏真实逻辑
  2. 结合SMC技术动态修改代码
  3. 利用异常处理机制实现代码流控制

分析这类技术需要:

  • 深入理解Windows调试API
  • 掌握异常处理流程
  • 具备静态分析和动态调试结合的能力
  • 熟悉常见的加密和代码混淆技术
DebugBlocker反调试技术详解 0x00 技术原理 DebugBlocker是一种硬核的反调试技术,其核心原理是: 父子进程调试关系 :父进程创建并调试子进程,两者通常是同一可执行程序 代码分流执行 :通过 IsDebuggerPresent 等检测调试的技术使父子进程执行不同代码 逻辑隐藏 :真正的程序逻辑往往在修改后的子进程中,而子进程无法直接附加调试 技术组合 :通常与SMC(自修改代码)技术和异常处理机制结合使用 工作流程: 父进程负责恢复控制流和程序代码 子进程执行真正的程序代码 0x01 关键API和结构体 1. CreateProcessA - 创建新进程及其主线程 关键结构体: 关键标志位 dwCreationFlags : DEBUG_PROCESS (0x00000001):启动并调试新进程,可使用 WaitForDebugEvent 接收调试事件 DEBUG_ONLY_THIS_PROCESS (0x00000002):与 DEBUG_PROCESS 同时使用时,调用方只能调试该新进程 2. WaitForDebugEvent - 等待调试进程中的调试事件 关键结构体: 异常记录结构体: 常见异常类型: 0x80000003 :int3断点触发 0xc0000005 :访问未分配(不合理)的地址触发 3. Get/SetThreadContext - 获取/设置线程上下文 4. Read/WriteProcessMemory - 进程内存读写 5. ContinueDebugEvent - 继续调试事件 dwContinueStatus 通常使用 DBG_CONTINUE 常量(0x10002),表示异常已处理,继续执行。 0x02 实例分析:2022鹏城杯BUG之眼 程序结构 父进程逻辑(sub_ 140002D50) 获取当前文件路径 调用 CreateProcessA 创建新进程, dwCreationFlags 设为1(DEBUG_ PROCESS) 使用 WaitForDebugEvent 等待调试事件 处理 0x80000003 (int3断点)异常: 获取异常地址 使用 GetThreadContext 获取主线程的RIP 用 WriteProcessMemory 将0xCC替换为0xC3(ret指令) 解密 enc_data 处的数据: 读取 enc_data 到缓冲区 按长度分组进行逐字节异或解密 将解密数据写回新进程 调用 ContinueDebugEvent 继续调试 子进程逻辑(sub_ 1400024B0) 向内存写入0xCC(int3) 以函数方式调用触发异常,让父进程捕获并修改 enc_data 将0xCC修改为0xC3(ret)后继续执行 创建并调试孙子进程 孙子进程处理 第一次在 0x141000000 触发异常: 子进程将EIP改为 0x140001330 并继续执行 在 0x140001330 执行中出现的异常: 使用修改后的 enc_data 进行处理 enc_data[0] :异常处距离 0x140001330 的偏移 enc_data[1] :代码块大小 enc_data[2] :用于修复0xCC 手动修复代码 使用IDAPython脚本修复解密后的代码: 0x03 对抗思路 绕过调试检测 : 修改 IsDebuggerPresent 返回值 使用硬件断点替代软件断点 获取解密代码 : 在合适位置patch,使孙子进程不设置调试关系 尝试附加调试孙子进程 API拦截 : 拦截 ContinueDebugEvent 等关键API 修改调试事件处理逻辑 静态分析 : 通过IDA Python等工具静态修复代码 分析加密算法和密钥生成方式 0x04 总结 DebugBlocker是一种有效的反调试技术,通过: 父子进程调试关系隐藏真实逻辑 结合SMC技术动态修改代码 利用异常处理机制实现代码流控制 分析这类技术需要: 深入理解Windows调试API 掌握异常处理流程 具备静态分析和动态调试结合的能力 熟悉常见的加密和代码混淆技术