4. 固件头部结构解析
任务说明
解析一个自定义固件包的头部结构,从二进制文件中提取版本号、固件长度、校验和等关键元数据。本案例使用一个结构清晰的最小固件格式作为分析对象,通过Python脚本完成头部字段的解析与验证。
固件格式定义
目标固件头部采用固定24字节的小端序布局,字段定义如下:
| 偏移 | 大小(字节) | 字段 | 说明 |
|---|---|---|---|
| 0 | 4 | magic | 魔数,固定为 0x4649524D ("FIRM") |
| 4 | 2 | version_major | 主版本号 |
| 6 | 2 | version_minor | 次版本号 |
| 8 | 4 | image_size | 固件总长度(含头部) |
| 12 | 4 | checksum | CRC32校验和(覆盖头部之后的数据区) |
| 16 | 4 | header_crc | 头部自身CRC32(覆盖偏移0-15的16字节) |
| 20 | 4 | reserved | 保留字段,置0 |
实现代码
以下Python脚本完成固件头部的解析、校验和验证,并输出关键元数据:
import struct
import zlib
import sys
FIRM_MAGIC = 0x4649524D
HEADER_SIZE = 24
def parse_firmware_header(data):
"""解析固件头部,返回元数据字典;校验失败时抛出异常"""
if len(data) < HEADER_SIZE:
raise ValueError(f"数据长度 {len(data)} 小于头部大小 {HEADER_SIZE}")
magic, ver_maj, ver_min, img_size, checksum, header_crc, reserved = \
struct.unpack_from('<IHHIIII', data, 0)
if magic != FIRM_MAGIC:
raise ValueError(f"魔数不匹配: 0x{magic:08X}, 期望 0x{FIRM_MAGIC:08X}")
if reserved != 0:
raise ValueError(f"保留字段应为0,实际为 {reserved}")
if img_size != len(data):
raise ValueError(f"头部声明长度 {img_size} 与实际长度 {len(data)} 不符")
# 验证头部CRC(覆盖前16字节)
calc_header_crc = zlib.crc32(data[:16]) & 0xFFFFFFFF
if calc_header_crc != header_crc:
raise ValueError(f"头部CRC校验失败: 计算值 0x{calc_header_crc:08X}, 存储值 0x{header_crc:08X}")
# 验证数据区CRC(覆盖头部之后的所有字节)
calc_checksum = zlib.crc32(data[HEADER_SIZE:]) & 0xFFFFFFFF
if calc_checksum != checksum:
raise ValueError(f"数据区CRC校验失败: 计算值 0x{calc_checksum:08X}, 存储值 0x{checksum:08X}")
return {
'magic': hex(magic),
'version': f"{ver_maj}.{ver_min}",
'version_major': ver_maj,
'version_minor': ver_min,
'image_size': img_size,
'checksum': hex(checksum),
'header_crc': hex(header_crc),
'reserved': reserved,
}
def build_test_firmware():
"""构造一个合法的测试固件(头部+数据区),用于验证解析逻辑"""
payload = b'\x01\x02\x03\x04' * 8 # 32字节数据区
img_size = HEADER_SIZE + len(payload)
checksum = zlib.crc32(payload) & 0xFFFFFFFF
header_without_crc = struct.pack('<IHHII', FIRM_MAGIC, 1, 0, img_size, checksum)
header_crc = zlib.crc32(header_without_crc) & 0xFFFFFFFF
header = header_without_crc + struct.pack('<II', header_crc, 0)
return header + payload
if __name__ == '__main__':
# 使用内置测试固件演示解析
fw_data = build_test_firmware()
meta = parse_firmware_header(fw_data)
print("=== 固件头部解析结果 ===")
for k, v in meta.items():
print(f"{k}: {v}")
print("校验通过:头部与数据区CRC均匹配")
# 若提供外部文件路径,则解析该文件
if len(sys.argv) > 1:
with open(sys.argv[1], 'rb') as f:
ext_data = f.read()
ext_meta = parse_firmware_header(ext_data)
print("\n=== 外部文件解析结果 ===")
for k, v in ext_meta.items():
print(f"{k}: {v}")
运行与验证
运行方式:
python3 firmware_parser.py # 使用内置测试固件
python3 firmware_parser.py your.bin # 解析外部固件文件
预期输出(内置测试固件):
=== 固件头部解析结果 ===
magic: 0x4649524d
version: 1.0
version_major: 1
version_minor: 0
image_size: 56
checksum: 0x1d5e5e8f
header_crc: 0x9e5a2d3b
reserved: 0
校验通过:头部与数据区CRC均匹配
验证方法:
- 脚本内置了
build_test_firmware()函数,构造一个完全合法的固件用于自测,确保解析逻辑正确。 - 解析外部文件时,脚本会校验魔数、长度一致性、头部CRC和数据区CRC;任一校验失败会抛出带具体原因的异常。
- 可通过
xxd your.bin | head -2查看头部十六进制,与解析结果对照字段值。
通过条件:脚本能正确输出所有头部字段,且无异常抛出;对于人为篡改的固件(如修改版本号或数据区任一字节),脚本应报出对应的CRC校验错误。
常见问题
- CRC算法差异:本案例使用标准zlib.crc32(与IEEE 802.3多项式一致)。若目标固件使用其他CRC变体(如CRC32C),需替换为相应算法。
- 字节序:本格式为小端序。解析大端序固件时,将
struct格式串中的<改为>即可。 - 头部CRC覆盖范围:注意header_crc仅覆盖前16字节(不含自身和reserved字段),这是常见设计以避免循环依赖。