x64dbg 插件 API 详解:_plugin_hash哈希函数与 MurmurHash3 底层实现
【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg
本篇技术指南聚焦 x64dbg 插件开发接口中一个实用但容易被忽略的导出函数 ——_plugin_hash。它允许插件对任意内存数据计算哈希值,是 x64dbg 内部用于模块内容校验、模块名索引等场景的基础工具。读完本文,你将掌握_plugin_hash的签名语义、参数与返回值约定、其底层 MurmurHash3 算法的平台差异,以及如何在自己的插件中安全地调用它。
函数概述:插件侧的通用哈希入口
_plugin_hash是 x64dbg 向插件导出的_plugin_系列函数之一,定义于 docs/developers/plugins/API/hash.rst。该函数的定位非常明确:对一段内存数据计算哈希值,文档原文指出它被 x64dbg 在多个内部位置使用("It is used by x64dbg in various places")。
函数原型如下:
duint _plugin_hash( const void* data, // data to hash duint size // size (in bytes) of the data to hash );从源码看,该函数的声明位于 src/dbg/_plugins.h,实现位于 src/dbg/_plugins.cpp:
duint _plugin_hash(const void* data, duint size) { return murmurhash(data, (size_t)size); }可以看到,_plugin_hash本身是一个轻量封装,实际计算工作全部委托给 x64dbg 内部的murmurhash函数。插件在需要"对数据算个哈希"时,无需自行引入哈希库,直接调用此接口即可与 x64dbg 内部使用同一套哈希语义。
参数与返回值详解
参数data
指向待哈希数据的起始地址,类型为const void*。该参数对数据内容不做任何假设,任意字节序列(包括包含\0的二进制数据)都可以作为输入,因为哈希长度完全由第二个参数size决定,而非依赖字符串终止符。
参数size
待哈希数据的长度,单位是字节,类型为duint。duint是 x64dbg 定义的"调试器地址宽度整数"类型,其定义见 src/dbg_types.h:
- 64 位构建下为
unsigned long long(64 位无符号整数); - 32 位构建下为
unsigned long __w64(32 位无符号整数)。
因此_plugin_hash的返回值宽度与当前构建的位数一致:x64dbg x64 版返回 64 位哈希,x86 版返回 32 位哈希。调用方在跨位数场景(例如插件同时兼容 x32/x64 版 x64dbg)下应注意这一差异。
返回值
返回计算得到的哈希值,类型同样为duint。注意这是非加密哈希(non-cryptographic hash),仅适合作为索引、比对、去重等用途,不能用于密码存储、数字签名等安全敏感场景。
底层实现:MurmurHash3 及 x64dbg 的定制
murmurhash的实现位于 src/dbg/murmurhash.cpp,头文件为 src/dbg/murmurhash.h。文件头部注释明确说明:MurmurHash3 由 Austin Appleby 编写并置于公有领域,x64dbg 直接内嵌了该算法。
平台相关的算法选择
在 src/dbg/murmurhash.h 中,murmurhash根据构建平台选择不同的 MurmurHash3 变体:
#ifdef _WIN64 static inline unsigned long long murmurhash(const void* data, size_t len) { unsigned long long hash[2]; MurmurHash3_x64_128(data, len, 0x1337, hash); #else // x86 static inline unsigned long murmurhash(const void* data, size_t len) { unsigned int hash[1]; MurmurHash3_x86_32(data, len, 0x1337, hash); #endif // _WIN64 return hash[0]; }关键点:
- x64 构建:调用
MurmurHash3_x64_128(128 位输出),种子(seed)固定为0x1337,最终只取输出缓冲区中第一个 64 位字作为返回值; - x86 构建:调用
MurmurHash3_x86_32(32 位输出),种子同样是0x1337,取第一个 32 位字返回。
由此可以推断:同一份数据在 x32/x64 版 x64dbg 下得到的哈希值是不同的。如果你的插件需要把哈希值持久化到数据库、配置文件或与远端比对,必须标注其对应的构建位数,避免跨位数比对出错。
支持超过 2GB 数据的定制修改
src/dbg/murmurhash.cpp 顶部有一处非常有价值的改动记录:
// x64dbg: changed 'int len' to 'size_t len' and converted loops to forward // indexing to support files >2GB. See: https://github.com/x64dbg/x64dbg/issues/3583也就是说,x64dbg 将 MurmurHash3 原始实现中的int len改为size_t len,并将循环改为正向索引,从而支持对超过 2GB 的数据进行哈希。这意味着_plugin_hash传入的size在 x64 构建下理论上可覆盖到 64 位无符号整数范围,插件对超大缓冲区(如大型转储、大文件映射)计算哈希是安全的。
x64dbg 内部的典型应用场景
文档称该函数"被 x64dbg 在多个位置使用",从源码中可以找到两个代表性案例,它们也恰好展示了插件可以借鉴的用法。
模块内容哈希:ModContentHashFromAddr
在 src/dbg/module.cpp 中,x64dbg 用murmurhash对模块的文件映射内容整体计算哈希:
duint ModContentHashFromAddr(duint Address) { SHARED_ACQUIRE(LockModules); auto module = ModInfoFromAddr(Address); if(!module) return 0; if(module->fileMapVA != 0 && module->loadedSize > 0) return murmurhash((void*)module->fileMapVA, module->loadedSize); else return 0; }这里data是模块文件映射的起始虚拟地址(fileMapVA),size是模块已加载的大小(loadedSize)。该哈希用于描述"模块内容",是检测模块内容变化、建立模块内容指纹的基础。插件若想对目标进程某个模块的文件内容做一致性校验,可以采用完全相同的思路:先读取模块文件内容到缓冲区,再调用_plugin_hash。
模块名哈希:ModHashFromName
在 src/dbg/module.cpp 中,murmurhash还被用于对模块名计算哈希:
duint ModHashFromName(const char* Module, bool tolower) { // return MODINFO.hash (based on the name) ASSERT_NONNULL(Module); auto len = strlen(Module); if(!len) return 0; duint hash = 0; if(tolower) { auto & moduleLower = TLSData::get()->moduleHashLower; moduleLower.clear(); for(size_t i = 0; i < len; i++) moduleLower.push_back(StringUtils::ToLower(Module[i])); hash = murmurhash(moduleLower.c_str(), moduleLower.size()); } else { hash = murmurhash(Module, len); } // update the hash cache ... }注意此处演示了一个实用技巧:当需要大小写不敏感的哈希时,先把输入统一转换为小写再哈希,而不是引入大小写折叠的哈希算法。插件在建立以模块名或字符串为键的索引表时,可以参考这一模式。
在插件中调用_plugin_hash的示例
在插件代码中,只需包含插件头文件(_plugins.h)即可直接调用。下面是一个对模块文件内容计算哈希的参考示例:
#include "_plugins.h" #include <stdio.h> // 读取文件内容并计算其哈希,返回哈希值;失败时返回 0 duint HashFileContents(const char* fileName) { FILE* f = fopen(fileName, "rb"); if(!f) return 0; fseek(f, 0, SEEK_END); long fileSize = ftell(f); fseek(f, 0, SEEK_SET); if(fileSize <= 0) { fclose(f); return 0; } unsigned char* buffer = (unsigned char*)malloc((size_t)fileSize); if(!buffer) { fclose(f); return 0; } size_t readBytes = fread(buffer, 1, (size_t)fileSize, f); fclose(f); duint result = 0; if(readBytes == (size_t)fileSize) result = _plugin_hash(buffer, (duint)readBytes); free(buffer); return result; }在插件菜单命令或回调中即可调用该函数,例如:
duint h = HashFileContents("C:\\target\\module.dll"); if(h != 0) _plugin_logprintf("module content hash: %llX\n", (unsigned long long)h);使用注意事项
- 非加密用途:MurmurHash3 是高速非加密哈希,碰撞概率在数据量适中时可接受,但不应用于任何需要抗恶意构造输入的场合。
- 位数差异:x32 与 x64 构建使用不同的算法变体且返回值宽度不同,同一数据的哈希结果不一致。跨构建共享哈希值时需注明构建位数。
- 种子固定:内部固定使用种子
0x1337,x64dbg 内部各处的哈希(模块内容、模块名)都基于同一种子,插件可直接复用这一语义。 - 超大输入:得益于 src/dbg/murmurhash.cpp 中的
size_t改造,超过 2GB 的数据也可安全哈希,但实际传入size时仍受duint宽度约束(x86 构建下为 32 位上限)。
相关文档与源码索引
- 函数官方文档:docs/developers/plugins/API/hash.rst
- 插件 API 函数总览:docs/developers/plugins/API/index.rst
- 函数声明:src/dbg/_plugins.h
- 函数实现:src/dbg/_plugins.cpp
- 底层算法实现:src/dbg/murmurhash.cpp、src/dbg/murmurhash.h
- 内部应用示例:src/dbg/module.cpp(
ModContentHashFromAddr与ModHashFromName) duint类型定义:src/dbg_types.h
【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考