1. 固件镜像文件系统解析
任务目标
给定一个典型的IoT固件镜像文件,通过分析其头部特征识别文件系统类型(squashfs或cramfs),并提取其中的文件内容。本案例使用一个可复现的最小固件样本完成全流程验证。
环境与依赖
- 操作系统:Linux(推荐Ubuntu 22.04+)
- 工具:
binwalk(固件分析)、unsquashfs(squashfs提取)、cramfsck(cramfs提取) - 安装命令:
sudo apt-get install binwalk squashfs-tools cramfsprogs
固件样本准备
为独立复现,先构造一个包含squashfs文件系统的最小固件镜像。该镜像由一个简单的rootfs目录打包而成,内含两个测试文件,用于验证提取结果。
# 创建rootfs目录结构与测试文件
mkdir -p rootfs/etc rootfs/bin
echo "version=1.0" > rootfs/etc/version.conf
echo "#!/bin/sh\necho hello" > rootfs/bin/hello.sh
chmod +x rootfs/bin/hello.sh
# 使用mksquashfs生成squashfs镜像
mksquashfs rootfs firmware.squashfs -comp xz -b 131072
# 在镜像前附加一个简单的固件头(模拟真实固件格式)
python3 -c "
import struct
with open('firmware.squashfs','rb') as f:
data = f.read()
header = b'FIRM' + struct.pack('>I', len(data)) + b'\x00'*8
with open('firmware.bin','wb') as f:
f.write(header + data)
print(f'固件大小: {len(header)+len(data)} 字节')
"
关键完整代码
以下Python脚本实现固件文件系统识别与提取的完整流程:
#!/usr/bin/env python3
"""IoT固件文件系统解析器 - 支持squashfs与cramfs识别及提取"""
import os
import struct
import subprocess
import sys
import tempfile
# 文件系统魔数签名
SIGNATURES = {
b'hsqs': 'squashfs', # little-endian squashfs
b'sqsh': 'squashfs', # big-endian squashfs
b'CramFS': 'cramfs', # cramfs
}
def detect_fs(data: bytes) -> tuple:
"""在固件数据中扫描文件系统签名,返回(类型, 偏移量)或(None, -1)"""
for sig, fs_type in SIGNATURES.items():
offset = data.find(sig)
if offset != -1:
return fs_type, offset
return None, -1
def extract_squashfs(data: bytes, offset: int, out_dir: str) -> bool:
"""从偏移量处提取squashfs镜像并解包"""
with tempfile.NamedTemporaryFile(suffix='.squashfs', delete=False) as tmp:
tmp.write(data[offset:])
tmp_path = tmp.name
try:
result = subprocess.run(
['unsquashfs', '-d', out_dir, tmp_path],
capture_output=True, text=True
)
return result.returncode == 0
finally:
os.unlink(tmp_path)
def extract_cramfs(data: bytes, offset: int, out_dir: str) -> bool:
"""从偏移量处提取cramfs镜像并解包"""
with tempfile.NamedTemporaryFile(suffix='.cramfs', delete=False) as tmp:
tmp.write(data[offset:])
tmp_path = tmp.name
try:
result = subprocess.run(
['cramfsck', '-x', out_dir, tmp_path],
capture_output=True, text=True
)
return result.returncode == 0
finally:
os.unlink(tmp_path)
def parse_firmware(fw_path: str, out_dir: str = 'extracted') -> bool:
"""主解析函数:识别文件系统类型并提取内容"""
with open(fw_path, 'rb') as f:
data = f.read()
fs_type, offset = detect_fs(data)
if fs_type is None:
print(f"[!] 未识别到已知文件系统签名: {fw_path}")
return False
print(f"[+] 检测到文件系统: {fs_type} (偏移量: 0x{offset:X})")
os.makedirs(out_dir, exist_ok=True)
if fs_type == 'squashfs':
ok = extract_squashfs(data, offset, out_dir)
elif fs_type == 'cramfs':
ok = extract_cramfs(data, offset, out_dir)
else:
ok = False
if ok:
print(f"[+] 提取成功,输出目录: {out_dir}")
return True
else:
print(f"[!] 提取失败,请检查工具是否安装")
return False
if __name__ == '__main__':
if len(sys.argv) != 2:
print(f"用法: {sys.argv[0]} <固件镜像路径>")
sys.exit(1)
success = parse_firmware(sys.argv[1])
sys.exit(0 if success else 1)
运行与验证
- 保存上述代码为
fw_parser.py,并确保固件样本firmware.bin位于同一目录。 - 运行解析器:
python3 fw_parser.py firmware.bin - 预期输出(确定性结果):
[+] 检测到文件系统: squashfs (偏移量: 0x10)
[+] 提取成功,输出目录: extracted
- 验证提取结果:
# 检查提取出的文件结构
find extracted -type f
# 预期输出:
# extracted/etc/version.conf
# extracted/bin/hello.sh
# 验证文件内容
cat extracted/etc/version.conf # 应输出: version=1.0
cat extracted/bin/hello.sh # 应输出: #!/bin/sh\necho hello
通过条件:解析器正确识别squashfs类型及偏移量(0x10),提取出的两个文件内容与原始rootfs完全一致,即判定案例通过。
常见问题
- 签名误报:某些固件可能在非文件系统区域包含魔数字节。若提取失败,可尝试用
binwalk firmware.bin查看所有扫描结果,手动确认正确偏移后修改代码中的扫描逻辑。 - 工具缺失:若提示
unsquashfs或cramfsck未找到,执行sudo apt-get install squashfs-tools cramfsprogs安装。 - 大端squashfs:部分路由器固件使用大端格式(签名
sqsh),本代码已覆盖,但unsquashfs需支持相应压缩类型。