Lunar PE解析完整指南:PeImage如何用C优雅封装PE文件6大数据目录

📅 2026/8/22 13:27:36
Lunar PE解析完整指南:PeImage如何用C优雅封装PE文件6大数据目录
Lunar PE解析完整指南PeImage如何用C#优雅封装PE文件6大数据目录【免费下载链接】LunarA lightweight native DLL mapping library that supports mapping directly from memory项目地址: https://gitcode.com/gh_mirrors/luna/LunarLunar 是一个轻量级原生 DLL 映射库支持直接从内存将 DLL 映射到任意进程中。它的核心难点之一就是在没有操作系统加载器帮忙的情况下自己读懂 PE 文件。这篇文章带你了解 Lunar 中的 PE 解析引擎——PeImage类看看它是如何用 C# 优雅地封装 PE 文件的 6 大数据目录的帮助新手快速理解 PE 结构与 DLL 映射库的设计思路。为什么要自己解析 PE 文件正常情况下Windows 加载器会替我们把 PE 文件翻译进内存。但 Lunar 要做的是把 DLL 映射到别的进程里加载器帮不上忙了。️于是 Lunar 必须亲自上阵找到 DLL 导出了哪些函数导出表找到它依赖哪些函数导入表处理地址搬迁重定位表初始化 TLS 回调、安全 Cookie 等好消息是这些数据的位置地图都写在 PE 头部的**数据目录表Data Directory Table**里。Lunar 只需要沿着地图把 6 个关键目录读出来映射工作就有据可查了。PeImage一个入口统揽 6 大数据目录PE 解析的总入口是 PeImage.cs。它的设计非常克制——整个类就做了三件事用 .NET 内置的PEReader读取 PE 头校验输入确实是一个 DLL不是就直接抛异常一口气构造出 6 个数据目录对象ExportDirectory new ExportDirectory(peReader.PEHeaders, imageBytes); ImportDirectory new ImportDirectory(peReader.PEHeaders, imageBytes); // ... LoadConfig / Relocation / Resource / Tls 同理这 6 个目录恰好对应一个 DLL 映射所需的全部信息数据目录作用源码位置Export 导出表按名称/序号查找导出函数ExportDirectory.csImport 导入表列出依赖的外部 DLL 与函数ImportDirectory.csLoad Config 加载配置安全 Cookie、异常处理、CFG 标志LoadConfigDirectory.csRelocation 重定位表映射地址变化时该修正哪些位置RelocationDirectory.csResource 资源表提取 SxS 清单依赖解析用ResourceDirectory.csTLS 线程存储表枚举 TLS 回调映射后需执行TlsDirectory.cs基类 DataDirectoryBase把最脏的活收拢在一处PE 文件中最让人头疼的概念是RVA相对虚拟地址——PE 头里记录的所有位置都不是文件偏移而是内存中的相对位置。如果每个目录类都自己写一遍换算逻辑代码会又乱又容易错。Lunar 的解法是抽象基类 DataDirectoryBase.cs它统一提供RvaToOffsetRVA → 文件偏移核心公式是rva - section.VirtualAddress section.PointerToRawData即找到 RVA 所在节再把节内位置换算成文件中的实际位置VaToRva虚拟地址 → RVA只需减去镜像基址IsValid该目录在 PE 头中是否存在很多 DLL 的某些目录是空的这个设计让 6 个子类都能专注读什么而不用操心在哪里读。逐个拆解 6 大数据目录的实现1️⃣ 导出目录用二分查找定位函数导出表是映射后找入口点的关键。Lunar 在 ExportDirectory.cs 中实现了一个漂亮的细节按名称查找导出函数时走二分查找而不是线性遍历——因为 PE 规范保证导出名称表是排序的。它还正确处理了函数转发Forwarding如果函数地址落在导出目录范围内说明这不是真实函数而是指向另一个 DLL 的字符串转发。查找结果通过 ExportedFunction.cs 这个简洁的 record 返回转发字符串和地址都清清楚楚。2️⃣ 导入目录描述符 双重表结构导入表是依赖清单。ImportDirectory.cs 的读取流程逐个读取ImageImportDescriptor描述符遇到FirstThunk 0表示列表结束每个描述符包含一个 DLL 名称如kernel32.dll和该 DLL 下要导入的函数列表值得注意的地方是它对PE32 与 PE64 双架构的支持见 ImportDirectory.cs并且区分了 IAT导入地址表与原始 thunk 表——这是正确解析延迟绑定等场景的关键。解析结果通过 ImportDescriptor.cs 和 ImportedFunction.cs 两个 record 返回。3️⃣ 加载配置目录安全机制的开关面板LoadConfigDirectory.cs 提取的是三项隐形但致命的信息Security Cookie栈金丝雀的随机值没有它函数一调用就崩Guard Flags控制流保护CFG相关标志异常数据SEH 异常处理表的位置与数量这些正是手动映射 DLL 时最常翻车的地方——Lunar 在 README 中把控制流保护设置、异常处理器初始化、安全 Cookie 生成列为招牌特性底气就来自这里。解析结果封装在 LoadConfigData.cs 中。4️⃣ 重定位目录位运算拆出类型与偏移如果 DLL 被映射到了和编译时不同的地址所有硬编码的绝对地址都要修正——这就是重定位表的作用。RelocationDirectory.cs 的读取逻辑很有代表性每个重定位项是一个 16 位整数高 4 位是重定位类型低 12 位是偏移量。Lunar 用两个位运算 12和 0xFFF就把它们拆开了再通过RvaToOffset把每个重定位点还原成文件偏移供后续打补丁使用。5️⃣ 资源目录三级树里捞出 SxS 清单资源表是一个三层树结构类型 → 名称/ID → 语言。Lunar 并不解析整棵树而是精准地沿 ResourceDirectory.cs 中的路径只提取它需要的那个节点24 号资源RT_MANIFEST下的 DLL 清单。拿到清单字符串后它还会做几处清洗用正则修复引号、替换SXS_ASSEMBLY_NAME等占位符最终解析成XDocument。这份清单之后会被 ActivationContext.cs 和 FileResolver.cs 用来解析 SxS 依赖重定向——也就是这个 DLL 依赖的版本实际应该加载哪个文件。6️⃣ TLS 目录枚举映射后必须执行的回调TLS线程本地存储回调是 DLL 初始化阶段的隐形钩子。TlsDirectory.cs 同样区分 PE32/PE64 两种布局从AddressOfCallBacks指向的数组中逐个读出回调地址直到遇到 0 为止每个回调都封装为 TlsCallback.cs 返回给上层由映射器在正确时机执行。设计亮点小结这套封装好在哪单一入口职责清晰PeImage.cs 只做组装每个目录一个类互不干扰公共逻辑上提基类RVA/VA 换算、有效性判断全部收敛在 DataDirectoryBase.cs子类零重复代码用 record 表达数据解析结果统一用 Records 目录下的不可变 record如ExportedFunction、LoadConfigData承载语义直观PE32/PE64 双架构透明支持差异点被隔离在各自目录类内部上层完全无感为映射目标服务每个目录解析出来的都不是好看的展示数据而是映射流程直接要用的施工图纸——找入口、补重定位、执行 TLS 回调、生成安全 Cookie一环扣一环 延伸阅读想了解这套 PE 解析结果如何被用于实际的 DLL 映射流程可以顺着这条线索阅读映射总控LibraryMapper.cs映射选项MappingFlags.cs文件依赖解析FileResolver.cs底层原生结构定义Native/Structs测试用例含 x64/x86X64Tests.cs、X86Tests.cs掌握PeImage的这套设计后你再去看任何 PE 解析器比如各类调试器、加载器、逆向工具都能一眼认出那 6 张熟悉的地图了。【免费下载链接】LunarA lightweight native DLL mapping library that supports mapping directly from memory项目地址: https://gitcode.com/gh_mirrors/luna/Lunar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考