1. 解析固件卷头
任务目标
编写一个独立可运行的Python脚本,读取UEFI固件卷(Firmware Volume)二进制文件,解析其头部结构,提取并输出文件系统GUID、FV大小、头部长度等关键字段。
固件卷头结构
UEFI固件卷头(EFI_FIRMWARE_VOLUME_HEADER)定义在PI规范中,关键字段如下:
| 偏移 | 大小 | 字段 | 说明 |
|---|---|---|---|
| 0x00 | 8 | ZeroVector | 零向量,用于签名验证 |
| 0x08 | 4 | FileSystemGuid | 文件系统GUID(EFI_FIRMWARE_FILE_SYSTEM2_GUID = 8C8CE578-8A3D-4F1C-9935-896185C32DD3) |
| 0x0C | 8 | FvLength | 固件卷总大小(字节) |
| 0x14 | 4 | Signature | 头签名,应为 '_FVH'(0x4856465F) |
| 0x18 | 4 | Attributes | 固件卷属性位掩码 |
| 0x1C | 2 | HeaderLength | 头部长度(字节) |
| 0x1E | 2 | Checksum | 整个固件卷的校验和 |
| 0x20 | 2 | ExtHeaderOffset | 扩展头偏移(0表示无) |
| 0x22 | 1 | Reserved | 保留 |
| 0x23 | 1 | Revision | 头版本修订号 |
完整实现
import struct
import uuid
import sys
# EFI_FIRMWARE_FILE_SYSTEM2_GUID
FV_FS2_GUID = uuid.UUID('8c8ce578-8a3d-4f1c-9935-896185c32dd3')
def parse_fv_header(fv_data: bytes) -> dict:
"""解析固件卷头,返回关键字段字典"""
if len(fv_data) < 0x28:
raise ValueError(f'数据不足:需要至少0x28字节,实际{len(fv_data)}字节')
# 偏移0x08:FileSystemGuid(16字节)
fs_guid = uuid.UUID(bytes_le=fv_data[0x08:0x18])
# 偏移0x0C:FvLength(8字节,小端)
fv_length = struct.unpack_from('<Q', fv_data, 0x0C)[0]
# 偏移0x14:Signature(4字节)
signature = fv_data[0x14:0x18]
sig_str = signature.decode('ascii', errors='replace')
# 偏移0x18:Attributes(4字节)
attributes = struct.unpack_from('<I', fv_data, 0x18)[0]
# 偏移0x1C:HeaderLength(2字节)
header_length = struct.unpack_from('<H', fv_data, 0x1C)[0]
# 偏移0x1E:Checksum(2字节)
checksum = struct.unpack_from('<H', fv_data, 0x1E)[0]
# 偏移0x20:ExtHeaderOffset(2字节)
ext_header_offset = struct.unpack_from('<H', fv_data, 0x20)[0]
# 偏移0x23:Revision(1字节)
revision = fv_data[0x23]
return {
'file_system_guid': str(fs_guid),
'is_standard_fs2': fs_guid == FV_FS2_GUID,
'fv_length': fv_length,
'signature': sig_str,
'attributes': hex(attributes),
'header_length': header_length,
'checksum': hex(checksum),
'ext_header_offset': ext_header_offset,
'revision': revision,
}
def main():
if len(sys.argv) != 2:
print('用法: python parse_fv.py <固件卷文件路径>')
sys.exit(1)
filepath = sys.argv[1]
with open(filepath, 'rb') as f:
fv_data = f.read()
try:
info = parse_fv_header(fv_data)
except ValueError as e:
print(f'解析失败: {e}')
sys.exit(1)
# 验证签名
if info['signature'] != '_FVH':
print(f'错误: 签名不是"_FVH",实际为"{info["signature"]}",文件可能不是固件卷')
sys.exit(1)
print('=== 固件卷头解析结果 ===')
print(f'文件系统GUID : {info["file_system_guid"]}')
print(f'标准FS2 GUID : {"是" if info["is_standard_fs2"] else "否"}')
print(f'FV总大小 : {info["fv_length"]} 字节 (0x{info["fv_length"]:X})')
print(f'签名 : {info["signature"]}')
print(f'属性 : {info["attributes"]}')
print(f'头部长度 : {info["header_length"]} 字节')
print(f'校验和 : {info["checksum"]}')
print(f'扩展头偏移 : {info["ext_header_offset"]}')
print(f'修订版本 : {info["revision"]}')
# 基本一致性检查
if info['header_length'] < 0x28:
print(f'警告: 头部长度({info["header_length"]})小于最小头长度(0x28)')
if info['fv_length'] < info['header_length']:
print(f'警告: FV总大小({info["fv_length"]})小于头部长度({info["header_length"]})')
if __name__ == '__main__':
main()
运行方式
- 将上述代码保存为
parse_fv.py(需要Python 3.6+,无需第三方库) - 准备一个固件卷二进制文件(可从BIOS镜像中提取,或使用UEFI开发工具生成)
- 执行:
python parse_fv.py <固件卷文件>
验证方法
通过以下条件判断解析是否正确:
- 程序输出签名必须为
_FVH,否则文件不是标准固件卷 - 文件系统GUID若为标准FS2 GUID(
8c8ce578-8a3d-4f1c-9935-896185c32dd3),输出会标记"是" - 头部长度应≥0x28(40字节),且≤FV总大小
- 可用十六进制编辑器对照检查:偏移0x14处应为ASCII字符"_FVH",偏移0x0C处的8字节小端整数应等于输出中的FV总大小
预期输出示例
对于包含标准固件卷的文件,输出格式如下(具体值取决于输入文件):
=== 固件卷头解析结果 ===
文件系统GUID : 8c8ce578-8a3d-4f1c-9935-896185c32dd3
标准FS2 GUID : 是
FV总大小 : 1048576 字节 (0x100000)
签名 : _FVH
属性 : 0x4
头部长度 : 0x48
校验和 : 0x0
扩展头偏移 : 0x48
修订版本 : 2