免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Dependencies 源码解析①:PE 文件内存映射全景图——Process Hacker phlib 到底做了什么

Dependencies 源码解析①:PE 文件内存映射全景图——Process Hacker phlib 到底做了什么 Dependencies 源码解析①PE 文件内存映射全景图——Process Hacker phlib 到底做了什么【免费下载链接】DependenciesA rewrite of the old legacy software depends.exe in C# for Windows devs to troubleshoot dll load dependencies issues.项目地址: https://gitcode.com/gh_mirrors/de/DependenciesDependencies是一个用 C# 重写的经典 Windows 开发工具旧版 depends.exe帮助开发者快速排查PE 文件.exe / .dll的DLL 依赖加载问题——比如缺少某个 DLL、位数不匹配这类让新手抓狂的报错。它的解析引擎并非从零手搓而是深度集成了 Process Hacker 项目中的phlib内存映射库。本文带你走一遍 PE 文件从磁盘到内存的完整链路看懂 phlib 到底做了什么。一、先建立全景图一次 PE 解析的调用链当你把一个 exe 拖进 Dependencies 时数据是这样流动的C# 业务层 (DependenciesLib/FindPeModule.cs) │ 调用 PE 类的 Load() ▼ C/CLI 桥接层 (ClrPhlib/src/managed/PE.cpp) │ 把托管字符串转成原生宽字符串 ▼ UnmanagedPE 封装 (ClrPhlib/src/unmanaged/UnmanagedPE.cpp) │ 持有 PH_MAPPED_IMAGE 结构体 ▼ Process Hacker phlib (third_party/phlib/mapimg.c) │ NtCreateSection NtMapViewOfSection ▼ Windows 内存映射视图 —— PE 内容以指针偏移方式被读取理解这条链的关键是phlib 把整个 PE 文件一次性映射进进程地址空间之后所有解析都只是在内存视图里按偏移取数据不再碰磁盘。这就是它又快又稳的根本原因。二、PE 文件如何进入内存内存映射技术1. 打开文件并创建内核 Section 对象入口函数在 mapimg.c 的PhLoadMappedImage它先调用PhMapViewOfEntireFile完成三件事以只读方式打开目标文件允许其他进程同时读取调用NtCreateSection基于文件创建 Section 对象调用NtMapViewOfSection把整个文件映射到当前进程地址空间得到一个viewBase指针。相关实现可精读 mapimg.c。这里有两个值得注意的细节权限按需申请只读解析只申请FILE_READ_ATTRIBUTES | FILE_READ_DATA不会锁死文件卸载同样直接PhUnloadMappedImage只是一句NtUnmapViewOfSection立即释放内存并解除对文件的占用见 mapimg.c。2. PhInitializeMappedImage逐层体检 PE 结构拿到内存视图后PhInitializeMappedImage 开始校验文件是否真的是一个合法 PE校验步骤做什么失败返回值DOS 头魔数检查文件头是否为MZSTATUS_INVALID_IMAGE_NOT_MZe_lfanew 偏移必须非 0 且小于文件大小STATUS_INVALID_IMAGE_FORMATPE 签名检查PE\0\0签名STATUS_INVALID_IMAGE_FORMATOptional Header 魔数必须是 32 位或 64 位魔数STATUS_INVALID_IMAGE_FORMAT内存探测用 SEH 异常探测指针访问越界返回异常码最后它把NtHeaders指针、节区表、可选头信息全部填进PH_MAPPED_IMAGE结构体——这个结构体就是后续所有解析的锚点。为什么用 SEH 探测而不是先判断长度因为 PE 文件可能被恶意构造或截断光看声明的长度不可信真正访问一下内存才能确认数据安全。三、映射之后phlib 提供的三大解析能力PH_MAPPED_IMAGE就绪后mapimg.h 中声明的一系列 API 就能无磁盘 I/O地读取 PE 的各个目录PhGetMappedImageImports/PhGetMappedImageDelayImports解析导入表含延迟加载导入返回按 DLL 分组的PH_MAPPED_IMAGE_IMPORTS结构——这正是 Dependencies 界面里导入的 DLL 列表的数据来源PhGetMappedImageExports解析导出表得到PH_MAPPED_IMAGE_EXPORTS支持按名称或序号查函数地址PhGetMappedImageResources枚举资源段用于提取嵌入的清单manifestPhCheckSumMappedImage计算 PE 校验和与文件头声明值比对判断二进制是否被修改过。四、ClrPhlib 桥接层C# 如何拥抱 phlibphlib 是纯 C 代码而 Dependencies 上层是 C#。两者之间的翻译官是ClrPhlib项目C/CLI核心结构分两层1. UnmanagedPE —— 原生封装层UnmanagedPh.h 中定义的UnmanagedPE类直接持有PH_MAPPED_IMAGE、导入/延迟导入/导出等原生结构体。它的 LoadPE 方法 只做了两件事确保旧映像已卸载然后调用PhLoadMappedImage加载新文件。析构时自动卸载杜绝内存泄漏。2. PE 类 —— C# 可见的托管门面PE.cpp 把上述原生能力翻译成 C# 友好的接口公开类型在 ClrPhlib.h 中声明Load()/Unload()映射与卸载并填充PeProperties机器类型、映像基址、入口点、子系统、校验和等见 InitPropertiesGetImports()/GetExports()惰性解析 本地缓存首次调用才解析之后直接返回缓存列表GetManifest()从资源段提取 UTF-8 嵌入清单供 SxS侧载分析使用IsWow64Dll()/GetProcessor()判断 32 位/64 位、x86/arm/arm64 架构——这正是32 位进程装 64 位 DLL 失败这类问题的判断依据。一个容易被忽视的细节NativeFileNativeFile.h 是System.IO.File的部分重写专门绕开 64 位系统中的WoW64 文件系统重定向32 位程序访问 System32 会被悄悄转到 SysWOW64。分析 DLL 依赖时这一点致命——它保证了 32 位 Dependencies 也能如实看到 64 位目录下的文件还顺带实现了基于 bcrypt 的 SHA256 文件哈希。五、源码导读建议按这个顺序精读顺序文件看点1third_party/phlib/mapimg.c内存映射与 PE 校验的完整实现约 1800 行2third_party/phlib/include/mapimg.hPH_MAPPED_IMAGE系列结构与全部 API 声明3ClrPhlib/src/unmanaged/UnmanagedPE.cpp最薄的原生封装理解桥接层的最佳起点4ClrPhlib/src/managed/PE.cpp托管门面缓存策略、清单提取、架构判断5DependenciesLib/FindPeModule.csC# 业务层如何组合使用 PE 类做 DLL 搜索6test/manifest-regress/清单回归测试样例含.dll.manifest实例 后续文章会基于这条链路继续深入ApiSet Schema 解析Windows 8 之后 DLL 名称如何被虚拟化与DLL 搜索顺序模拟FindPeModule 是如何复刻加载器行为的。六、小结用三句话记住本文phlib 的核心思想是先映射、后解析——一次NtCreateSection NtMapViewOfSection把 PE 文件搬进内存视图之后全是指针运算健壮性靠 SEH 探测——不信任文件自报的头部长度访问即校验畸形 PE 不会让工具崩溃分层清晰——C 层phlib做重活C/CLI 层ClrPhlib做翻译C# 层DependenciesLib做体验各层通过PH_MAPPED_IMAGE这一个结构体解耦。掌握这条 PE 内存映射全景链路你就拿到了读懂 Dependencies 整个解析引擎的地图。️【免费下载链接】DependenciesA rewrite of the old legacy software depends.exe in C# for Windows devs to troubleshoot dll load dependencies issues.项目地址: https://gitcode.com/gh_mirrors/de/Dependencies创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表