x64dbg 的 DataMiddle 命令详解:数据标记与“中间字节“的底层原理
2026/9/19 22:46:49 网站建设 项目流程

x64dbg 的 DataMiddle 命令详解:数据标记与"中间字节"的底层原理

【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg

导读

DataMiddle是 x64dbg 命令体系中用于将指定地址处的内存标记为 Middle 状态的基础命令,它与DataByteDataDwordDataUnknown等命令共同构成了反汇编视图中的数据编码标记系统。读完本文,你将掌握DataMiddle的完整语法与参数规则,理解它在 x64dbg 编码映射(EncodeMap)机制中扮演的角色——尤其是多字节数据类型"中间字节"的填充逻辑,并能结合源码调用链在实际调试中正确使用它。

本文以 docs/commands/types/DataMiddle.md 为骨架,并结合 src/dbg/commands/cmd-types.cpp 与 src/dbg/encodemap.cpp 的底层实现展开讲解。

DataMiddle 是什么

在 x64dbg 中,CPU 反汇编视图中的每一个内存位置都对应一个编码类型(ENCODETYPE),用于告诉调试器"这个字节应该按什么语义来解析和显示"。x64dbg 使用一个名为 EncodeMap 的编码映射表来记录这些信息,相关类型定义与接口声明位于 src/dbg/encodemap.h。

DataMiddle命令的作用正如其名:将指定地址处的数据标记为 Middle(中间)状态。Middle 并不是一种独立的数据格式,而是多字节数据类型(如 word、dword、qword、float、double 等)中"非起始字节"的占位标记——它表示"该字节属于某个多字节数据的中间部分"。

命令语法与参数

DataMiddle的完整语法如下:

DataMiddle arg1 [, arg2]

参数说明

参数含义是否必填默认值
arg1要标记的目标内存地址必填
arg2要标记的字节大小(Size,单位为字节)可选1

参数要点:

  • arg1地址:可以是绝对地址,也可以是 x64dbg 表达式(expression)。命令实现中通过valfromstring(argv[1], &addr, false)解析该值,因此支持寄存器、符号、dis.sel()之类的表达式形式(见 src/dbg/commands/cmd-types.cpp)。
  • arg2大小:指定从arg1开始、要标记为 Middle 的连续字节数。当不提供该参数时,默认按1字节处理(见 src/dbg/commands/cmd-types.cpp)。大小同样支持表达式求值。

结果(result)

DataMiddle不会设置任何结果变量。也就是说,执行完毕后无法通过$result或类似变量读取返回值,判断命令是否成功只能依靠命令的退出状态(成功返回 true,失败返回 false)。

命令注册与在命令体系中的位置

DataMiddle在调试器初始化阶段被注册为内置命令,注册代码位于 src/dbg/x64dbg.cpp:

dbgcmdnew("DataMiddle", cbInstrDataMiddle, true); //mark as Middle

从 src/dbg/x64dbg.cpp 附近的注册序列可以看出,它属于同一组"数据标记"命令,与DataUnknownDataByteDataWordDataDwordDataCodeDataJunk等并列。这组命令的全部命令文档集中收录在 docs/commands/types/index.rst 中。

源码实现:从命令到编码映射

DataMiddle的命令处理函数是cbInstrDataMiddle,位于 src/dbg/commands/cmd-types.cpp:

bool cbInstrDataMiddle(int argc, char* argv[]) { return cbInstrDataGeneric(enc_middle, argc, argv); }

它本身只是一个薄封装,核心逻辑全部落在通用处理函数cbInstrDataGeneric中(src/dbg/commands/cmd-types.cpp):

static bool cbInstrDataGeneric(ENCODETYPE type, int argc, char* argv[]) { if(IsArgumentsLessThan(argc, 2)) return false; duint addr; if(!valfromstring(argv[1], &addr, false)) return false; duint size = 1; if(argc >= 3) if(!valfromstring(argv[2], &size, false)) return false; bool created; if(!EncodeMapSetType(addr, size, type, &created)) { dputs(QT_TRANSLATE_NOOP("DBG", "EncodeMapSetType failed...")); return false; } if(created) DbgCmdExec("disasm dis.sel()"); else GuiUpdateDisassemblyView(); return true; }

这段实现的执行流程可以概括为四步:

  1. 参数校验与解析:参数数量不足 2 个(缺少地址)时直接返回 false;依次解析地址arg1与可选大小arg2,任一解析失败即返回 false;
  2. 写入编码映射:调用EncodeMapSetType(addr, size, type, &created)将类型写入 EncodeMap;
  3. 失败提示:若写入失败,向日志输出EncodeMapSetType failed...并返回 false;
  4. 界面刷新:若该地址之前没有编码映射条目(created为 true),则执行disasm dis.sel()强制重新反汇编当前选中地址;否则调用GuiUpdateDisassemblyView()刷新反汇编视图。无论哪种方式,标记结果都会立即在 CPU 视图中呈现。

核心原理:EncodeMapSetType 如何写"中间字节"

DataMiddle真正触碰底层逻辑的地方是EncodeMapSetType(src/dbg/encodemap.cpp)。理解它,就能理解 Middle 标记为何重要。

bool EncodeMapSetType(duint addr, duint size, ENCODETYPE type, bool* created) { ENCODEMAP map; duint base; if(!EncodeMapGetorCreate(addr, map, &base, created)) return false; auto offset = addr - base; size = std::min(map.size - offset, size); auto datasize = GetEncodeTypeSize(type); if(datasize == 1 && !IsCodeType(type)) { memset(map.data + offset, (uint8_t)type, size); } else { memset(map.data + offset, (uint8_t)enc_middle, size); ... } ... }

关键逻辑分析:

  • 类型大小查询GetEncodeTypeSize(src/dbg/encodemap.cpp)按类型返回其字节宽度,例如enc_byte为 1、enc_word为 2、enc_dword为 4、enc_qword为 8、enc_oword为 16、enc_ymmword为 32 等。
  • 两分支写入策略:当类型的字节宽度为 1 且不是代码类型时(如enc_byteenc_asciienc_unknownenc_middle),直接以该类型逐字节填充目标区间;否则(多字节类型)会先用enc_middle填充整个区间,再按datasize间隔把每个数据单元的起始字节写成目标类型,其余字节保持 Middle。
  • 尾部回收:写入完成后,区间末尾之后紧邻的 Middle 字节会被恢复为enc_unknown(src/dbg/encodemap.cpp),避免"中间状态"无限向后蔓延。

由此可以推断:enc_middle是编码映射内部用于填充多字节数据"缝隙"的哨兵值。例如把地址标记为 dword 时,第一个字节是enc_dword,随后 3 个字节是enc_middle,反汇编器据此知道这 4 个字节属于同一个 32 位数据单元。当用户手动执行DataMiddle时,本质上就是直接把这些字节置为"中间填充"状态,通常用于覆盖之前多字节标记遗留的中间字节,或在组合使用DataByte等单字节类型标记前清理区间。

典型使用场景与示例

场景一:清理多字节标记的残留中间字节

之前用DataDword把连续区域标记为 4 字节 dword 后,其中间字节均为 Middle。若只想把某一块重新标记为字节序列,可先用DataMiddle铺底:

DataMiddle 0x401000, 16

含义:将0x401000起始的 16 个字节全部标记为 Middle。

场景二:配合 DataUnknown 重置区域

系统默认情况下,未知区域会被EncodeMapGetType返回为enc_unknown(见 src/dbg/encodemap.cpp),而EncodeMapDelRange在删除整段编码时也是通过EncodeMapSetType(Start, End - Start + 1, enc_unknown)实现的(src/dbg/encodemap.cpp)。可见 Middle 与 Unknown 是两个互补的"非数据"标记:前者表示"多字节数据的内部字节",后者表示"未定义/未知"。在自定义数据布局时,可以先用DataUnknownDataMiddle重置区间,再按需标记。

场景三:表达式中使用

由于arg1支持表达式,可以直接绑定当前选中地址:

DataMiddle dis.sel()

这条命令会把反汇编视图中当前选中的地址标记为 Middle(默认大小 1 字节)。

相关命令一览

DataMiddle属于types命令组,同组命令及其文档包括:

命令作用文档
DataUnknown标记为 Unknown(未知)DataUnknown.md
DataByte标记为 1 字节数据DataByte.md
DataWord标记为 2 字节数据DataWord.md
DataDword标记为 4 字节数据DataDword.md
DataQword标记为 8 字节数据DataQword.md
DataCode/dc标记为代码DataCode.md
DataJunk标记为 Junk(垃圾/填充字节)DataJunk.md

所有类型命令的实现都复用cbInstrDataGeneric(src/dbg/commands/cmd-types.cpp),区别仅在于传入的ENCODETYPE常量,这也解释了为何它们的语法与行为高度一致。

小结

  • DataMiddle <addr> [, <size>]将指定地址(默认 1 字节)标记为 Middle 状态,不设置任何结果变量;
  • Middle 是 EncodeMap 中多字节数据类型的"中间字节"哨兵,由EncodeMapSetType自动生成,也可手动标记;
  • 底层实现路径为:命令注册(src/dbg/x64dbg.cpp)→ 命令处理器(src/dbg/commands/cmd-types.cpp)→ 通用处理cbInstrDataGeneric→ 编码映射EncodeMapSetType(src/dbg/encodemap.cpp);
  • 使用DataMiddle时注意它不会刷新结果变量,判断成败需依赖命令退出状态与反汇编视图的即时刷新。

在日常逆向与恶意样本分析中,理解DataMiddle与编码映射机制,能帮助你更精准地控制反汇编视图的数据解释方式,避免多字节标记残留导致的误读。

【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询