简介:这是一份面向 Windows 平台的二进制调试器 x64dbg 的完整源码包,适合逆向工程师、恶意软件分析师以及希望深入理解调试器实现原理的开发者。源码基于 C++/Qt 构建,包含调试引擎、反汇编界面、插件系统等核心模块,同时附带丰富的帮助文档、界面资源与工程配置,可作为二次开发或学习调试技术的参考蓝本。资源包共 1826 个文件,以 Markdown 文档、PNG 界面截图、C/C++ 头文件与实现文件、reStructuredText 文档为主要组成,另有 Qt 界面文件、库文件与构建脚本,整体大小仅 4.66MB,结构清晰便于查阅。当前已有 57 人浏览学习。借助这套源码,读者可以梳理调试器的断点管理、内存读写、表达式求值等关键流程,了解插件 API 的扩展方式,还能参考其界面布局与交互设计,是逆向工具研究与定制开发的高质量素材。 如果你还在用OllyDbg调试x64程序,大概率会遇到一个尴尬场景:OD1.x只能挂在32位进程上,碰到x64的样本直接歇菜;换到OD2.0,界面和插件生态又停留在十年前。x64dbg就是在这个痛点下长出来的现代开源调试器,它不光是能跑x64,还把操作习惯沿用了OD那套逻辑,所以很多人把它叫做“OD的开源接替者”。这篇我会直接从源码角度拆一下它的模块设计、断点链路、内存窗口的数据流,再聊插件SDK和从零编译的实操经验,适合正在做逆向、二进制漏洞分析或者想二次开发调试器的朋友。
1. x64dbg凭什么能接OD的班——从OllyDbg到x64dbg的架构演进
1.1 为什么老OD撑不到x64时代
OllyDbg 1.10是32位时代的调试器之王,但它本质上属于“单进程、单架构”的工具。x64进程在Windows上完全是另一套寄存器宽度和调用约定,OD1.x直接不认;OD2.0虽然补了一部分x64支持,却长期停留在beta状态,源码也不开放,插件作者想跟进都没有入口。更麻烦的是,OD的插件SDK基于Delphi生态,今天想在它上面做自动化分析、对接脚本引擎,维护成本特别高。
x64dbg的思路很直接:与其指望OD重写,不如写一个“精神续作”,把OD好用的部分全保留,同时把底层换成现代工具链。项目从2012年左右启动,最初叫x64_dbg,后来才改名x64dbg,使用GPL开源协议,Windows平台免费分发。它的目标是同时支持x86和x64调试,UI交互尽量贴近OD用户的肌肉记忆,但底层完全自己造轮子。
1.2 x64dbg的核心设计选择
从源码角度看,x64dbg的几个技术选型直接决定了它的扩展能力:
- 前端用Qt Widgets,跨平台UI在Windows上表现稳定,菜单、停靠窗口、快捷键系统都基于Qt的事件模型,二次开发改动界面比较容易。
- 反汇编引擎早期用BeaEngine,后来在新版本里切换到Zydis。Zydis对x86/x64指令集的覆盖更完整,而且反汇编速度也有优势,处理加壳样本后大段花指令时体验差别很明显。
- 表达式系统保留OD风格,比如
[eax+4]、模块名+偏移这类写法都能直接用在命令行、条件断点里。 - 内置脚本引擎,命令接口设计成“文本化”,几乎任何操作都可以用一条命令触发,和后文要讲的插件系统天然打通。
我自己用下来的感受是:x64dbg的学习曲线没有想象中陡,因为它刻意把OD用户熟悉的“命令栏+寄存器窗口+栈窗口+内存窗口”布局沿用下来,但如果只是把它当作一个OD的x64替代版,就错过了它最值钱的部分——可编程性和源码可读性。它和WinDbg的区别在于,WinDbg是内核态和用户态通吃的重型工具,调试命令更偏符号表驱动;x64dbg则更适合快速定位用户态逻辑、动态跟踪协议解析和恶意代码行为。
| 对比项 | x64dbg | OllyDbg 2.0 | WinDbg |
|---|---|---|---|
| 开源 | 是,GPL | 否 | 部分,但SDK复杂 |
| x64支持 | 原生支持 | 有限 | 原生支持 |
| 反汇编引擎 | Zydis | 自研 | DbgEng内置 |
| 插件SDK | C接口,上手轻 | Delphi接口老 | COM组件,门槛高 |
| 脚本能力 | 内置脚本+命令行 | 弱 | 命令宏丰富 |
| 典型场景 | 用户态逆向/漏洞分析 | 早期32位样本调试 | 内核/符号级调试 |
2. 源码级视角看dbg/gui/bridge三层拆解
2.1 仓库目录总览
抓一份最新源码,仓库根目录下最值得关注的是src子目录,里面三个核心模块分工非常清晰:
| 目录 | 职责 |
|---|---|
| src/dbg | 调试引擎核心:进程管理、断点、表达式、模块加载、内存读写、符号分析 |
| src/gui | 界面层:CPU窗口、内存窗口、栈窗口、脚本命令入口、菜单系统 |
| src/bridge | 桥接层:GUI和调试引擎之间的纯C接口通信,负责跨线程同步 |
这种三层解耦是我看过的调试器项目里比较清爽的。相比某些把调试逻辑和UI纠缠在一起的工具,x64dbg的代码边界明确:gui永远不直接调用Windows调试API,它只能通过bridge暴露的函数表去请求dbg干活;dbg也完全不感知用户点的是哪个按钮。好处是,哪怕你要把gui整个换掉,调试引擎还能原封不动复用。
2.2 dbg模块:调试引擎的“真相层”
dbg模块是整个项目的核心状态机。它内部管理着一个调试循环,等待Windows调试事件(如断点异常、DLL加载、线程创建)后分发处理,同时维护了进程句柄、线程列表、模块列表、符号缓存等内容。你从界面上看到的“进程”“线程”“句柄”窗口,数据几乎全部来自dbg侧维护的快照,而不是临时去调API。
dbg对外暴露的接口是DBGFUNCTIONS函数表,gui通过bridge拿到这个表之后,就能按需调用DbgGetModuleList、DbgMemRead、DbgSetBreakpoint这类操作。它内部还内置了command_t命令注册机制,像bp、bc、dump、disasm这些命令最终都绑定在dbg的命令调度器上。这样设计有一个好处:任何界面操作都能落到一条可复用的文本命令,脚本执行和手动操作本质上走同一条路径。
2.3 gui模块:界面仅仅是“另一个客户端”
gui基于Qt Widgets,打开src/gui里的代码,你会发现窗口类并不直接去读进程内存,而是把请求封装成GuiGetRemoteString之类的bridge调用。界面上的“反汇编窗口”“内存窗口”都只是一个渲染器,数据几乎全部来自dbg侧的快照。
lambda表达式窗口、寄存器窗口这些看起来独立的小控件,实际注册的都是同一个事件模型:断点命中后dbg更新状态,通过bridge广播状态变更,gui订阅变更然后重绘。这个模式有点像观察者模式,界面永远不会因为调试循环阻塞而卡死,因为耗时操作都发生在dbg侧的独立线程里。
2.4 bridge模块:跨线程通信的“安全管道”
bridge模块是很容易被忽略但极其关键的代码。调试器运行时,gui线程和dbg调试循环线程是完全不同的执行流,如果gui直接调用dbg内部函数,频繁切换执行状态会导致竞态和崩溃。bridge的做法是设计一组纯C函数指针表,配合互斥锁,在两者之间同步状态。
我记得第一次读bridge源码时,看到里面有不少*_LOCK之类的宏,起初觉得啰嗦,后来在写插件时遇到“调试器忙”的错误才意识到,这些锁保护的是调试循环的关键不变量。如果你想把x64dbg的调试内核嵌入到别的工具里,bridge这套接口设计就是现成的集成范本。
3. 从断点命中到内存窗口刷新:一条数据到底怎么走完的
3.1 断点的三重类型与处理链路
x64dbg支持软件断点、硬件断点、内存断点三类,它们对应完全不同的实现机制:
- 软件断点:把目标地址处的机器码替换成
0xCC(int3),CPU执行到这触发异常,调试器再把原始字节恢复,单步时还要处理指令缓存刷新。这是最常用的断点,也最容易受自修改代码干扰。 - 硬件断点:利用CPU的调试寄存器DR0-DR7,最多同时设4个,不修改目标数据,适合在只读区域或频繁被访问的位置下断。
- 内存断点:利用页保护属性,当目标内存页被读/写/执行时触发页异常,调试器捕获后再恢复原属性。原理上讲,它比硬件断点慢得多,但数量上更灵活。
断点命中后的数据流大体是:CPU触发异常 → Windows向调试器进程发送DEBUG_EVENT→ dbg侧的调试循环被唤醒 → 根据异常地址和访问类型匹配断点列表 → 更新寄存器、栈、反汇编上下文 → 通过bridge广播“暂停”状态 → gui刷新CPU窗口和内存窗口。很多新手在条件断点上踩坑,就是没搞懂这个链路:条件表达式是在dbg侧求值的,所以表达式的性能会直接影响调试循环的响应速度,不要在条件里写太复杂的循环去遍历大数组。
3.2 “删除模块分析”到底删了什么
很多人第一次看到右键菜单里的“删除模块分析”会误以为只是清除标签。实际上,x64dbg对每个加载的模块会做一次指令级分析,利用Zydis逐条反汇编,识别函数边界、指令回调点、循环结构、参数位置,并生成函数标签和跳转关系。这个分析结果会被缓存到模块关联数据里。
问题来了:对于加壳程序或自修改代码,程序运行后内存中的字节早就变了,旧分析结果就不再可靠,甚至会把错误标签显示在反汇编窗口里。选择“删除模块分析”后,dbg会丢弃该模块的缓存分析数据,下一次再显示反汇编窗口或命中该模块断点时按需重新分析。遇到解壳后二进制全部变化的场景,这个操作几乎是必做的。我习惯在OEP(原始入口点)到达后再删一次分析,让标签和函数边界对齐真实代码。
3.3 内存窗口的数据刷新逻辑
内存窗口并不是一个简单“读字节然后显示”的控件,它背后有一整套地址表达式逻辑。地址栏支持模块名+偏移、寄存器间接寻址、符号名,你甚至可以直接写[[esp+4]]这种嵌套解引用。每次表达式变化,gui会请求dbg侧执行表达式求值,再按当前列宽度和行字节数读内存。
实际操作中,动态分析一个全局链表时,我会先在数据窗口输入结构体起始地址,然后在布局里按结构体成员大小调好列宽,这样能直观看到next指针和value字段的变化。这里有个隐藏机制:内存窗口只在调试器处于暂停状态时才刷新,运行状态下的刷新会被抑制,这是为了防止在目标进程持续变动时读到不一致的数据。如果你发现内存数据半天不更新,先确认当前是运行还是暂停,而不是质疑数据渲染代码。
4. 二次开发入口:从插件SDK到x64dbg-mcp这样的扩展
4.1 最小插件怎么写
x64dbg的插件SDK以C接口为主,入口文件是pluginsdk目录下的plugin.h。一个最小插件只需要实现四个函数:PLUG_INIT、PLUG_PRELOAD、PLUG_LOAD、PLUG_UNLOAD,并在初始化时通过_plugin_registercommand注册自定义命令。下面是基础框架:
#include "plugin.h" static bool myCommand(int argc, char** argv) { _plugin_logputs("my command executed"); return true; } PLUG_EXPORT bool PLUG_INIT(PLUG_INITSTRUCT* initStruct) { initStruct->pluginVersion = 1; initStruct->sdkVersion = PLUG_SDKVERSION; _plugin_registercommand(initStruct->pluginHandle, "mycmd", myCommand, false); _plugin_logputs("plugin loaded"); return true; } PLUG_EXPORT bool PLUG_LOAD(void) { return true; } PLUG_EXPORT bool PLUG_UNLOAD(void) { return true; }用Visual Studio新建DLL工程,链接x64dbg.lib或直接让插件导出函数由调试器动态解析,编译出的DLL放进plugins目录,重启后就能在菜单里看到入口。需要注意SDK版本号必须和调试器匹配,版本不一致时插件会被静默忽略,这是新手最容易遇到“插件没反应”的原因。
4.2 事件回调与菜单扩展的实际用法
SDK里另一块核心是事件回调。通过_plugin_registercallback注册CBADDBREAK、CBMENUENTRY、CBPREMIUM等事件,可以在断点添加、菜单点击、异常命中时插入自定义逻辑。
举一个实用场景:分析恶意样本时,我想在每次VirtualAlloc被调用时自动记录参数,直接下条件断点并在命令里执行Log不太灵活。插件做法是注册CBEXECME事件,在命令“虚拟内存分配”触发后读取寄存器,把分配大小和地址写进自定义日志文件。这样零手工记录,批量跑样本时非常省心。
4.3 x64dbg-mcp给调试带来的新玩法
最近引起我关注的是x64dbg-mcp这类项目。它把调试器的命令、寄存器读写、内存访问、断点管理等能力封装成MCP协议(Model Context Protocol)下的工具调用形式,本质上等于给大模型暴露了一套结构化的调试器API。在合规前提下,这种“调试器能力服务化”的方向对自动化逆向很有想象力。
拆开看它做的事并不神秘:先启动一个本地MCP服务器,把x64dbg的命令行接口包装成工具函数,比如read_memory(addr, size)、set_breakpoint(address)、continue_execute(),然后让AI按这个工具集去交互。它的贡献在于把调试器从“人用鼠标点”变成“程序可调用”,以后做样本批量分析、协议逆向辅助、崩溃点自动定位都有机会半自动化。对想深入SDK的人来说,这个项目也是很好的架构参考:它完全没改x64dbg内核,只靠命令通道和插件接口就实现了智能联动。
5. 从零编译一个x64dbg:环境配置与常见翻车点
5.1 编译环境清单
我实际编译过的组合是Windows 11 + Visual Studio 2022 + CMake 3.24 + Qt 5.15.2。源码依赖Git子模块,所以克隆仓库时要带--recursive参数,否则第三方依赖缺失会在配置阶段直接报错。官方推荐用Qt 5.15或Qt 6.x,不推荐用太老的Qt 5.12,因为部分界面组件的对接接口有差异。
| 工具 | 版本建议 | 用途 |
|---|---|---|
| Visual Studio | 2022,勾选“使用C++的桌面开发” | 编译MSVC工具链 |
| CMake | 3.21及以上 | 生成构建工程 |
| Qt | 5.15.2或6.x | GUI库 |
| Git | 任意较新版本 | 拉取源码和子模块 |
| Windows SDK | VS自带 | 系统头文件与调试API |
5.2 实际操作命令
git clone --recursive https://github.com/x64dbg/x64dbg.git cd x64dbg cmake -G "Visual Studio 17 2022" -A x64 -DQT_DIR=D:/Qt/Qt5.15.2/5.15.2/msvc2019_64 .. cmake --build . --config Release构建完成后,在bin目录里能看到x32dbg.exe、x64dbg.exe和一堆DLL。运行前需要把Qt的DLL目录加入环境变量,或者用windeployqt工具拷贝依赖到可执行文件目录:
D:/Qt/Qt5.15.2/5.15.2/msvc2019_64/bin/windeployqt.exe D:/x64dbg-release/x64/x64dbg.exe5.3 翻车点记录
我踩过的坑主要有三个:
第一个是CMake配置阶段找不到Qt,提示QT_TARGET相关错误。原因是-DQT_DIR指向了根目录而不是具体的编译器版本目录,改成指向含lib/cmake/Qt5的目录就好了。
第二个是构建时混用了x86和x64输出目录,导致运行时提示“应用程序无法正常启动”。自己手动编译最好严格区分build-x86和build-x64两个目录,不要用同一套中间产物交叉编译。
第三个坑更隐蔽:插件加载失败。x64dbg对插件位数要求严格,x64版调试器只加载64位DLL,如果你用x86编译插件并复制到x64的plugins目录,它会在日志里静默忽略,没有任何弹窗提示。排查时先看日志窗口有没有plugin load failed,没有就检查位数是否匹配。
另外,调试涉及高权限进程时,x64dbg本身需要以管理员权限运行,否则附加到系统进程会失败。这个问题不算源码编译带来的,但很容易让人误以为是构建出来的版本有缺陷。
最后再分享一个我自己的习惯:每次从源码编译完调试器,我会顺手跑一遍内置的自动化测试脚本,确认断点、单步、表达式求值这些基础功能正常,再开始日常分析。毕竟调试器本身出了问题,后面所有逆向结果都不可信。如果你也打算深挖这个项目,建议先读dbg目录里的debugger.cpp和command.cpp,这两份文件基本能把调试循环和命令系统的核心逻辑串起来了。
本文还有配套的精品资源,点击获取