1. 固件文件系统识别
任务目标
给定一个未知的固件镜像文件,通过特征签名识别其文件系统类型(SquashFS、CramFS、JFFS2等),为后续提取做准备。本案例使用一个已知的OpenWrt固件片段作为输入,完成从镜像到文件系统类型判定的完整闭环。
环境依赖
需要binwalk(固件分析工具)和file命令。安装:apt install binwalk file。若binwalk的magic数据库过旧,可先执行binwalk --update更新签名库。
实现方案
识别文件系统类型有三种递进手段:特征字符串匹配(快速预判)、binwalk签名扫描(标准做法)、结构体解析(精确验证)。本案例提供完整脚本,对输入镜像依次执行三种检测并汇总结果。
关键完整代码
#!/usr/bin/env python3
import os
import re
import subprocess
import sys
import struct
MAGIC_PATTERNS = {
b"hsqs": "SquashFS (little-endian, v4)",
b"sqsh": "SquashFS (big-endian, v4)",
b"shsq": "SquashFS (big-endian, v3)",
b"sqsh"[::-1]: "SquashFS (little-endian, v3)",
b"\x45\x3d\xcd\x28": "CramFS (little-endian)",
b"\x28\xcd\x3d\x45": "CramFS (big-endian)",
b"\x19\x85\x20\x03": "JFFS2 (little-endian)",
b"\x85\x19\x03\x20": "JFFS2 (big-endian)",
}
def check_magic(data):
"""在文件头部和偏移0x1000处检查魔数"""
results = []
for offset in (0, 0x1000):
if offset + 4 > len(data):
continue
chunk = data[offset:offset+4]
for magic, name in MAGIC_PATTERNS.items():
if chunk == magic:
results.append((offset, name, "magic"))
return results
def check_binwalk(path):
"""调用binwalk扫描文件系统签名"""
try:
out = subprocess.check_output(
["binwalk", path], stderr=subprocess.DEVNULL, timeout=30
).decode(errors="replace")
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
return []
results = []
for line in out.splitlines():
if any(k in line for k in ("SquashFS", "CramFS", "JFFS2")):
parts = line.split()
if parts and parts[0].isdigit():
results.append((int(parts[0]), line.strip(), "binwalk"))
return results
def parse_squashfs_superblock(data, offset):
"""解析SquashFS v4超级块关键字段"""
if offset + 96 > len(data):
return None
magic, inode_cnt, mod_time, block_size, frag_cnt = struct.unpack(
"<IIIII", data[offset:offset+20]
)
comp_id = struct.unpack("<H", data[offset+20:offset+22])[0]
comp_map = {1: "gzip", 2: "lzma", 3: "lzo", 4: "xz", 5: "lz4", 6: "zstd"}
return {
"magic_hex": hex(magic),
"inode_count": inode_cnt,
"block_size": block_size,
"compression": comp_map.get(comp_id, f"unknown({comp_id})"),
}
def main():
if len(sys.argv) != 2:
print("用法: python3 fs_identify.py <固件镜像路径>")
sys.exit(1)
path = sys.argv[1]
if not os.path.isfile(path):
print(f"错误: 文件不存在 - {path}")
sys.exit(1)
with open(path, "rb") as f:
data = f.read()
print(f"=== 分析文件: {path} (大小: {len(data)} 字节) ===\n")
# 1. 魔数预检
magic_hits = check_magic(data)
if magic_hits:
print("[1] 魔数匹配:")
for off, name, _ in magic_hits:
print(f" 偏移 0x{off:08x}: {name}")
else:
print("[1] 魔数匹配: 未找到已知文件系统魔数")
# 2. binwalk扫描
bw_hits = check_binwalk(path)
print("\n[2] binwalk签名扫描:")
if bw_hits:
for off, desc, _ in bw_hits:
print(f" 偏移 {off}: {desc}")
else:
print(" 未发现文件系统签名")
# 3. 结构体验证(仅对SquashFS做精确解析)
print("\n[3] SquashFS超级块解析:")
parsed = False
for off, name, src in magic_hits + bw_hits:
if "SquashFS" in name and src == "magic":
info = parse_squashfs_superblock(data, off)
if info:
print(f" 偏移 0x{off:08x}: inode数={info['inode_count']}, "
f"块大小={info['block_size']}, 压缩={info['compression']}")
parsed = True
break
if not parsed:
print(" 未解析到有效的SquashFS超级块")
# 4. 汇总判定
print("\n=== 判定结果 ===")
all_hits = magic_hits + bw_hits
if not all_hits:
print("无法识别文件系统类型,可能不是常见的SquashFS/CramFS/JFFS2镜像。")
print("建议: 尝试 'binwalk -A' 或 'binwalk -E' 进一步分析。")
else:
# 取偏移最小的命中作为主类型
primary = min(all_hits, key=lambda x: x[0])
print(f"主文件系统类型: {primary[1]} (偏移 {primary[0]})")
if len(all_hits) > 1:
print(f"共发现 {len(all_hits)} 处签名,可能存在嵌套文件系统。")
if __name__ == "__main__":
main()
运行方式
# 准备测试镜像(若无现成固件,可下载OpenWrt官方固件或使用以下方式生成最小SquashFS测试镜像)
# 生成最小SquashFS测试镜像(需root权限):
# mkdir -p /tmp/rootfs && echo "test" > /tmp/rootfs/hello.txt
# mksquashfs /tmp/rootfs test.squashfs -comp gzip
# 运行识别脚本
python3 fs_identify.py test.squashfs
验证方法与通过条件
使用上述生成的test.squashfs作为输入,预期输出应包含:
- 魔数匹配:偏移
0x00000000处命中SquashFS (little-endian, v4)(对应hsqs魔数) - binwalk扫描:报告
SquashFS filesystem, little endian, version 4.0等描述 - 超级块解析:显示inode数≥1、块大小通常为131072(128KB)、压缩方式为gzip
- 判定结果:主类型为SquashFS
判定通过条件:脚本输出的主类型与实际文件系统一致,且超级块解析字段与mksquashfs默认参数吻合。若使用真实固件(如OpenWrt的*-squashfs.bin),同样应识别出SquashFS及其偏移位置。
常见问题
- binwalk无输出:尝试
binwalk --update更新签名库,或使用binwalk -A(opcode扫描)辅助判断。 - 魔数偏移非0:固件常带有bootloader头部,文件系统通常从偏移0x10000或更高地址开始,脚本已覆盖0和0x1000两个常见位置,实际使用时以binwalk扫描结果为准。
- JFFS2无固定魔数偏移:JFFS2的魔数
0x19852003出现在每个擦除块起始处,binwalk会报告多个命中,取第一个即可。