uefi pe32+ 文件头识别需依次验证:mz签名(0x4d5a)、pe签名(0x00004550)、可选头魔数0x20b、machine字段(x64为0x8664等);节表起始位置= dos头偏移+136,节数取自coff头numberofsections;eat目录位于数据目录第15项(索引14),签名须为"$eat"(0x54414524)。

UEFI PE32+ 文件头怎么识别
不是所有以 .efi 结尾的文件都是合法 UEFI 映像;C# 里直接用 File.ReadAllBytes 读出来,第一件事是确认它是否真有 UEFI 兼容的 PE 结构。关键看 DOS 头后的 PE 签名和可选头魔数。
常见错误现象:System.BadImageFormatException 或解析出错后字段全为 0 —— 很可能跳过了 DOS stub、没校验 "PE\0\0" 签名,或误把 32 位 PE(0x10b)当 PE32+(0x20b)处理。
- 先读前 64 字节,检查
bytes[0] == 0x4d && bytes[1] == 0x5a(即"MZ") - 再从
bytes[60]取 DWORD 偏移,读该位置的 4 字节是否等于0x00004550(即"PE\0\0") - 接着跳过 COFF 头(20 字节),读可选头前 2 字节:必须是
0x0b02(小端,对应0x20b)才是 PE32+ - UEFI 规范要求 Machine 字段为
0x8664(x64)或0x014c(x86),ARM64 是0xaa64;不匹配就不是目标平台固件
如何安全提取 EFI_IMAGE_NT_HEADERS64 和节表
C# 没有内置 UEFI PE 解析器,靠手动偏移计算容易越界或对齐错误。Windows SDK 的 IMAGE_NT_HEADERS64 结构在 .NET 中不能直接 Marshal.PtrToStructure,因为 UEFI 映像常含非标准节名、空节或重叠虚拟地址(VMA)。
使用场景:你想读取 `.text` 节原始字节做反汇编,或找 `.reloc` 节验证是否支持基址重定位 —— 这些都依赖正确解析节表起始位置和数量。
- 可选头大小固定为 112 字节(PE32+),节表紧跟其后,起始偏移 = DOS 头偏移 + 4 + 20 + 112
- 节表项大小是 40 字节,但实际节数要从 COFF 头的
NumberOfSections字段(偏移 2 + 2 字节处)读,不能硬写循环 10 次 - 每个节的
Name是 8 字节 ANSI 字符串,需用Encoding.ASCII.GetString(bytes, offset, 8).TrimEnd('\0'),别用UTF8否则乱码 - 注意
VirtualAddress和SizeOfRawData可能为 0 —— 表示该节只存在于内存中(如 .bss),磁盘上无对应数据
为什么用 BinaryReader 比 Span<byte></byte> 更稳妥
UEFI 映像里大量字段是 little-endian,且存在“按文件对齐(FileAlignment)”和“按内存对齐(SectionAlignment)”两套规则。用 Span<byte></byte> 手动 BitConverter.ToInt32(data.Slice(i, 4)) 容易忽略字节序翻转,或因未校验边界导致 IndexOutOfRangeException。
性能影响不大:单个 EFI 文件通常几 KB 到几百 KB,BinaryReader 的封装开销可忽略;但能避免 90% 的偏移计算失误。
- 构造时用
new BinaryReader(new MemoryStream(bytes), Encoding.ASCII, leaveOpen: true) - 读 DWORD 用
reader.ReadUInt32()(自动 little-endian),别用ToInt32 - 读字符串前先
reader.BaseStream.Position记录起点,读完 8 字节后手动Seek回去,避免破坏后续读取流位置 - 遇到
SizeOfOptionalHeader == 0或节表超出文件长度时,立即返回错误,不要尝试“尽力而为”解析
EFI 特有结构:如何定位和验证 IMAGE_DATA_DIRECTORY 中的 IMAGE_DIRECTORY_ENTRY_EAT
UEFI 驱动依赖导出表(Export Address Table)暴露入口点(如 UefiMain),但它不在标准 PE 的 IMAGE_DIRECTORY_ENTRY_EXPORT(索引 0),而是在 UEFI 扩展目录项 IMAGE_DIRECTORY_ENTRY_EAT(索引 14)。漏掉这点,就找不到驱动主函数地址。
容易踩的坑:直接查 Windows 文档里的目录索引表,会发现没有 14 这一项 —— 这是 EDK2 自定义扩展,仅在 UEFI 映像中有效。
- PE32+ 可选头中,数据目录从偏移 120 开始,共 16 项,每项 8 字节(RVA + Size)
- 第 15 项(索引 14)即
IMAGE_DIRECTORY_ENTRY_EAT,若RVA != 0且Size >= 24,才认为存在有效 EAT - EAT 结构头 4 字节是签名
0x54414524("$EAT"),紧接着是版本号(通常为 1)、导出函数数、名称 RVA 数组起始 RVA - 别假设 EAT 一定在某个节里 —— 它可能被打包进 .data 或自定义节(如
".sdata"),需遍历节表匹配 RVA 范围
复杂点在于:不同 EDK2 版本生成的 EAT 格式略有差异,有些省略名称数组,有些把函数地址表放文件末尾。没看到 "$EAT" 签名,不等于没有导出 —— 也可能是精简版 UEFI 应用,靠硬编码入口偏移(AddressOfEntryPoint)启动。











