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)

运行与验证

  1. 保存上述代码为 fw_parser.py,并确保固件样本 firmware.bin 位于同一目录。
  2. 运行解析器:python3 fw_parser.py firmware.bin
  3. 预期输出(确定性结果):
[+] 检测到文件系统: squashfs (偏移量: 0x10)
[+] 提取成功,输出目录: extracted
  1. 验证提取结果:
# 检查提取出的文件结构
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需支持相应压缩类型。