🎨 颜色标记说明

  • 粉色:特别重要、主线必掌握、优先复盘的内容。
  • 蓝色:核心概念、术语、分类名。
  • 橙色:需要关注的边界、易错点、注意事项。
  • 绿色:解释类内容、帮助理解知识点。

返回:全书总览 · 上一章:PEiD 原理 · 下一章:远控样本分析

调试器的核心循环

用户态调试器不是“每条指令都由调试器执行”,而是让目标进程正常运行,在调试事件发生时接管控制。

1
2
3
4
5
6
创建或附加目标进程
-> WaitForDebugEvent
-> 解析事件并暂停相关线程
-> 读取/修改内存和线程上下文
-> ContinueDebugEvent
-> 等待下一个事件

Windows 会报告进程、线程、DLL、调试字符串、异常和退出等事件。

创建与附加

两种常见模式:

  • 以调试标志创建新进程,从启动阶段开始观察。
  • 附加到已运行进程,接收后续调试事件。

调试器需要维护:

  • 被调试进程句柄。
  • 每个线程的句柄和状态。
  • 已加载模块的基址、大小和路径。
  • 断点原始字节和命中状态。
  • 符号、反汇编和用户标注。

软件断点

x86/x64 常用 INT 3,单字节操作码为 0xCC

1
2
3
4
5
6
7
8
保存目标地址原始字节
写入 0xCC
运行目标
命中断点异常
恢复原始字节
调整指令指针
单步执行原指令
必要时重新写入 0xCC

一个可靠的软件断点管理器必须保存原始字节,并正确处理恢复、单步和重新安装的状态转换。

修改代码页后还应考虑内存保护与指令缓存一致性。

硬件断点

x86/x64 调试寄存器可监视少量执行、读写或写入地址。优点是无需改写目标代码,缺点是槽位少、长度和对齐受限制,而且通常需要为相关线程设置上下文。

常见寄存器:

  • DR0–DR3:断点地址。
  • DR6:状态。
  • DR7:启用方式和监视条件。

内存断点

调试器还可通过页面保护监控更大范围的访问。例如使用保护页,在访问时触发异常,再由调试器判断具体地址。

这种方式粒度通常是内存页,可能带来大量无关命中,并改变目标的内存行为。

单步执行

设置 EFLAGS/RFLAGS 的 Trap Flag 后,CPU 在执行一条指令后产生单步异常。调试器利用它实现:

  • 单步进入。
  • 软件断点原指令执行后的重装。
  • 某些“执行到返回”或条件控制逻辑。

“单步跳过”遇到 CALL 时,调试器通常在下一条指令临时设置断点,而不是让 CPU 直接理解高级调试命令。

异常分派

断点和单步都以异常形式到达调试器。调试器必须决定:

  • 这是自己设置的断点,还是程序主动执行 INT 3
  • 异常是否已经处理。
  • 应把 DBG_CONTINUE 还是未处理状态传回系统。
  • 指令指针是否需要修正。

处理错误可能导致重复命中、跳过原指令或意外终止进程。

反汇编窗口如何形成

调试器读取目标内存,从当前地址调用反汇编引擎,把字节流解码成:

  • 指令长度和助记符。
  • 操作数类型。
  • 分支目标。
  • 内存引用。

变长 x86 指令要求从正确边界开始解码;从数据区或指令中间开始,仍可能得到“看似合法”的错误指令序列。

OllyDbg 与现代环境

OllyDbg 对理解 32 位用户态调试非常经典,但今天的实践还应熟悉 x64dbg、WinDbg 和现代符号体系。工具界面会变化,调试事件、异常、上下文和断点机制才是可迁移知识。

学习检查清单

检查项 状态
能描述 Windows 调试事件循环 待复盘
能完整说明 0xCC 软件断点的恢复流程 待复盘
能区分软件、硬件与内存断点 待复盘
能说明 Trap Flag 在单步中的作用 待复盘
能解释错误指令边界为何会产生伪反汇编 待复盘