---
name: re-fw-extract
description: >
  固件提取与解包：binwalk/unblob、magic 扫描、字节序。
  触发词：固件、binwalk、解包固件、firmware、IoT、MCU、8051、AVR、PIC、MSP430、Intel HEX、.hex
capabilities: [firmware-extraction]
---

# 固件提取与解包

## 何时使用 / 何时不用

- 用：拿到固件文件（.bin / .img / 升级包 / IoT 镜像）需要解开看内容
- 用：需要判断固件打包方式、架构与字节序
- 不用：已是文件系统镜像 / rootfs（直接走 [[re-fw-rootfs]]）
- 不用：需要运行固件观察行为（走 [[re-fw-emulate]]）
- 不用：需实物板子硬件提取（走 [[re-hardware-io]]）

## 工具准备

所有工具先验证再使用。解包与 magic 扫描是纯静态分析，可免沙箱（[[re-analyze/platform-tips]] 最高原则）；解出产物要运行时转 [[re-fw-emulate]]。

### binwalk —— 自动解包主力

binwalk 有两条并存的产品线，装前先分辨：**v3（Rust 重写）是 upstream 当前主线**，**v2（原 Python 实现）属 legacy**，两者的参数集合与模块支持不同。

- v3（当前主线）：按官方 README 的渠道安装——Docker 镜像 / `cargo install binwalk` / 源码构建。upstream 已不再把 pip 当作安装入口
- v2（legacy）：PyPI 的 `binwalk` 包停在 2.1.0（2015-01），各发行版仓库包同属 2.x 线（Debian trixie 2.4.3、bookworm 2.3.4）
  - Linux: `apt install binwalk`（Debian/Ubuntu）/ `dnf install binwalk` / `pacman -S binwalk`
  - pip: `pip install binwalk`
  - macOS: `brew install binwalk`
  - Windows/WSL: Windows 本机无官方包，用 WSL 内 Linux 版
- 验证: `binwalk --version`——**使用前先确认 major**。本技能的命令示例（`-e` / `-M` / `-E`）在 v2 与 v3 均保留；需要 v3 的其它参数时按实际版本查 `binwalk --help`

### unblob —— 更准的自动解包（推荐主力）

- 跨平台: `pip install unblob`（依赖较多，建议 venv: `python3 -m venv venv && venv/bin/pip install unblob`）
- 各发行版基本无官方包，用 pip
- 验证: `unblob --version`

### dd —— 按偏移切分

- Linux/macOS: coreutils 自带（macOS 自带 /usr/bin/dd）
- Windows/WSL: WSL 内 Linux 版；Windows 本机用 Git Bash 自带 dd 或 WSL
- 验证: `dd --version`

### hexdump —— 十六进制查看与手工 magic 扫描

- Linux: `apt install bsdmainutils`（Debian/Ubuntu）/ `dnf install util-linux`（Fedora/RHEL，含 hexdump）
- macOS: 自带 /usr/bin/hexdump
- Windows/WSL: WSL 内 Linux 版
- 验证: `hexdump -C /dev/null`（无报错即可用；macOS 版无 `--version`）

### sasquatch —— 老式/厂商魔改 squashfs 解包

- Linux: 源码编译（`git clone https://github.com/devttys0/sasquatch`，需 `apt install zlib1g-dev liblzma-dev build-essential` 后 `make`）
- macOS: 同上源码编译（需 Xcode Command Line Tools）
- Windows/WSL: WSL 内编译
- 验证: `sasquatch -h` 输出用法（编译产物在仓库子目录，需加入 PATH）

## 操作步骤

按顺序执行，每步记下结果。

1. **自动解包（unblob 优先，binwalk 兜底）**：
   ```sh
   unblob firmware.bin                      # 更准，自动识别 50+ 容器格式并递归解包
   # 或 binwalk：
   binwalk firmware.bin                     # 先列出签名与偏移
   binwalk -Me firmware.bin                 # -M 递归 -e 提取
   ```
   产物：`firmware.bin/`（unblob）或 `_firmware.bin.extracted/`（binwalk）目录。解出的文件系统转 [[re-fw-rootfs]]，ELF 转 [[re-binary-core]]。

2. **magic 手工扫描（自动解包不全时）**：
   ```sh
   hexdump -C firmware.bin | head -40
   # 或按 binwalk 报告的偏移核对：
   dd if=firmware.bin bs=1 skip=<偏移> count=16 | hexdump -C
   ```
   常见魔数：JPEG `FF D8 FF`、gzip `1F 8B`、squashfs `hsqs`、cramfs `45 3D CD 28`（小端，大端反序）、U-Boot `27 05 19 56`、jffs2 `85 19`、ELF `7F 45 4C 46`。识别出一个就按该格式处理。

3. **字节序判断**：
   ```sh
   file firmware.bin                        # 输出含 LSB/MSB 提示
   readelf -h <解出的ELF>                    # Machine 字段 + Data 字段（大小端）
   strings firmware.bin | head               # 可读串确认字节序
   ```
   大端 ARM/MIPS 固件常见：大端时魔数与字符串按大端编码（如 `hsqs` 反序出现）；确认后整个流程按该字节序进行。

4. **嵌套容器逐层解**：
   - unblob / `binwalk -Me` 会自动递归，但嵌套（tar 里再 zip、自定义头包着 gzip）常中途断
   - 逐层手动：先解外层 → `file` 确认内层类型 → 用对应工具（tar/gzip 系统自带；squashfs 用 sasquatch；其他用 [[re-fw-rootfs]] 工具准备的 7z/unsquashfs）再解，直到出现文件系统或 ELF
   - **结束标记后的附加数据**：图片（PNG `IEND`、JPEG `FFD9`）等格式的结束标记之后常附加容器/压缩流（解析器读到结束标记即停，附加数据对正常查看不可见）——`file` 会把整文件报成图片。**定结束标记位置不能靠裸字节 first/last 搜索**（`IEND` 字样可出现在 tEXt chunk 载荷内、`FF D9` 可出现在 JPEG 的 APP/COM 段内，首个/末个命中都可能是假标记）：PNG 从 8 字节签名起按 `Length/Type/Data/CRC` 遍历到结构合法且长度为 0 的 `IEND`，JPEG 从 `SOI` 起按 marker/segment 解析、`SOS` 后按 `FF00` stuffing 与 `RSTn` 处理，取**语法位置成立**的 `EOI`；用解析出的真实结尾偏移去切附加数据（裸搜结果只作候选）
   - **zip 缺签名也能修复**：附加的 zip 可能缺本地文件头开头的 `PK\x03\x04`（4 字节被剥）——用字段自洽验证：补上签名后 version（常见 20/45）、mod date（年 1980+）、compressed/uncompressed size（与 EOCD/中央目录条目一致）全部合理，且 EOCD 在尾部完好 → 补 `PK\x03\x04` 前缀即完整可解（`unzip` 报 "missing 4 bytes" 或 zipfile `OSError: Invalid argument` 是典型征兆）
   - **套娃模式自动化**：同一手法重复出现（如每层都是"图片+尾部 zip 含下一层"）时写循环自动剥——提取尾部 → 修复 → 解压 → 定位下一层 → 重复，直到无附加数据；中间产物每层命名保留（可回滚）

5. **自动失败时手工切分（dd 按偏移）**：
   ```sh
   binwalk firmware.bin                     # 找内嵌文件偏移
   dd if=firmware.bin of=part1.bin bs=1 skip=<偏移1> count=<长度1>
   dd if=firmware.bin of=part2.bin bs=1 skip=<偏移2>
   file part*.bin                           # 每块验证
   ```
   厂商自定义头最常见：跳过头部 N 字节后才是标准格式（先 hexdump 目测头长度再切）。

## MCU 镜像分析（8051 / AVR / PIC / MSP430）

### 何时进入本节

- 前置：已走完步骤 1-5（自动解包、magic 扫描、字节序判断、嵌套解包、dd 切分），确认镜像里没有文件系统/ELF/可执行容器
- 判断条件（满足即可进入本节）：
  - 无文件系统：binwalk/unblob 零命中或只有少量误报，无 squashfs/cramfs/jffs2 等魔数
  - 单镜像：体积小（几十 KB 量级）、无分区表、无 bootloader 引导头、无多段结构
  - MCU 特征：`file` 报 data/unknown；strings 无操作系统与库特征串（无 "libc"、无 RTOS 内核名）；镜像开头低地址处是短跳转/向量数据而非可读头
- 例外分流：镜像中出现 RTOS 特征（调度器/任务创建字符串、TCB 形态）→ 定位复位向量与启动代码后转 [[re-rtos]]

### 镜像格式：Intel HEX 与二进制

- **Intel HEX（.hex）**：ASCII 文本，逐行记录 `:LLAAAATT<数据><校验和>`——LL 数据长度、AAAA 段内地址、TT 记录类型（00 数据 / 01 结束 / 02·04 扩展地址 / 03·05 起始地址）、末字节校验和
  - 地址可分段不连续：多段记录之间留空，整文件按 bin 反汇编必然错位
  - 先转内存映像：按记录把数据落位到实际地址（02/04 扩展地址记录决定地址高 16 位），再按 flash 起始基址导入反汇编器
- **二进制（.bin）**：原始内存映像，无地址信息，导入时手动给基址
- 程序内存布局（泛化，具体值因芯片而异）：
  - flash 起始：0x0000 或芯片映射基址（如 0x8000/0x10000 量级），以复位向量所在位置反推更可靠
  - 中断向量表：低地址固定区，复位向量位于表内固定偏移（见下节）

### 向量表定位

- 复位向量 → 启动代码：从复位向量位置取入口地址，沿此反汇编即为启动序列（禁中断、初始化堆栈/时钟/外设、跳 main）
- 复位向量位置各架构不同：8051 在 0x0000、AVR 在 0x0000、MSP430 在 0xFFFE（高地址端）、PIC 多为 0x0000（个别系列另有固定偏移）——按架构查位置，不猜
- 跑 RTOS 的固件：复位向量 → 启动代码 → 内核初始化/任务创建 → 调度器启动，衔接 [[re-rtos]] 按任务表拆分

### ISA 识别与反汇编

- 识别要点（结合指令字宽、寻址特征、字节序判断）：
  - **8051**：哈佛架构，程序/数据空间分离（MOVC 读程序区、MOVX 访问外部数据区）；SFR 特殊功能寄存器（0x80-0xFF 直接寻址）；0x20-0x2F 区可位寻址；累加器 A 与 DPTR 主导数据搬运
  - **AVR**：RISC，32 个通用寄存器（R0-R31）寄存器文件，指令大多单周期；哈佛架构
  - **PIC**：哈佛架构，寄存器分 bank、靠状态位切换（bank 切换）；指令字宽随系列不同（12/14/16 位），导入反汇编器时按对应宽度
  - **MSP430**：冯诺依曼（统一地址空间），16 位；中断向量表集中在高地址区（0xFFE0-0xFFFF）
  - 字节序：AVR/PIC/MSP430 为小端；8051 无原生端序指令、多字节数据惯例大端存放（Keil C51 默认大端），与其余三者相反——导入反汇编器时以实际数据确认（多字节常量、跳转目标地址的字节排列为判断依据）
- 反汇编：选对应架构的处理器模块导入（Ghidra 含 8051/AVR/PIC/MSP430 处理器支持，[[re-ghidra]]；其他反汇编器同样有对应处理器，工具可替换，方法为核心）
- 熔丝位/配置位（泛化概念）：烧录期配置项（各架构叫法不同：熔丝位/配置位/安全位），烧录时一次性写入，影响时钟源选择、看门狗、代码/读保护等。它不体现在镜像内容里，但决定芯片后续硬件行为——镜像读不到时要先怀疑它

### 本节常见坑与陷阱

- **Intel HEX 多段当 bin 直接反汇编**：现象——反汇编错位，地址与代码对不上，全是无效指令；原因——HEX 分段记录地址不连续，且忽略 02/04 扩展地址记录会把高地址段数据落到低地址；对策——先按记录类型把数据落到实际地址（扩展地址记录参与地址合成），生成连续内存视图，再按 flash 基址导入
- **向量表偏移猜错**：现象——从文件头直接反汇编得到一堆数据/无效指令，找不到入口；原因——各架构复位向量位置不同（8051/AVR 在 0x0000、MSP430 在 0xFFFE、PIC 多为 0x0000 或系列固定偏移），猜错就从数据开始反汇编；对策——先确认架构，再按该架构复位向量固定位置取入口，从复位向量跟启动代码
- **熔丝位锁定读保护，镜像不可得**：现象——芯片读保护开启，读取操作返回全 F/拒绝响应，拿不到完整镜像；原因——烧录期配置位开启了读保护（代码保护），正常读取接口被锁；对策——走替代路径：芯片自带 UART bootloader 交互（若未禁用）、逻辑分析仪采集固件升级/烧录流量（[[re-hardware-io]]）按协议还原；镜像不可读时不强求，改从行为侧分析（串口输出/协议交互）
- **数据 EEPROM 与程序 flash 混淆**：现象——反汇编出大量无效指令，或分析半天发现是数据；原因——镜像含数据 EEPROM/校准表/配置表映像（非易失数据区，独立于程序区），被当成代码；对策——用复位向量/向量表定位程序区起点，程序区之外的高熵/全 F/重复模式段先排除为数据区，再反汇编

## 跨域联合

- [[re-firmware]]：工作流第 2 步固定调用本技能
- 解出的文件系统 → [[re-fw-rootfs]]；解出的可运行程序 → [[re-fw-emulate]]
- 解出的 ELF → [[re-binary-core]]（[[re-format-elf]] / [[re-ghidra]]）；发现恶意样本 → [[re-malware]]
- 本技能被 [[re-analyze]] 的 triage「分析固件 / IoT 设备」路径调用（re-firmware → re-fw-extract）

## 常见坑与陷阱

- **自动解包失败 ≠ 没东西**：现象——binwalk 输出空白或解出 0 字节文件；原因——厂商自定义头/加密层使魔数不匹配，签名库漏判；对策——hexdump 手工扫自定义头（步骤 2），跳过头部偏移再用 dd 切分（步骤 5）
- **字节序错 → 全错**：现象——解出的文件/ELF/字符串全乱；原因——大端 ARM/MIPS 固件被当小端处理；对策——先 `file` + `readelf -h` 确认字节序（步骤 3），后续解包/分析全程保持一致
- **大端 MIPS 头被 binwalk 漏**：现象——binwalk 识别不出已知文件系统（如 squashfs）；原因——签名按小端特征匹配，大端魔数反序；对策——hexdump 手工找反序魔数，按偏移 dd，用 sasquatch 解
- **解包产物混入垃圾**：现象——解出的"文件"是随机数据，`file` 报 data；原因——签名误报或切分偏移错位；对策——每个产物 `file` + 看头 16 字节验证 magic，无效即重试偏移
- **扩展名与内容不符（伪装扩展名）**：现象——`.jpg` 报 PNG、`.so` 报 tar、`.c` 报 XZ；原因——作者故意用无关扩展名（隐写/套娃/免检场景常见）；对策——永远以 `file`/魔数为准，扩展名只当线索；`file` 输出带 "with extra data prepended" 等提示时直接照做
- **zip 报错但能列出文件**：现象——`unzip` 报 "missing 4 bytes"/"invalid zip with overlapped components"（zip bomb 误报）或 Python zipfile `OSError: [Errno 22] Invalid argument`；原因——本地文件头缺 `PK\x03\x04` 签名，或数据前有前缀垃圾；对策——先 `unzip -l` 看能否列出（能列出说明 EOCD/中央目录完好），补签名（步骤 4）或用 `UNZIP_DISABLE_ZIPBOMB_DETECTION=TRUE` 强制
- **ME 固件（Intel ME / CSME）从启动链入手**：现象——解出 ME 固件分区（FTPR/RBE/BUP）后不知从何下手，代码里一串 ROM_SVC_* 调用看不懂；原因——ME 是独立处理器固件，启动链与普通固件不同：ROM（PCH 内固化，不可提取）验签 FTPR → 解压 RBE（初始化 PCH/SPI、UTOK 校验、boot_cfg）→ kernel（ThreadX RTOS）→ BUP（硬件 bringup，读 boot_cfg 决定开不开 DCI）→ pm/vfs/heci/crypto 等 38 个 user processes；对策——先解 FPT（Flash Partition Table：ME 区域固定偏移处的分区路由表，记录各分区位置/大小/类型——ROM/RBE 无文件系统全靠查它定位分区，UEFIExtract 源码有解析参考），再按 RBE→kernel→BUP 顺序跟进；调试接口/硬件初始化逻辑在 BUP
- **UTOK 是 ME 调试解锁的 OEM 后门**：现象——想开 ME 调试接口（DCI/DFX）找不到开关；原因——Intel 为 OEM 留的解锁机制：SPI Flash 的 FPT 分区表中独立 UTOK 分区，写入数据即可解锁 DFX 调试接口（无需硬件熔丝）；对策——逆向 RBE 的 `rbe_utok_check` 定位校验逻辑：标志位 UTOK[0x298]==1 才启动调试能力（厂家默认 FF 不启动），且 DCI 开放受多层校验链控制（boot_mode==2、boot_cfg 解析、CT handler 门控、FPF Debug Auth 等），单改标志位不够；定位技巧：ME 运行 base 是 0x4000，字符串偏移按此换算
- **ME 固件漏洞利用受 ROM cookie 限制**：现象——ME 固件里找到缓冲区溢出（如 Intel-SA-00086：CT 文件 `num_records`（偏移 +0x06 的 uint16）无校验，record 数超 100 覆写栈 cookie/返回地址），ROP 却打不通；原因——ME 的 cookie 由 ROM（mask ROM 不可提取）在 SRAM 预填充进程上下文表生成，无法像 TXE 平台那样用 TLS 绕过；对策——先确认目标平台 cookie 来源（ROM 生成则溢出利用受限），完整逆向认证链路（UTOK→boot_cfg→CT handler 多层校验）后找非溢出路径
- **分块周期变换**：现象——单一全文件 XOR/旋转只在开头有效；原因——变换按块大小（常见 256/512/1024/2048/4096）重置；对策——先按常见 Flash/传输块大小检查变换是否按块重置周期，显式分块解码
- **块首自带掩码混淆**：现象——高熵固件被当强加密；原因——分块自带掩码混淆（如 `mask = block[0]`、`plain[i] = ROR8(packed[i], n) XOR mask`（mask 字节自身不参与变换，i 从 1 起））；对策——先用强 crib（向量表 SRAM 栈指针/Thumb Reset Vector）约束恢复首块，再验证模型
- **众数 crib 不可靠**：现象——文本密集块用众数字节做掩码仍乱码；原因——最常见明文字节不一定是零；对策——改用块首字节模型，验证标准：乱码消失 + 第二份同系列固件复现 + round-trip 逐字节一致
- **CRC 字符串 ≠ CRC 字段**：现象——看到 CRC 名字符串就假设末尾是标准 CRC；原因——字符串只是元数据键；对策——系统排除常见 CRC/硬件 CRC/Adler/累加族后保留为未知字段，不强行命名
（来源：reverse-skill field-journal，MIT）
