【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

2025-04-27 0 117

目录

一、引言

二、调试器核心功能设计与实现

三、断点功能

四、高级功能

五、附加功能

六、开发环境与实现概要

七、项目展示及完整代码参考

八、总结


一、引言

        在软件开发领域,调试器是开发者不可或缺的工具。它不仅能帮助定位代码中的逻辑错误,还能深入理解程序运行时的底层机制。本文将阐述一个基于Windows 10操作系统和VS2015开发环境、使用C/C++语言实现的调试器项目。该调试器具备丰富的基础功能、断点机制、高级特性及附加工具,项目旨在熟悉调试器开发原理,文末将提供完整项目代码实现给大家参考。

二、调试器核心功能设计与实现

(一)调试机制的建立:创建与附加

        调试器的首要任务是建立与目标程序的关联,主要通过**创建新进程调试**和**附加现有进程调试**两种方式实现。

创建新进程调试

        通过Windows API中的`CreateProcess`函数启动目标程序,并指定调试标志`DEBUG_PROCESS`。此时调试器作为父进程,可捕获子进程的所有调试事件(如断点触发、异常抛出等)。在调试循环中,使用`WaitForDebugEvent`函数阻塞等待调试事件,解析事件类型(如`EXCEPTION_DEBUG_EVENT`、`CREATE_PROCESS_DEBUG_EVENT`),并进行相应处理(如中断程序、更新调试信息)。

附加现有进程调试

        利用`OpenProcess`获取目标进程句柄,通过`DebugActiveProcess`函数附加调试器。此过程需处理权限问题,确保调试器具备足够的访问权限。附加成功后,目标进程会暂停运行,调试器接管其执行流程,后续通过`ContinueDebugEvent`恢复进程运行。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

(二)汇编代码的显示与修改

1. 汇编代码显示:
– 使用 BeaEngine 反汇编引擎,将目标进程内存中的机器码转换为可读的汇编指令
– 通过 DbgUi 类实现格式化显示,包括地址、机器码、助记符和注释的对齐展示
– 使用不同颜色高亮显示不同类型的指令(如跳转、调用等)

2. 汇编代码修改:
– 使用 XEDParse 库将用户输入的汇编指令转换为机器码
– 通过 WriteProcessMemory 函数将生成的机器码写入目标进程内存
– 提供交互式的汇编模式,支持实时修改和错误检查

(三)内存与栈的查看和修改

1. 内存查看与修改:
– 使用 ReadProcessMemory 读取目标进程内存内容
– 通过 DbgUi::showMem 函数以十六进制和ASCII格式显示内存数据
– 使用 WriteProcessMemory 实现内存修改
– 支持通过 VirtualProtectEx 修改内存页属性来确保写入权限

2. 栈查看与修改:
– 通过 GetThreadContext 获取线程上下文,读取ESP/EBP等栈相关寄存器
– 使用 StackWalk64 函数遍历调用栈
– 结合 dbghelp.dll 的符号解析功能显示栈帧信息
– 同样可以通过 ReadProcessMemory/WriteProcessMemory 读写栈内存

 

(四)寄存器的查看与修改

 

1. 寄存器查看:
– 通过 GetThreadContext 获取线程上下文(CONTEXT结构体),包含所有寄存器信息
– 使用 DbgUi::showReg 函数格式化显示寄存器值
– 通过对比新旧值,用不同颜色高亮显示发生变化的寄存器

2. 寄存器修改:
– 先用 GetThreadContext 获取当前寄存器状态
– 修改 CONTEXT 结构体中对应的寄存器值
– 通过 SetThreadContext 将修改后的值写回目标线程

示例代码:

CONTEXT ct = { CONTEXT_ALL };  // 包含所有寄存器
GetThreadContext(hThread, &ct);  // 获取
ct.Eax = newValue;  // 修改
SetThreadContext(hThread, &ct);  // 写回

本质就是利用Windows调试API中的线程上下文操作函数来实现寄存器的读写。

(五)调试事件循环

示例代码:

E_Status DbgEngine::Exec() {
    DEBUG_EVENT dbgEvent = { 0 };
    
    while(true) {
        // 等待调试事件
        WaitForDebugEvent(&dbgEvent, 30);
        
        // 根据事件类型处理
        switch(dbgEvent.dwDebugEventCode) {
            case EXCEPTION_DEBUG_EVENT:      // 异常事件
            case CREATE_PROCESS_DEBUG_EVENT: // 进程创建
            case CREATE_THREAD_DEBUG_EVENT:  // 线程创建
            case EXIT_PROCESS_DEBUG_EVENT:   // 进程退出
            case LOAD_DLL_DEBUG_EVENT:       // DLL加载
            // ... 其他事件处理
        }
        
        // 继续执行
        ContinueDebugEvent(dbgEvent.dwProcessId,
            dbgEvent.dwThreadId,
            dwStatus);
    }
}

三、断点功能

(一)断点类型与原理

1. 软件断点(INT 3断点)

软件断点(INT 3)的大致实现如下:

class BPSoft : public BPObject {
    unsigned char m_uData;  // 保存原始指令字节
    
    bool Install() {
        // 1. 保存原始指令字节
        m_dbgObj.ReadMemory(m_uAddress, &m_uData, 1);
        
        // 2. 写入INT3指令(0xCC)
        char c = '\\xCC';
        m_dbgObj.WriteMemory(m_uAddress, (pbyte)&c, 1);
        return true;
    }
    
    bool Remove() {
        // 1. 恢复原始指令
        m_dbgObj.WriteMemory(m_uAddress, (pbyte)&m_uData, 1);
        
        // 2. 修正EIP(因为触发断点时EIP会多加1)
        CONTEXT ct = { CONTEXT_CONTROL };
        m_dbgObj.GetRegInfo(ct);
        ct.Eip--;
        m_dbgObj.SetRegInfo(ct);
        return true;
    }
};

核心原理:
1. 设置断点时保存原始字节,并替换为0xCC(INT3指令)
2. 触发断点时恢复原始字节,并将EIP回退一个字节
3. 通过异常处理来捕获INT3中断实现断点功能

这就是最基本的软件断点实现方式。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

 2. 单步和硬件断点

单步和硬件断点的实现如下:

单步执行(TF断点):

class BPTF : public BPObject {
    bool Install() {
        // 设置EFLAGS的TF位(Trap Flag)
        CONTEXT ct = { CONTEXT_CONTROL };
        m_dbgObj.GetRegInfo(ct);
        PEFLAGS pEflags = (PEFLAGS)&ct.EFlags;
        pEflags->TF = 1;  // 设置陷阱标志
        return m_dbgObj.SetRegInfo(ct);
    }
};

硬件断点:

class BPHard : public BPObject {
    bool Install() {
        CONTEXT ct = { CONTEXT_DEBUG_REGISTERS };
        // 设置调试寄存器
        // DR0-DR3: 存储断点地址
        // DR7: 控制断点类型(执行/读/写)和长度
        if(ct.Dr0 == 0) {
            ct.Dr0 = m_uAddress;  // 断点地址
            pDbgReg7->L0 = 1;     // 启用断点
            pDbgReg7->RW0 = m_eType;  // 断点类型
            pDbgReg7->LEN0 = m_uLen;  // 断点长度
        }
        return m_dbgObj.SetRegInfo(ct);
    }
};

主要特点:
– 单步:通过设置EFLAGS的TF位,每执行一条指令就触发异常
– 硬件断点:利用CPU的调试寄存器(DR0-DR7),支持执行/读/写断点,最多4个

优势:
– 单步:不修改代码,适合追踪执行流程
– 硬件断点:不修改内存,适合监控内存访问,且数量有限

这两种断点都是通过CPU提供的硬件功能来实现的,比软件断点更特殊。

 

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

 

 4. 内存访问断点

内存访问断点的大致实现如下:

class BPAcc : public BPObject {
    E_BPType m_eType;    // 断点类型(读/写/执行)
    uint m_uLen;         // 监视长度
    DWORD m_oldProtect;  // 原始页面属性
    
    bool Install() {
        // 1. 修改内存页属性,触发访问异常
        VirtualProtectEx(m_dbgObj.m_hCurrProcess,
            (LPVOID)m_uAddress,
            m_uLen,
            PAGE_GUARD,  // 设置为Guard页
            &m_oldProtect);
        return true;
    }
    
    bool Remove() {
        // 恢复原始页面属性
        VirtualProtectEx(m_dbgObj.m_hCurrProcess,
            (LPVOID)m_uAddress,
            m_uLen,
            m_oldProtect,
            &dwOldProtect);
        return true;
    }
    
    bool IsHit() {
        // 根据异常信息判断访问类型(读/写/执行)
        switch(m_eType) {
            case breakpointType_acc_r:  // 读断点
            case breakpointType_acc_w:  // 写断点
            case breakpointType_acc_e:  // 执行断点
        }
    }
};

核心原理:
1. 通过修改内存页属性(PAGE_GUARD)来监控内存访问
2. 当目标地址被访问时触发异常
3. 在异常处理中判断访问类型(读/写/执行)
4. 支持设置监视范围的长度

这种方式可以监控较大范围的内存访问,但会影响性能。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

(二)断点管理与界面交互

1. 断点管理:
– 通过`BreakpointEngine`类管理断点列表,支持软件断点(INT3)、硬件断点(DR)、内存断点和TF单步断点
– 软件断点通过替换指令为0xCC实现,硬件断点使用调试寄存器,内存断点修改页面属性
– 每个断点都继承自`BPObject`基类,实现`Install()`、`Remove()`等统一接口

2. 界面交互:
– 使用命令行方式接收用户输入,如'b'设置断点,'l'显示断点列表,'g'运行等
– 通过`DbgUi`类处理显示格式化,包括断点位置高亮、寄存器变化显示等
– 使用Windows控制台API实现颜色显示和布局排版

核心代码示例:

// 断点管理
BPObject* AddBreakPoint(uaddr uAddress, E_BPType eType);
bool DeleteBreakpoint(uint uIndex);

// 界面交互
void showBreakPointList();
void showReg(const CONTEXT& ct);

本质是将断点管理功能与命令行界面结合,提供友好的调试体验。

四、高级功能

(1)条件断点功能:
– 实现在 `BPObject` 类中,通过 `SetCondition()` 和 `IsHit()` 方法支持条件断点
– 使用 `Expression` 类来解析和计算条件表达式
– 断点类型包括:
  – 软件断点 (`BPSoft`): 通过写入 INT3 指令(0xCC)实现
  – 硬件断点 (`BPHard`): 利用调试寄存器实现
  – 内存访问断点 (`BPAcc`): 监控内存访问
  – 单步执行断点 (`BPTF`): 利用 TF 标志实现

(2)反反调试技术:
– 在 `HidePEB.h` 中实现了隐藏 PEB 调试标志:
  – 修改 `BeingDebugged` 字段为 0
  – 清除 `NtGlobalFlag` 标志
– 在 `DbgObject` 类中实现了 API Hook:
  – 通过 `HOOKObject` 和 `HOOKEngine` 类管理 Hook
  – 可以 Hook 关键 API 如 `IsDebuggerPresent`
  – 支持保存和恢复原始函数数据

(3)插件支持:
– 在 `AddPlugin.h` 中实现了插件系统:
  – 定义了插件结构体 `PLUGIN_T`,包含:
    – 插件名称
    – DLL 实例句柄
    – 插件函数指针
  – 通过 `g_pv` 结构体管理插件列表
  – `load_plugin()` 函数用于加载插件:
    – 加载插件 DLL
    – 获取插件导出函数
    – 添加到插件列表
– 插件接口:
  – 插件需要导出 `funTest` 函数
  – 可以通过 `.load <dll name>` 命令动态加载插件
  – 支持最多 50 个插件同时加载

五、附加功能

1. 导入导出表解析:完整实现,支持32位和64位PE文件

2. 符号解析:通过dbghelp.dll实现,支持符号加载和解析

3. 源码调试:有基础实现,但可能功能不够完整

4. DUMP功能:支持内存dump和文件导出

5. 其他功能:
   – 断点管理:完整支持
   – 堆栈显示:基本实现
   – 代码高亮:支持控制台颜色显示

 

六、开发环境与实现概要

 

6.1 环境配置

        – 操作系统:Windows 10(64位),支持x86和x64架构调试。
        – 开发工具:Visual Studio 2015或以上,使用C++语言结合Windows SDK开发。

6.2 界面设计

        采用dos窗口显示

6.3 实现概要

        这是一个基于Windows平台的用户态调试器,使用C++开发,实现了调试器的核心功能,包括断点管理、内存操作、符号解析等功能。

核心功能模块

(1)调试引擎核心 (DbgEngine类)
– 进程调试控制:创建/附加进程
– 调试事件处理循环
– 异常处理机制
– 调试会话管理

(2)断点系统 (BreakpointEngine类)
实现了四种类型的断点:
1. 软件断点(BPSoft)
   – 使用INT3指令(0xCC)实现
   – 通过替换目标地址的指令字节实现

2. 硬件断点(BPHard)
   – 利用CPU的调试寄存器(DR0-DR7)
   – 支持执行、读写断点

3. 内存访问断点(BPAcc)
   – 通过修改内存页属性实现
   – 支持读/写/执行权限控制

4. 单步执行断点(BPTF)
   – 利用EFLAGS寄存器的TF位
   – 实现指令级单步执行

(3)符号处理系统
– 使用dbghelp.dll提供的功能
– 支持调试符号的加载和解析
– 提供符号信息查询
– 支持调用栈回溯

(4)反汇编系统
结合使用三个重要的外部库:
1. BeaEngine 4.1
   – x86/x64指令反汇编
   – 支持多种指令集

2. dbghelp.dll
   – 调试符号处理
   – PE文件分析
   – 调用栈分析

3. XEDParse.dll
   – 指令编码/解码
   – 汇编指令解析
   – 机器码生成

6.4 技术特点

(1)系统架构
– 采用面向对象设计
– 模块化结构清晰
– 良好的继承和多态设计

(2)关键技术
1. Windows调试API的使用
   – CreateProcess/DebugActiveProcess
   – WaitForDebugEvent
   – ReadProcessMemory/WriteProcessMemory
   – VirtualProtectEx
   – GetThreadContext/SetThreadContext

2. 断点实现技术
   – 指令修改(软件断点)
   – 调试寄存器配置(硬件断点)
   – 内存属性控制(内存断点)
   – 标志位设置(单步执行)

(3)高级功能
– 条件断点支持
– 一次性断点
– 多种断点类型
– 表达式计算器
– 反汇编显示
– 内存/寄存器操作

6.5 项目特色

1. 完整性:实现了调试器的所有核心功能
2. 可扩展性:模块化设计便于功能扩展
3. 稳定性:包含异常处理和错误恢复机制
4. 实用性:支持多种调试场景和需求

6.6 技术依赖

– Windows SDK
– BeaEngine 4.1 (反汇编引擎)
– dbghelp.dll (调试符号支持)
– XEDParse.dll (指令解析)

七、项目展示及完整代码参考

(一)功能截图说明

        – 功能菜单:(如图1所示)。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

图1

        – 单步运行:(如图2所示)。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

图2

        – 查看及修改汇编(如图3所示)。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

 图3

        – 查看及修改寄存器(如图4所示)。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

图4

        – 查看栈(如图5所示)

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

图5

        – 断点标记:用红色标记断点位置(如图6所示)。

【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg)

图6

(二)完整代码参考
https://download.csdn.net/download/linshantang/90530517

八、总结

        本文介绍的调试器通过Windows API和C/C++语言实现了完整的调试功能链,从基础的进程控制到高级的反反调试和插件机制,覆盖了开发者在调试过程中的核心需求。其设计思路和实现方法不仅适用于Windows平台,也为其他操作系统的调试工具开发提供了参考。

这个调试器项目主要收获:

        1. Windows调试器的核心实现原理,包括断点机制、进程控制和内存操作
        2. 反调试对抗技术,如PEB结构修改和API Hook的实现
        3. 良好的软件架构设计,展示了如何构建可扩展的模块化系统
        4. Windows系统底层知识,包括汇编、PE文件格式和系统API的使用

 

平台声明:以上文章转载于《CSDN》,文章全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,仅作参考。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/linshantang/article/details/147351406

遇见资源网 编程语言 【第52节】Windows编程必学之从零手写C++调试器下篇(仿ollydbg) http://www.ox520.com/157464.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务