1. 解析固件卷头

任务目标

编写一个独立可运行的Python脚本,读取UEFI固件卷(Firmware Volume)二进制文件,解析其头部结构,提取并输出文件系统GUID、FV大小、头部长度等关键字段。

固件卷头结构

UEFI固件卷头(EFI_FIRMWARE_VOLUME_HEADER)定义在PI规范中,关键字段如下:

偏移大小字段说明
0x008ZeroVector零向量,用于签名验证
0x084FileSystemGuid文件系统GUID(EFI_FIRMWARE_FILE_SYSTEM2_GUID = 8C8CE578-8A3D-4F1C-9935-896185C32DD3)
0x0C8FvLength固件卷总大小(字节)
0x144Signature头签名,应为 '_FVH'(0x4856465F)
0x184Attributes固件卷属性位掩码
0x1C2HeaderLength头部长度(字节)
0x1E2Checksum整个固件卷的校验和
0x202ExtHeaderOffset扩展头偏移(0表示无)
0x221Reserved保留
0x231Revision头版本修订号

完整实现

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()

运行方式

  1. 将上述代码保存为 parse_fv.py(需要Python 3.6+,无需第三方库)
  2. 准备一个固件卷二进制文件(可从BIOS镜像中提取,或使用UEFI开发工具生成)
  3. 执行: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