LIEF Ghidra 插件 LoadConfiguration 分析器:还原 PE `IMAGE_LOAD_CONFIG_DIRECTORY` 结构与 ARM64EC 元数据
逆向工程开发工具【免费下载链接】LIEFLIEF - Library to Instrument Executable Formats (C, Python, Rust)项目地址https://gitcode.com/gh_mirrors/li/LIEF点击查看免费下载本篇技术指南围绕 LIEF 提供的 Ghidra 分析器Analyzer「LIEF - PE LoadConfiguration」展开讲解它如何借助 LIEF 的 PE 解析能力在 Ghidra 中重建IMAGE_LOAD_CONFIG_DIRECTORY结构的完整字段布局与正确数据类型并顺带定义IMAGE_ARM64EC_METADATA、IMAGE_CHPE_METADATA_X86等用于分析 ARM64EC 混合架构二进制的新结构。读完本文你将理解该分析器的触发条件、生成的全部数据类型与字段语义、类型在 Ghidra 中的组织方式以及如何在 GUI 与 headless 模式下运行和验证它。一、背景为什么 Ghidra 原生难以完整识别 Load ConfigurationPE 的加载配置Load Configuration由可选头中的数据目录项IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG指向其数据结构IMAGE_LOAD_CONFIG_DIRECTORY记录了与 Windows 加载器行为相关的众多安全与运行时元数据包括 SEH 异常处理表、Control Flow GuardCFG相关的函数表与标志位、/GS安全 Cookie、Return Flow Guard、热补丁表、Enclave 配置指针等。该结构由微软随每个 Windows 版本频繁演进新字段不断追加旧字段在 32/64 位平台下尺寸也不同。LIEF 在 C 头文件 include/LIEF/PE/LoadConfigurations/LoadConfiguration.hpp 中对该结构做了完整建模其类注释明确说明“This structure is frequently updated by Microsoft to add new metadata”。而截至当前Ghidra 内建的 PE 支持尚不能原生识别较新版本中引入的诸多属性。LIEF 的 Ghidra 分析器正是为解决这一缺口而设计它不再让分析师面对一堆“dq 未知语义”的裸数据而是把每个字段的名称、类型、偏移都精确地落在 Ghidra 的数据类型系统与 Listing 中。其实现位于 plugins/ghidra/src/main/java/lief/ghidra/plugins/analyzers/pe/LoadConfigurationAnalyzer.java。此外该分析器还顺带定义了 CHPECompiled Hybrid Portable Executable即 ARM64EC元数据结构帮助逆向工程师理解 ARM64EC 混合二进制中某些函数与指针的真实用途——这一点对分析 Windows on ARM 的混合代码尤其有价值。BinaryNinja 的 LIEF 插件也提供了同样的能力参见 BinaryNinja 侧的分析器文档。二、分析器注册与运行时机LoadConfigurationAnalyzer继承自 LIEFAbstractAnalyzer在构造时完成自身注册名称LIEF - PE LoadConfiguration描述增强 PELoadConfiguration及其相关结构如IMAGE_ARM64EC_METADATA的类型定义类型AnalyzerType.FUNCTION_ANALYZER函数分析器在分析流程中较早参与数据布局优先级AnalysisPriority.FORMAT_ANALYSIS.after().after()即排在格式分析阶段之后默认启用setDefaultEnablement(true)安装插件后无需手动勾选即可生效单次分析setSupportsOneTimeAnalysis()允许对已加载程序执行一次性补跑它的适用范围通过canAnalyze(Program)限定只有 PE 程序复用 Ghidra 内建PEUtil.canAnalyze(program)才会触发ELF/MachO 等格式不受影响。三、分析流程从 LIEF 解析到 Ghidra 数据落盘分析器的核心逻辑集中在added()方法整体链路如下加载 LIEF Binary 对象通过 Context.java 的Context.create(program, monitor, log)先由FSRL.fromProgram(program)取得当前程序的文件系统定位符FSRL再经FSRLHelper.load(fsrl)经由 JNI 桥接把同一个文件交给 LIEF 重新解析得到一个lief.pe.Binary对象。这样分析器拿到的结构字段值全部来自 LIEF 的独立解析而非 Ghidra 的猜测。读取 Load Configuration调用ctx.getBin().getLoadConfiguration()返回OptionalLoadConfiguration。若该 PE 根本没有 Load Configuration 数据目录项则直接返回true不做任何改动。构造结构类型根据程序指针宽度决定后缀——ctx.is32bits()为32否则为64从而生成IMAGE_LOAD_CONFIG_DIRECTORY32/IMAGE_LOAD_CONFIG_DIRECTORY64结构内部名_IMAGE_LOAD_CONFIG_DIRECTORY32/64然后按 LIEF 解析出的字段存在性逐个追加成员。地址换算与落盘从Binary.getLoadConfigurationDir()取出数据目录的 RVA经Context.translateAddress()换算为 Ghidra 程序地址——换算规则是若目标值大于等于 LIEF 侧getImageBase()则减去镜像基址再叠加到 Ghidra 的program.getImageBase()上否则按普通偏移处理。最终ctx.defineData(address, loadConfigTy)用DataUtilities.createData在对应地址写入该结构类型并清除冲突的默认数据。所有生成的类型都归类在 Ghidra 的/LIEF/PE类别路径下由 PE 版 TypeBuilder.java 的getCategoryPath()决定基类ROOT /LIEFPE 扩展PE冲突时使用DataTypeConflictHandler.REPLACE_HANDLER以 LIEF 的版本替换已有类型。四、IMAGE_LOAD_CONFIG_DIRECTORY的完整字段布局4.1 固定头部字段分析器首先无条件添加下列固定字段名称与类型完全对照 LoadConfigurationAnalyzer.java字段类型说明SizeU32结构自身大小也是解析器判断结构版本的依据TimeDateStampU32时间戳MajorVersion/MinorVersionU16版本号GlobalFlagsClear/GlobalFlagsSetU32系统全局标志对应 Gflags.exeCriticalSectionDefaultTimeoutU32临界区默认超时DeCommitFreeBlockThreshold/DeCommitTotalFreeThresholdUPTR堆去提交阈值指针宽度LockPrefixTableADDRx86 专有的 LOCK 前缀替换表 VAMaximumAllocationSize/VirtualMemoryThresholdUPTR最大分配/堆段阈值ProcessAffinityMaskUPTR进程关联掩码仅 .exeProcessHeapFlagsU32进程堆标志CSDVersionU16Service Pack 版本DependentLoadFlagsU16依赖加载标志EditListADDR系统保留SecurityCookieADDR/GS安全 Cookie 指针这里的基础类型来自基类 TypeBuilder.javaU16/U32对应 Word/DWordUPTRgetUnsignedPointer()按程序指针宽度自动取 U32 或 U64ADDRgetAddrType()是宽度与指针一致的无目标类型指针RVA则使用IBO32DataTypeimage base offset32 位。4.2 按需追加的扩展字段固定头部之后分析器针对 LIEF 能解析出的每个可选字段逐个检测对应LoadConfiguration返回Optional的访问器字段存在才加入结构从而保证结构尺寸与真实二进制一致。这些字段及其类型映射如下依据分析器源码中的isPresent()分支字段类型语义SEHandlerTable指向 UPTR 的指针x86 的 SEH 处理器表SEHandlerCountUPTRSEH 表项数GuardCFCheckFunctionPointer/GuardCFDispatchFunctionPointerADDRCFG 检查/分派函数指针GuardCFFunctionTable指向 U32 的指针CFG 合法目标表GuardCFFunctionCountUPTRCFG 目标表项数GuardFlagsIMAGE_GUARD_FLAGS枚举CFG 相关标志见下节CodeIntegrityIMAGE_LOAD_CONFIG_CODE_INTEGRITY代码完整性信息GuardAddressTakenIatEntryTable/GuardAddressTakenIatEntryCountADDR / UPTR取地址 IAT 表GuardLongJumpTargetTable/GuardLongJumpTargetCountADDR / UPTRlongjmp 目标表DynamicValueRelocTableADDR动态重定位表DVRTCHPEMetadataPointer指向元数据结构的指针CHPEARM64EC/x86 模拟元数据GuardRFFailureRoutine/GuardRFFailureRoutineFunctionPointerADDRReturn Flow Guard 失败例程DynamicValueRelocTableOffsetRVA相对重定位表的偏移DynamicValueRelocTableSectionU16动态重定位表所在节索引Reserved2U16保留应为 0GuardRFVerifyStackPointerFunctionPointerADDR校验栈指针的函数HotPatchTableOffsetRVA热补丁表偏移Reserved3U32保留EnclaveConfigurationPointerADDREnclave 配置VolatileMetadataPointerADDR易失性元数据GuardEHContinuationTable/GuardEHContinuationCountADDR / UPTREH 延续EH Continuation表GuardXFGCheckFunctionPointer/GuardXFGDispatchFunctionPointer/GuardXFGTableDispatchFunctionPointerADDRXFG 相关指针CastGuardOsDeterminedFailureModeADDRCastGuard 失败模式GuardMemcpyFunctionPointerADDRGuarded memcpyUmaFunctionPointersADDRUMAUnified Memory Access函数指针字段的语义说明可对照 C 侧LoadConfiguration各访问器的注释如se_handler_table()注明“仅 x86 可用”、lock_prefix_table()注明“仅 x86”等在 include/LIEF/PE/LoadConfigurations/LoadConfiguration.hpp 中可以找到每个字段的权威解释。正是这种“存在才加入”的策略使分析器既适配较老、较小的 Load Configuration也能完整覆盖 Windows 11 上体积更大的新版结构。五、IMAGE_GUARD_FLAGSGuardFlags 枚举类型GuardFlags字段没有简单地标注为 U32而是被注册为一个独立的枚举类型IMAGE_GUARD_FLAGSPE 版TypeBuilder.getType(IMAGE_GUARD_FLAGS)首次被引用时创建。该枚举在 plugins/ghidra/src/main/java/lief/ghidra/plugins/analyzers/pe/TypeBuilder.java 中定义包含以下取值枚举成员值含义IMAGE_GUARD_CF_INSTRUMENTED0x00000100使用系统提供的支持执行 CFI 检查IMAGE_GUARD_CFW_INSTRUMENTED0x00000200执行控制流与写完整性检查IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT0x00000400含合法的 CFG 目标元数据IMAGE_GUARD_SECURITY_COOKIE_UNUSED0x00000800未使用/GS安全 CookieIMAGE_GUARD_PROTECT_DELAYLOAD_IAT0x00001000支持只读延迟加载 IATIMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION0x00002000延迟导入表位于独立.didat节IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT0x00004000含被抑制的导出信息同时暗示存在取地址 IAT 表IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION0x00008000启用导出抑制IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT0x00010000含 longjmp 目标信息IMAGE_GUARD_RF_INSTRUMENTED0x00020000含返回流RF插桩与元数据IMAGE_GUARD_RF_ENABLE0x00040000请求系统启用返回流保护IMAGE_GUARD_RETPOLINE_PRESENT0x00100000以 retpoline 支持构建IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT0x00400000含 EH 延续目标信息IMAGE_GUARD_XFG_ENABLED0x00800000以 XFG 构建已弃用IMAGE_GUARD_CASTGUARD_PRESENT0x01000000存在 CastGuard 插桩IMAGE_GUARD_MEMCPY_PRESENT0x02000000存在 Guarded Memcpy 插桩分析器还在此基础上生成IMAGE_LOAD_CONFIG_CODE_INTEGRITY结构FlagsU16CI 信息是否可用、CatalogU160xFFFF 表示不可用、CatalogOffsetU320xFFFF 表示不可用、ReservedU32用于标注CodeIntegrity字段。运行后Listing 中GuardFlags字段会直接显示为IMAGE_GUARD_CF_INSTRUMENTED这样的具名枚举值而非裸整数。六、ARM64EC 元数据IMAGE_ARM64EC_METADATA家族6.1 CHPEMetadataPointer 的版本化解析当 LIEF 检测到CHPEMetadataPointer存在时分析器会进一步读取getCHPEMetadata()。由于 CHPE 元数据存在多种变体分析器按实际类型与版本分别处理ARM64ECCHPEMetadataARM64依据getVersion()选择类型——版本 2 使用IMAGE_ARM64EC_METADATA_V2其余使用IMAGE_ARM64EC_METADATA并把结构落盘到指针指向的地址随后将CHPEMetadataPointer字段类型化为指向该结构的指针x86 模拟CHPEMetadataX86依据版本选择IMAGE_CHPE_METADATA_X86、_V2或_V3无法识别退化为普通ADDR指针保证结构仍可正确定位。Java 端CHPEMetadata/CHPEMetadataARM64/CHPEMetadataX86的字段访问器定义在 plugins/ghidra/src/main/java/lief/pe/CHPEMetadata.java 与 plugins/ghidra/src/main/java/lief/pe/CHPEMetadataARM64.java 中C 侧对应模型位于 include/LIEF/PE/LoadConfigurations/CHPEMetadata.hpp、MetadataARM64.hpp 与 MetadataX86.hpp。6.2IMAGE_ARM64EC_METADATA结构定义基础版本IMAGE_ARM64EC_METADATA在 PE 版TypeBuilder中按以下成员构建VersionULONG、CodeMapRVA、CodeMapCountULONG、CodeRangesToEntryPointsRVA、RedirectionMetadataRVA、__os_arm64x_dispatch_call_no_redirect、__os_arm64x_dispatch_ret、__os_arm64x_dispatch_call、__os_arm64x_dispatch_icall、__os_arm64x_dispatch_icall_cfg均为 RVA、AlternateEntryPoint、AuxiliaryIATRVA、CodeRangesToEntryPointsCount、RedirectionMetadataCountULONG、GetX64InformationFunctionPointer、SetX64InformationFunctionPointer、ExtraRFETableRVA、ExtraRFETableSizeULONG、__os_arm64x_dispatch_fptr、AuxiliaryIATCopyRVA。IMAGE_ARM64EC_METADATA_V2则将 V1 结构整体作为首成员V1再追加AuxDelayloadIAT、AuxDelayloadIATCopyRVA与ReservedBitFieldULONG。X86 侧同理IMAGE_CHPE_METADATA_X86记录Version与CHPECodeAddressRangeOffset/Count、各WowA64Dispatch*FunctionPointer_V2追加CompilerIATPointer_V3再追加WowA64RdtscFunctionPointer。这组结构从源码结构上看遵循了“以版本为前缀、低版本结构嵌套进高版本”的递增式布局便于向前兼容。6.3 对 ARM64EC 辅助数组的深度标注除了结构本身process()方法还会把 ARM64EC 元数据指向的三个关键数组也一并落盘见 LoadConfigurationAnalyzer.javaCodeMap按getCodeMapCount()生成IMAGE_ARM64EC_METADATA_CODE_RANGE数组。该元素是位域结构Type2 bitIMAGE_ARM64EC_METADATA_CODE_RANGE_TYPE枚举取值ARM640、ARM64EC1、AMD642RVA30 bit构成一个 packed 的 32 位成员随后是LengthU32CodeRangesToEntrypoints按计数生成IMAGE_ARM64EC_CODE_RANGE_ENTRY_POINT数组每项为StartRva、EndRva、EntryPoint三个 RVARedirectionMetadata生成IMAGE_ARM64EC_METADATA_REDIRECTION数组每项为From、To两个 RVA描述 x64 调用被重定向到 ARM64EC 桩的映射。通过这些数组的定义Ghidra 能在分析 ARM64EC 二进制时自动把“代码范围”“入口点”“重定向”数据识别为具名结构数组而不是零散的数字。七、运行与验证7.1 安装前提分析器属于 LIEF Ghidra 插件包。安装分为两步详见 Ghidra 插件索引文档将 LIEF 插件扩展包按 Ghidra 官方扩展安装流程放入 Ghidra 的Extensions目录将 LIEF 共享库LIEF.dll、libLIEF.dylib或libLIEF.so放置到同一目录例如 Linux 上为~/.config/ghidra/ghidra_版本_PUBLIC/Extensions/libLIEF.so。装好后可在 CodeBrowser注意是 CodeBrowser 而非 Project 窗口的File Configure中看到 LIEF 配置项。若分析器未生效通常先排查共享库是否安装正确错误提示会给出期望的库路径少数依赖额外能力的功能还可能需要 LIEF 的 extended 版本共享库。7.2 GUI 与 headless 两种触发方式GUI正常导入 PE 文件到 Ghidra 后分析器默认启用setDefaultEnablement(true)随自动分析运行也可在分析选项里手动触发一次性分析。Headless仓库提供了用于验证分析器效果的脚本 plugins/ghidra/ghidra_scripts/RunAnalyzers.java。它以AsciiExporter导出分析前的 Listing依次调用LoadConfigurationAnalyzer、ExceptionsAnalyzer、RelocationsAnalyzer的added()再导出分析后的 Listing用于对比结构标注前后的差异。这也演示了分析器 API 的编程式调用方式构造分析器 →canAnalyze(program)判断 →added(program, addressSet, monitor, log)执行。7.3 效果确认运行后打开数据目录指向的地址应能看到IMAGE_LOAD_CONFIG_DIRECTORY64或32结构整体展开字段名与官方结构一一对应、指针字段自动解析为指向真实函数/数据的引用例如GuardCFCheckFunctionPointer会显示为PTR__guard_check_icall_...、GuardFlags以具名枚举呈现。所有结构位于/LIEF/PE类型类别下可被后续分析或导出直接复用。八、原理小结一条从文件字节到 Ghidra 类型的完整链路从底层看该分析器打通了“文件 → LIEF 解析 → Ghidra 类型系统”的整条链路文件级LIEF 的 C 解析器按IMAGE_LOAD_CONFIG_DIRECTORY的Size字段判断结构版本并逐一读取字段模型见 include/LIEF/PE/LoadConfigurations/LoadConfiguration.hpp绑定层Ghidra 插件通过 JNI 将 LIEF 对象映射为lief.pe.LoadConfiguration、lief.pe.CHPEMetadata等 Java 对象访问器与 C 模型一一对应分析器层LoadConfigurationAnalyzer按字段存在性动态拼装StructureDataType通过TypeBuilder复用/创建基础类型、枚举与嵌套结构落盘层Context.translateAddress()完成 LIEF VA/RVA 到 Ghidra 地址的换算defineData()写入结构冲突数据按REPLACE_HANDLER策略被替换。正是这条链路让 Ghidra 中对 PE Load Configuration 的逆向从“手工对照 SDK 头文件”升级为“结构即见即所得”同时也为后续基于 Ghidra 的自动化分析如 CFG 目标提取、ARM64EC 混合代码梳理提供了可靠的数据基础。使用该分析器时需要注意它依赖 LIEF 共享库的正确安装且分析对象必须是 Ghidra 可识别的 PE 格式生成类型固定存放于/LIEF/PE类别下如需迁移可在此处直接复制到其他类别或导出为 C 头文件。赞分享逆向工程开发工具【免费下载链接】LIEFLIEF - Library to Instrument Executable Formats (C, Python, Rust)项目地址https://gitcode.com/gh_mirrors/li/LIEF点击查看免费下载相关推荐LIEF 0.17.0 PE 模块重大重构解析从解析器重构到 ARM64EC/ARM64X 支持LIEF 0.17.0 PE 模块重大重构解析从解析器重构到 ARM64EC/ARM64X 支持 导读 LIEF 0.17.0 对 PEPortable E逆向工程开发工具探索PE文件结构与逆向分析利器 - PEDA探索PE文件结构与逆向分析利器 PEDA 项目简介 PEDAPowerful exploitation development and analysis是一网络安全逆向工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考