十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

x64dbg DbgMemRead 内存读取 API 详解:从桥接调用到页级读取实现

x64dbg DbgMemRead 内存读取 API 详解:从桥接调用到页级读取实现 x64dbg DbgMemRead 内存读取 API 详解从桥接调用到页级读取实现【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbgDbgMemRead是 x64dbg 调试器导出的核心内存读取接口允许插件、脚本扩展与 GUI 组件以进程虚拟地址为参数安全地从被调试进程中读取任意长度的字节数据。它是 x64dbg 中一切基于读内存实现的地址解析、反汇编预览、数据提取与表达式求值的基础设施。读完本文你将掌握DbgMemRead的完整签名与参数语义、返回值与失败时的边界行为、从桥接层到调试器内核MemRead的完整调用链与页级分块读取原理并能在自己的插件代码中正确、健壮地使用它。函数签名与参数语义DbgMemRead在桥接层bridge以 C 导出函数的形式对外暴露声明位于 src/bridge/bridgemain.h原型如下bool DbgMemRead( duint va, void* dest, duint size );参数类型含义vaduint源虚拟地址Virtual address即被调试进程内存空间中的起始地址destvoid*指向调用方预先分配缓冲区的指针缓冲区大小必须不小于size字节sizeduint希望读取的字节数关于类型duint它是 x64dbg 中代表调试器地址宽度的整型在 32 位构建下对应 32 位无符号整数在 64 位构建下对应 64 位无符号整数与目标架构的指针宽度保持一致可从桥接层的类型体系推断。因此调用方在 64 位调试会话中应将va视为 64 位地址且同一份插件源码可以跨 32/64 位构建使用而无需改动参数类型。返回值与失败语义DbgMemRead在读取成功时返回true此时dest中填充了从va开始的size字节数据读取失败时返回false。需要特别注意的是失败时的行为——桥接层实现 src/bridge/bridgemain.cpp 在底层读取失败后会先对dest指向的缓冲区执行memset(dest, 0, size)清零再返回falseBRIDGE_IMPEXP bool DbgMemRead(duint va, void* dest, duint size) { #ifdef _DEBUG if(IsBadWritePtr(dest, size)) { GuiAddLogMessage(DbgMemRead with invalid boundaries!\n); __debugbreak(); return false; } #endif //_DEBUG if(!_dbg_memread(va, dest, size, 0)) { // Zero the buffer on failure memset(dest, 0, size); return false; } return true; }这带来两个明确的约定缓冲区必须可写在_DEBUG构建中如果dest指向的内存不可写或大小越界会向 GUI 日志输出DbgMemRead with invalid boundaries!并触发__debugbreak()断言中断。发布构建下该检查被编译排除但调用方仍应保证缓冲区合法。失败后缓冲区内容确定失败时缓冲区会被清零因此调用方不必担心读到未初始化的残留数据但绝不能依赖失败时缓冲区为全零来判断读取失败——正确的做法是始终检查返回值。此外底层读取在跨页读取未读满时会通过SetLastError设置ERROR_PARTIAL_COPY见下文MemRead实现失败调用方可以通过GetLastError进一步诊断原因。调用链与底层实现原理DbgMemRead只是桥接层的一层薄封装其完整调用链为DbgMemRead (bridge) └─ _dbg_memread (debugger 导出) └─ MemRead (内存子系统核心) └─ MemoryReadSafePage ├─ IgnoreThisRead (工作集页面规避) └─ MemoryReadSafe桥接层到调试器内核DbgMemRead内部调用调试器动态库导出的_dbg_memread见 src/dbg/_exports.cppextern C DLL_EXPORT bool _dbg_memread(duint addr, unsigned char* dest, duint size, duint* read) { return MemRead(addr, dest, size, read); }注意最后一个参数read实际读取字节数的输出指针在DbgMemRead中被传为0即桥接层不关心精确读到的字节数只关心整体成功与否。页级分块读取真正的读取逻辑集中在内存子系统的MemRead实现位于 src/dbg/memory.cpp其核心策略是以内存页PAGE_SIZE即 0x1000 字节为单位分块读取从而避免一次ReadProcessMemory跨越无效页面导致整体失败bool MemRead(duint BaseAddress, void* Buffer, duint Size, duint* NumberOfBytesRead, bool cache) { if(!Buffer) return false; if(!MemIsCanonicalAddress(BaseAddress) || !DbgIsDebugging()) return false; if(cache !MemIsValidReadPtr(BaseAddress, true)) return false; duint bytesReadTemp 0; if(!NumberOfBytesRead) NumberOfBytesRead bytesReadTemp; *NumberOfBytesRead 0; duint offset 0; duint requestedSize Size; duint sizeLeftInFirstPage PAGE_SIZE - (BaseAddress (PAGE_SIZE - 1)); duint readSize std::min(sizeLeftInFirstPage, requestedSize); while(readSize) { SIZE_T bytesRead 0; auto readSuccess MemoryReadSafePage(fdProcessInfo-hProcess, (PVOID)(BaseAddress offset), (PBYTE)Buffer offset, readSize, bytesRead); *NumberOfBytesRead bytesRead; if(!readSuccess) break; offset readSize; requestedSize - readSize; readSize std::min((duint)PAGE_SIZE, requestedSize); ... } auto success *NumberOfBytesRead Size; SetLastError(success ? ERROR_SUCCESS : ERROR_PARTIAL_COPY); return success; }从实现中可以提炼出以下关键行为前置校验缓冲区为空指针直接失败地址必须满足canonical 地址检查MemIsCanonicalAddress且当前必须处于调试会话中DbgIsDebugging。也就是说未附加/未启动被调试进程时调用DbgMemRead必然返回false。首页对齐处理第一次读取的大小被裁剪为当前地址到页尾的剩余空间PAGE_SIZE - (BaseAddress (PAGE_SIZE - 1))保证第一次读取不会越出所在页之后每次以整页PAGE_SIZE为单位推进直至读完Size字节。逐页容错任何一页读取失败即break中断循环最终以实际读到的字节数 请求的字节数作为成功判定*NumberOfBytesRead Size并设置ERROR_SUCCESS或ERROR_PARTIAL_COPY。安全页读取每页实际通过MemoryReadSafePage完成src/dbg/memory.cpp。该函数会先调用IgnoreThisRead当启用了工作集查询bQueryWorkingSet且目标页不在进程工作集内QueryWorkingSetEx判定VirtualAttributes.Valid为假时直接以全零填充缓冲区并视为成功这是为了规避某些反调试手段对异常页读取的检测否则回退到常规的MemoryReadSafe读取路径。从源码结构可以推断这种分页 部分读取容忍的设计使得DbgMemRead非常适合读取可能横跨多个内存区间的连续数据块例如一段用户选中的指令字节而不会因为某一页不可读就丢弃整块数据。完整示例读取反汇编窗口选中数据原文档给出的示例展示了最典型的用法——把 GUI 反汇编窗口中用户选中的地址范围对应的字节数据读取出来。下面是对该示例的完整展开并补充了失败处理// read user selected data from disassembly window SELECTIONDATA sel; if(!GuiSelectionGet(GUI_DISASSEMBLY, sel)) return; // 获取选区失败例如没有活动反汇编视图 uint16_t size sel.end - sel.start 1; uint8_t* dest new uint8_t[size]; bool success DbgMemRead(sel.start, dest, size); // on success, the selected data is stored in dest if(success) { // 此时 dest[0..size-1] 为 sel.start 起的 size 字节内存内容 // 例如可用于保存、哈希或进一步反汇编分析 } delete[] dest; // 用完释放避免内存泄漏要点说明SELECTIONDATA是在 src/bridge/bridgemain.h 中定义的结构体包含duint start与duint end两个字段分别表示选中范围的起始与结束地址闭区间因此字节数为end - start 1。GuiSelectionGet(GUI_DISASSEMBLY, sel)与GuiSelectionSet是配套的选区读写接口src/bridge/bridgemain.hGUI_DISASSEMBLY指明目标视图为反汇编窗口x64dbg 还支持其它视图类型可参照桥接头文件中的GUISELECTIONTYPE枚举。示例使用new uint8_t[size]动态分配与size等长的缓冲区也可改用栈上定长数组或std::vectoruint8_t但必须保证dest容量不小于size否则属于未定义行为。由于失败时缓冲区会被清零调试时可以依赖该特性辅助排查但正式代码务必以返回值为准。在仓库中的实际使用场景DbgMemRead在整个 GUI 与测试代码中被广泛使用以下两个例子可帮助理解其典型场景指令边界探测在 src/gui/Src/BasicView/Disassembly.cpp 中GUI 通过DbgMemRead(va, test, sizeof(test))读取指令字节以判定某地址是否构成有效的指令边界从而决定折叠/展开等交互行为。指针解引用取标签在 src/bridge/bridgemain.cpp 的DbgGetLabelAt中当地址本身没有标签时先通过DbgMemIsValidReadPtr确认可读再用DbgMemRead(addr, (unsigned char*)addr_, sizeof(duint))读取该地址处的指针值进而解析形如label的间接标签。插件与集成测试多个集成测试插件如 src/tests/cmdline_init/plugin.cpp、src/tests/issue3803/plugin.cpp都会调用DbgMemRead来验证调试器在不同命令行启动场景下内存读取的正确性。这些调用都遵循同一模式先确认地址可读/有效再以小尺寸或分页方式读取最后依据返回值做分支处理。相关 API 家族DbgMemRead是内存访问 API 家族中的一员与它配套的接口均声明于 src/bridge/bridgemain.h插件开发时通常组合使用函数作用DbgMemRead(duint va, void* dest, duint size)从被调试进程地址va读取size字节到destDbgMemWrite(duint va, const void* src, duint size)向被调试进程地址va写入size字节是读取的逆操作桥接实现见 src/bridge/bridgemain.cppDbgMemIsValidReadPtr(duint addr)检查地址是否可安全读取常用于读取前预检底层对应_dbg_memisvalidreadptr见 src/dbg/_exports.cppDbgMemFindBaseAddr(duint addr, duint* size)返回addr所在内存区块的基址可同时输出区块大小用于把任意地址规整到区块范围DbgMemGetPageSize(duint base)获取base所在页/区块的大小src/bridge/bridgemain.cppDbgMemMap(MEMMAP* memmap)获取被调试进程的完整内存映射区块列表用于遍历内存布局src/bridge/bridgemain.cpp推荐的最佳实践组合是先用DbgMemIsValidReadPtr预检再调用DbgMemRead并始终检查返回值如需把读取限制在某区块内可先用DbgMemFindBaseAddr/DbgMemGetPageSize确定范围。注意事项与最佳实践必须处于调试会话中未启动或未附加进程时MemRead会因DbgIsDebugging()为假而直接失败调用前建议用DbgIsDebugging()判断。缓冲区容量dest必须至少有size字节的可写空间_DEBUG构建下会通过IsBadWritePtr校验越界会触发断言并输出日志DbgMemRead with invalid boundaries!。失败即清零读取失败后缓冲区被memset清零且SetLastError会被设为ERROR_PARTIAL_COPY或ERROR_SUCCESS可用GetLastError辅助诊断。地址有效性非 canonical 地址会被MemIsCanonicalAddress拦截跨页读取是安全的内部按页分块、容忍单页失败但读取结果可能是不完整的务必以返回值与NumberOfBytesRead语义为准。与选区配合读取 GUI 选中内容时注意SELECTIONDATA的start/end是闭区间字节数计算为end - start 1GuiSelectionGet本身也可能失败需检查其返回值。线程安全DbgMemRead是桥接层通用读取接口被 GUI 各视图与测试插件广泛并发调用从源码结构看其设计目标即支持在调试会话内的任意时刻安全读取但插件侧仍应避免在调试器关键暂停/恢复状态转换的瞬间发起批量读取。【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表