引言
IoT 与固件逆向是 CTF 里最「物理」的方向:它不给你一个规整的二进制,而是给你一段从设备里读出来的裸镜像、一个串口日志,或者一个厂商官网的升级包。题目要你从这些碎片里还原出设备的行为、找出隐藏的凭据或后门,最终拿到 flag。它和移动逆向共享「多层剥离」的思路,但多了硬件层与文件系统层的复杂度。
工程上的难点集中在三处。第一是入口不确定:固件可能藏在 SPI Flash 芯片里、可能从 OTA 升级包拿到、也可能只能通过串口 dump;不同入口得到的镜像完整度差别很大,需要先判断「我手上这份是完整镜像还是分区片段」。第二是架构五花八门:ARM、MIPS、MIPSel、ARM Thumb、甚至 RISC-V,交叉反编译必须选对架构与端序,选错了反汇编出来全是垃圾指令。第三是运行环境难以复现:嵌入式程序依赖特定的硬件外设、内核模块与启动脚本,直接丢到 x86 上跑不起来,必须靠 QEMU 做用户态或系统态仿真。
本文按「攻击面 → 固件提取 → 解包 → 架构识别 → 硬件调试 → 协议分析 → 凭据排查 → 仿真 → 防御」的顺序组织,所有实验在自建靶场固件、公开的 CTF 附件与自己拥有或获授权的设备上完成。设备侧的整体防护体系可参考 移动与 IoT 安全 ;文件系统层的结构与取证方法可对照 Linux 文件系统与磁盘 与 Misc 方向隐写与取证 。
目录
- IoT 设备的攻击面与题目形态
- 固件提取:Flash、OTA 与串口三条路径
- binwalk 解包与文件系统还原
- 架构识别与交叉反编译
- 硬件调试接口:UART 与 JTAG
- 通信协议分析:MQTT 与 CoAP
- 硬编码凭据与后门排查
- 仿真运行:QEMU 与 Firmadyne
- 防御要点与检测
1. IoT 设备的攻击面与题目形态
一个典型的 IoT 设备由四层构成,每层都有独立的攻击面:
| 层级 | 组件 | 典型攻击面 |
|---|---|---|
| 硬件层 | SoC、Flash、调试口 | UART/JTAG 暴露、Flash 可读 |
| 固件层 | Bootloader、内核、根文件系统 | 硬编码凭据、未签名更新 |
| 系统层 | 服务进程、Web 面板、UPnP | 命令注入、认证绕过 |
| 通信层 | MQTT、CoAP、私有协议 | 明文传输、弱认证、重放 |
CTF 里的固件题目通常按「你拿到的入口」分类:
- 给整段 Flash 镜像:最常见,需要自己判断分区表与文件系统类型。
- 给厂商 OTA 升级包:通常是加密或签名过的容器,考的是解密与解压链。
- 给设备与串口:需要用 USB-TTL 连接,读启动日志甚至进 bootloader 命令行。
- 给一段抓包:考的是协议逆向与密钥提取,与流量分析方向重叠。
判断题型后立刻能确定路线:有整镜像就直接 binwalk,有升级包先分析容器格式,有串口就先抓启动日志。启动日志是信息密度最高的入口——它通常会打印 SoC 型号、内核版本、分区挂载点、以及各个服务的启动状态,一次采集就能省掉大量猜测。
2. 固件提取:Flash、OTA 与串口三条路径
三条提取路径的成本与完整度各不相同,实际做题时按可获得性选择。
路径一:从 Flash 芯片直读。设备主板上通常有一颗 SPI NOR Flash(如 Winbond W25Q128,16 MB)。硬件手法是用 SPI 编程器(CH341A)夹住芯片读取,或在设备上找到 SPI 的 CLK/MOSI/MISO/CS 测试点飞线。软件手法是在能拿到 shell 的前提下,直接在设备上读 /dev/mtd*:
# 本地教学:在已授权的自建设备上 dump 固件
cat /proc/mtd # 列出分区表:名字、大小、擦除块大小
dd if=/dev/mtd0 of=/tmp/boot.bin # 读 bootloader 分区
dd if=/dev/mtdblock2 of=/tmp/rootfs.bin # 读根文件系统分区
nanddump -f /tmp/ubifs.bin /dev/mtd3 # NAND 设备需用 nanddump
/proc/mtd 是解题的关键线索:它直接告诉你每个分区的名字(u-boot、kernel、rootfs、nvram)与偏移大小,比从整镜像里猜要快得多。注意 /dev/mtd* 是字符设备(带 OOB 数据),/dev/mtdblock* 是块设备(去掉 OOB),NAND 设备必须用 nanddump 才能保留正确的 ECC 布局。
路径二:从 OTA 升级包。厂商的升级包可能是明文 tar、加密的 zip、或者自定义容器(如某些厂商用 AES-CBC 加密后包一层 header)。分析顺序是:先 file 与 xxd 看头部,再 binwalk 扫内嵌签名,最后若发现是加密容器,就从设备固件里找解密密钥(通常硬编码在某个 libupgrade.so 或 upgrade 二进制里)。
硬件读取的细节值得单独说:SPI NOR Flash 的 SOIC-8 封装引脚顺序是固定的(1 号脚有圆点标记),用 CH341A 编程器夹住后读出的镜像是整个芯片的线性映射,包含所有分区。读出后应当先 sha256sum 记录哈希(保证后续分析的是同一份数据),再用 xxd 核对首字节——若开头是 0xFF 的成片填充,说明该区域未写入或被擦除。还有一种常见情况是双镜像(A/B 分区):芯片里存了两份固件以便回滚,解题时两份都要看,因为老版本可能没有修掉后门。
路径三:从串口 dump。当 Flash 被读出保护(RDP)锁住时,只能通过 UART 进 bootloader(U-Boot)命令行,用 md(memory display)与 tftpput/ymodem 把内存里的固件传到主机。这条路径最慢但最通用。
三条路径可以组合使用:先用 OTA 包拿到明文根文件系统(解包最快),再用 UART 验证运行时行为(看真实启动参数),最后若发现关键逻辑在加密分区里,才动用 Flash 直读或 JTAG。按「成本从低到高」的顺序尝试,是最省时间的策略。
# 本地教学:U-Boot 命令行下的常见操作(自建设备)
=> bdinfo # 打印板级信息:内存布局、波特率
=> printenv # 打印环境变量,常含启动命令与分区定义
=> md.b 0x80000000 0x100 # 以字节为单位 dump 内存
=> tftpput 0x80000000 0x100000 192.168.1.10:dump.bin # 通过 TFTP 传出
printenv 的输出常含 bootargs(内核启动参数,能看出根文件系统类型与分区)、bootcmd(默认启动命令),以及出厂默认的 IP 与升级口令。
3. binwalk 解包与文件系统还原
拿到固件镜像后的第一步是识别结构。binwalk 通过扫描已知签名定位内嵌的文件系统与压缩流:
# 本地教学:固件解包标准流程
binwalk firmware.bin # 列出所有识别到的签名与偏移
binwalk -e firmware.bin # 按签名提取(仅对本地自建文件)
binwalk -E firmware.bin # 熵分析:识别加密段(熵接近 8 的区间)
strings -a -n 8 firmware.bin | grep -iE "password|admin|token" | head
熵分析是判断「有没有加密」的关键:正常固件里代码段熵约 6–7,压缩数据熵约 7.9,而全 0 的空白区熵接近 0。若镜像中间出现一段持续高熵的区域,基本可以断定是加密或强压缩的内容,此时 binwalk 的签名扫描会失效,需要先解密再解包。
嵌入式根文件系统的类型决定了后续怎么挂载:
| 类型 | 特征 | 还原方式 |
|---|---|---|
| SquashFS | 魔数 hsqs(小端)/ sqsh(大端) | unsquashfs -d out rootfs.sqsh |
| JFFS2 | 魔数 0x1985(字节序敏感) | jefferson -d out jffs2.img |
| UBIFS | 需 UBI 层信息(UBI#) | 先 ubireader_extract_images 再 ubireader_extract_files |
| CramFS | 魔数 0x28cd3d45 | cramfsck -x out cramfs.img |
| ext2/3/4 | 标准 ext 超级块 | mount -o loop 或 debugfs |
| YAFFS2 | 无固定魔数,需按 OOB 布局解析 | unyaffs 或 yaffs2utils |
# 本地教学:SquashFS 还原(最常见的类型)
unsquashfs -d rootfs_extracted rootfs.sqsh
ls rootfs_extracted/etc/init.d/ # 启动脚本,看起了哪些服务
cat rootfs_extracted/etc/passwd # 默认账号与口令哈希
cat rootfs_extracted/etc/shadow # 若存在,含真正哈希
还原后的根文件系统是信息宝库,按优先级排查:
/etc/passwd、/etc/shadow:默认账号与口令哈希(嵌入式设备常用弱口令)。/etc/init.d/、/etc/rc.local:启动脚本,能看出有哪些服务、监听哪些端口。/etc/下的厂商配置:Wi-Fi 默认口令、云端 API key、MQTT broker 地址与凭据。/usr/sbin/、/usr/bin/:自定义二进制,是逆向的主要目标。/etc/nginx/或/etc/lighttpd/:Web 面板配置,常暴露 CGI 路径。
4. 架构识别与交叉反编译
嵌入式设备极少用 x86,架构选错会让反汇编彻底失败。识别方法有三层,从快到准:
# 本地教学:架构识别三层法
# 第一层:ELF 头(对单个可执行文件最准)
file rootfs_extracted/usr/sbin/upgrade
# 输出形如:ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked
# 第二层:镜像整体(对无文件系统的裸镜像)
binwalk -Y firmware.bin # 用 capstone 反汇编扫描,猜测架构
# 第三层:读启动日志里的 SoC 型号,反查其内核架构
嵌入式常见的几种架构与识别要点:
| 架构 | file 输出关键词 | 端序 | 典型厂商 |
|---|---|---|---|
| ARM | ARM, EABI5 | 小端为主 | 高通、博通、全志 |
| ARM Thumb | ARM, EABI5, Thumb | 小端 | 代码密度优化场景 |
| MIPS | MIPS, MIPS32 | 大小端都有 | 联发科、瑞昱、Atheros |
| MIPSel | MIPS, MIPS32, LSB | 小端 | 大量家用路由器 |
| RISC-V | RISC-V, RV32/RV64 | 小端为主 | 新兴 IoT 芯片 |
| PowerPC | PowerPC | 大端 | 部分工业设备、老网络设备 |
反编译工具的选择:IDA Pro 对 MIPS 与 ARM 的支持最成熟,Ghidra 免费且反编译质量在近年追得很近。加载时必须显式选对处理器型号与端序,特别是 MIPS 的 mipsl(小端)与 mipsb(大端)不能混。若 file 报出的是「MIPS, MIPS32 rel2」,在 IDA 里选 mipsr(MIPS32 小端)通常正确。
一个实用的验证技巧:反汇编后如果看到大量非法指令或明显的对齐错乱,就是架构或端序选错了;正确的反汇编里函数序言(ARM 的 push {r4, lr}、MIPS 的 addiu sp, sp, -0x20)应当规律出现。
# 本地教学:命令行交叉反编译(无 GUI 环境)
# 用 Ghidra headless 批量反编译
analyzeHeadless /tmp/proj fw -import ./upgrade -postScript DecompileAll.java
# 或用 objdump 做快速粗读(注意指定架构)
objdump -D -b binary -m mips:isa32 -EB firmware.bin | head -50
5. 硬件调试接口:UART 与 JTAG
硬件层接口是「绕过一切软件防护」的后门:拿到 UART 就能读启动日志、进 bootloader、甚至拿到 root shell;拿到 JTAG 能直接读写内存与寄存器。
UART 定位:主板上通常有 3 或 4 个未焊接的焊盘,标着 TX、RX、GND、VCC。用万用表找 GND(与屏蔽罩或电容负极导通),再用示波器或逻辑分析仪看哪个脚在启动时有数据(TX 脚会有约 3.3V 的方波)。波特率常见为 115200 或 57600,用 screen 或 picocom 连接:
# 本地教学:连接 UART(自建设备)
picocom -b 115200 -d 8 -p n /dev/ttyUSB0
# 或在 minicom 中:115200 8N1,无流控
# 启动设备后能看到 U-Boot 与内核日志
一个高频技巧是在 U-Boot 启动倒计时期间打断自动启动(通常是按任意键),进命令行后可以改 bootargs(如加 init=/bin/sh 直接进 shell)、或用 md/tftpput 导出内存。有些设备还允许从 U-Boot 直接 tftpboot 一个自定义内核,完全绕过厂商固件。
JTAG 定位:JTAG 有 4–5 个必需信号(TCK、TMS、TDI、TDO、可选 TRST),比 UART 复杂但权限更高——能 halt CPU、读写任意内存、设硬件断点。定位方法是用 JTAGulator 或 JTAGenum 自动扫描引脚组合。拿到 JTAG 后可用 OpenOCD 连接:
# 本地教学:OpenOCD 连接(需已知 SoC 的 tap 定义)
openocd -f interface/ftdi/ft2232.cfg -f target/mips_m4k.cfg
# 在 telnet 控制台里:
# halt 停止 CPU
# mdw 0x80000000 16 读 16 个字
# dump_image out.bin 0x80000000 0x100000
JTAG 的价值在于绕过 Flash 读保护:即便固件被加密存储,CPU 运行时的内存里也一定有明文(或至少是解密后的代码),通过 JTAG halt 后 dump 内存即可。
检测与防御:硬件接口的防御手段是「熔断 eFuse 禁用 JTAG」「启用安全启动」「Flash 加密 + 密钥存于 SoC 内部不可读区域」。这些措施能把成本从「飞线 + 万用表」提高到「需要芯片级攻击」,是消费级与工业级设备的主要分界线。
6. 通信协议分析:MQTT 与 CoAP
IoT 设备与控制端、云端的通信协议是最容易出问题的环节,因为它常常在「能跑就行」的优先级下被草率实现。
MQTT 是发布/订阅模型,基于 TCP(1883 明文 / 8883 TLS)。它的报文结构极简:固定头(1 字节类型 + 变长剩余长度)、可变头、载荷。CONNECT 报文里含 Client ID、用户名、密码——这三者在明文 1883 上完全可见。
# 本地教学:抓取并解析 MQTT(自建 broker 与设备)
tcpdump -i eth0 -w mqtt.pcap port 1883
tshark -r mqtt.pcap -Y mqtt -T fields -e mqtt.msgtype -e mqtt.topic -e mqtt.msg
# 直接用 mosquitto 客户端订阅通配主题,看设备在发什么
mosquitto_sub -h broker.local -t '#' -v
MQTT 的常见问题:允许匿名连接(allow_anonymous true);ACL 未配置导致任意客户端可订阅 # 通配主题,从而拿到所有设备的数据;用客户端 ID 或 MAC 当作身份标识(可伪造);TLS 未启用或未校验服务端证书。
报文类型的高四位决定了包的种类,做题时按这张表快速定位:
| 类型值 | 名称 | 用途 |
|---|---|---|
| 1 | CONNECT | 建立连接,携带 Client ID / 用户名 / 密码 |
| 2 | CONNACK | 连接确认,返回码 0 表示成功 |
| 3 | PUBLISH | 发布消息,含主题名与载荷 |
| 8 | SUBSCRIBE | 订阅主题(可含通配符 + 与 #) |
| 12 | PINGREQ | 心跳保活 |
PUBLISH 的载荷格式由设备厂商自定义:可能是裸 JSON、可能是二进制结构体、也可能是加密后的一段数据。若载荷是加密的,密钥通常硬编码在固件的对应二进制里,此时需要回到反编译流程找解密函数——这也是「协议题」与「固件题」经常合并出现的原因。
CoAP 是面向 UDP 的轻量协议(默认 5683),为受限设备设计。报文结构为固定 4 字节头 + token + options + payload,用 GET/POST 等方法操作资源,语义上类似 HTTP。它的 Block1/Block2 选项用于分块传输,观察(Observe)选项用于订阅资源变化。
# 本地教学:CoAP 探测(自建设备)
coap-client -m get coap://device.local/.well-known/core # 发现可用资源
coap-client -m get coap://device.local/status
私有二进制协议是固件题的高频考点:题目给一段 UART 或 TCP 抓包,要求还原协议字段。分析方法与 Misc 方向隐写与取证 里的流量题一致——先看字节频率与固定位置的常量(可能是魔数与长度字段),再看载荷是否随操作变化,最后用固件里对应的解析函数交叉验证。协议逆向与网络协议栈的基础知识可参考 网络协议基础 。
一个实用的交叉验证方法:在反编译出的固件里搜协议相关的字符串或常量(如魔数 0x55AA、校验多项式),找到解析函数后,把函数逻辑与抓包逐字节对照,就能确定每个字段的含义。
检测与防御:MQTT 应强制 TLS 与双向认证、禁用匿名、按设备粒度配置 ACL;CoAP 应使用 DTLS(coaps://)并启用 PSK 或证书;私有协议应做完整性校验(HMAC)与防重放(nonce + 时间戳)。固件更新必须签名验证,否则一次 OTA 劫持就能批量控制设备。
7. 硬编码凭据与后门排查
硬编码凭据是 IoT 设备最常见也最致命的问题,排查应当系统化而非靠 grep 碰运气。
第一层:文件系统里的静态凭据。
# 本地教学:凭据排查脚本化
grep -rniE "(password|passwd|pwd|secret|token|api[_-]?key)\s*[:=]" rootfs_extracted/etc/ | head -40
cat rootfs_extracted/etc/passwd rootfs_extracted/etc/shadow 2>/dev/null
# 查 SSH 密钥、TLS 证书私钥
find rootfs_extracted -name "*.pem" -o -name "id_rsa" -o -name "*.key"
第二层:二进制里的字符串。凭据可能被编译进可执行文件而非配置文件:
# 本地教学:二进制字符串排查
strings -a -n 6 rootfs_extracted/usr/sbin/* | grep -iE "admin|root|12345|default" | head
# 对特定二进制做更细的排查
strings -a -n 4 rootfs_extracted/usr/bin/cgi-bin/login.cgi | less
第三层:后门与调试接口。常见形态包括:调试用的隐藏 CGI(如 /cgi-bin/debug)、带固定口令的 telnetd 启动项、以特定 UDP 包触发的命令执行、以及「万能口令」(某个固定字符串绕过所有认证)。
# 本地教学:查启动脚本里的可疑服务
grep -rn "telnetd\|dropbear\|debug" rootfs_extracted/etc/init.d/
# 查是否有硬编码的默认 Wi-Fi 口令算法(常见于用 MAC 派生口令的设备)
grep -rn "ssid\|wpa\|wlan" rootfs_extracted/etc/ | head -20
第四层:弱口令哈希破解。嵌入式设备的 /etc/shadow 常含 DES 或 MD5 crypt 的哈希,用 hashcat 或 john 针对嵌入式设备的常见口令字典(如设备型号、root、admin、12345)爆破:
# 本地教学:识别并破解嵌入式口令哈希
cat rootfs_extracted/etc/shadow | grep -v '^[^:]*:[*!]'
# $1$ 开头是 MD5 crypt,$5$ 是 SHA-256,$6$ 是 SHA-512
hashcat -m 500 hashes.txt wordlist.txt # -m 500 = md5crypt
检测与防御:每一台设备的默认凭据必须唯一(出厂时随机生成或首次登录强制修改);禁止编译进调试后门;发布固件前用自动化工具扫描硬编码凭据;关闭不必要的调试服务(telnet、调试 CGI);启用安全启动确保固件不可篡改。
8. 仿真运行:QEMU 与 Firmadyne
当固件依赖特定硬件而无法在真机上跑时,仿真能在 x86 主机上复现设备行为。它有两个层次:
用户态仿真(qemu-user) 只模拟 CPU 指令集,让单个二进制在主机内核上运行。它快、简单,适合分析单个程序(如某个 CGI):
# 本地教学:用户态仿真 MIPS 二进制
sudo apt install qemu-user-static binfmt-support
qemu-mipsel -L rootfs_extracted ./rootfs_extracted/usr/sbin/upgrade
# -L 指定动态链接库的根目录(sysroot),否则找不到 .so
# 若程序检查架构,可配合 qemu-mipsel-static 与 chroot
sudo chroot rootfs_extracted qemu-mipsel-static /usr/sbin/upgrade
用户态仿真的常见失败原因是「缺少动态库」或「调用了主机不存在的 ioctl」。前者用 -L 或 chroot 解决,后者只能转系统态仿真或手工 patch。
系统态仿真(qemu-system) 模拟整个 SoC 与外设,能启动完整的内核与根文件系统。Firmadyne 与 FirmAE 是自动化这一过程的框架:它们从固件里提取内核与文件系统、推断网络配置、构造启动脚本,最终把设备「跑起来」并提供网络访问。
# 本地教学:FirmAE 自动化仿真(对自建靶场固件)
sudo ./run.sh -c -a ./firmware.bin # -c 清理环境,-a 自动分析并仿真
# 仿真成功后可用 nmap 扫描其网段,直接对 Web 面板做测试
系统态仿真的三个主要障碍:自定义内核(厂商魔改的内核可能缺少 QEMU 支持的驱动,需要换用通用内核或打补丁);NVRAM 依赖(设备从 Flash 的 NVRAM 分区读配置,仿真时需要构造一份默认 NVRAM);硬件外设(Wi-Fi 芯片、GPIO、看门狗在仿真里不存在,程序可能卡在初始化)。FirmAE 相比 Firmadyne 在「解决启动卡死」上有显著改进,成功率更高。
仿真的价值不只是「跑起来」:一旦设备在仿真里运行,就能用常规的 Web 漏洞扫描、命令注入测试、以及动态调试手段去分析它,把嵌入式问题转化为熟悉的 Linux 服务问题。
网络配置是仿真中最容易被忽略的一环。QEMU 默认用用户态网络(SLIRP),设备只能主动外连、外部无法直连它,这对测试 Web 面板是致命的。正确做法是配置 TAP 网桥,把仿真设备接入主机的一个虚拟网段:
# 本地教学:为仿真设备配置 TAP 网络(需要 root)
sudo ip tuntap add dev tap0 mode tap
sudo ip link set tap0 up promisc on
sudo ip addr add 192.168.0.1/24 dev tap0
# 在 QEMU 启动参数里加:
# -netdev tap,id=n1,ifname=tap0 -device e1000,netdev=n1
# 之后即可用 nmap 扫描 192.168.0.0/24 找到仿真设备
nmap -sV -p- 192.168.0.100
有了可直连的仿真环境,后续的漏洞验证就能像测一台普通 Linux 服务器一样进行,这一步往往比仿真本身更有价值。
检测与防御:仿真能成功本身就是一种警告——它意味着设备的固件没有做足够的硬件绑定校验。防御手段包括「启动时校验关键外设的指纹」「内核做完整性度量」「关键逻辑放安全 enclave」。当然,这些手段的成本与设备价格成正比,消费级设备通常不做。
9. 防御要点与检测
把前面的攻击面反过来,就是一份 IoT 固件安全要求清单:
| 攻击面 | 风险 | 防御措施 |
|---|---|---|
| Flash 可读 | 固件与凭据泄露 | Flash 加密,密钥存 SoC 内部 |
| UART/JTAG 暴露 | 直接获得 shell 或内存读写 | 熔断 eFuse、隐藏调试口 |
| 未签名固件 | 恶意固件刷入 | 安全启动 + 固件签名验证 |
| 硬编码凭据 | 批量设备被控 | 每台唯一凭据、出厂强制改密 |
| 明文 MQTT/CoAP | 流量窃听与重放 | TLS/DTLS + 双向认证 |
| 无 ACL | 通配订阅拿到全量数据 | 按设备粒度配置 ACL |
| 调试服务常开 | telnet/调试 CGI 被利用 | 发布版移除所有调试接口 |
| OTA 无校验 | 升级劫持 | 签名 + 防回滚(版本号单调递增) |
三条工程原则值得强调。第一是**「出厂即安全」:不能指望用户去改默认口令,必须在生产环节为每台设备生成唯一凭据。第二是「信任链从硬件开始」:安全启动(Secure Boot)让 CPU 只执行签名过的 bootloader,是整条信任链的根,没有它,上层所有防护都可能被一次刷机绕过。第三是「最小暴露」**:设备上不该有的服务一律不装,调试接口在量产版一律关闭。
检测侧的能力建设同样重要:网络层监控设备是否在向异常地址发送数据(被控信号)、固件更新是否来自预期源、是否存在异常的 MQTT 订阅行为。这些属于设备侧的持续可观测性,与 移动与 IoT 安全 中讨论的设备生命周期管理直接相关。
权衡取舍
| 场景 | 优先手段 | 理由 | 局限 |
|---|---|---|---|
| 有完整 Flash 镜像 | binwalk -e + unsquashfs | 一步到位还原文件系统 | 加密段需先解密 |
| 只有 OTA 升级包 | 先分析容器格式与密钥 | 唯一入口 | 密钥可能硬编码在别处 |
| Flash 有读保护 | UART 进 bootloader | 绕过 Flash 保护 | 需要焊接与串口工具 |
| 软件防护极强 | JTAG 直接读内存 | 绕过一切软件限制 | 需要引脚定位与 SoC 定义 |
| 单个二进制分析 | qemu-user 用户态仿真 | 快,无需完整系统 | 缺库与 ioctl 会失败 |
| 要测 Web 面板 | FirmAE 系统态仿真 | 提供完整网络环境 | 自定义内核可能启动失败 |
| 协议不明 | 抓包 + 固件交叉验证 | 字段语义可确定 | 依赖固件里能找到解析函数 |
| 无硬件可用 | 纯静态分析 | 零硬件成本 | 加密与动态解密逻辑看不到 |
选型的判断顺序是:先确定「手上有什么」,再确定「缺什么」。有整镜像就静态解包;缺文件系统就走 OTA 或串口;缺明文代码就上仿真或 JTAG。切忌在没确认架构与端序的情况下直接反编译——那只会浪费时间在垃圾指令上。
常见坑清单
- 架构或端序选错:MIPS 大小端混用,反汇编全是非法指令;先用
file确认,再在 IDA/Ghidra 里显式选型号。 - 把
/dev/mtd当块设备读:字符设备含 OOB 数据,直接dd得到的镜像无法挂载;NAND 必须用nanddump。 - binwalk 在加密段上无效:高熵区域扫不出签名,先做熵分析确认加密范围,再找解密密钥。
- SquashFS 版本不兼容:新版
unsquashfs可能不支持老固件的 LZMA 压缩,需要装对应版本或用-comp指定。 qemu-user报「No such file or directory」:实际是找不到动态链接器,用-L指定 sysroot 或用qemu-*-static。- 系统态仿真卡在启动:多为 NVRAM 缺失或外设初始化失败;用 FirmAE 的
-c清理并用通用内核替换。 - UART 波特率猜错:日志全是乱码,常见值是 115200 与 57600,两个都试一遍。
- 忽略
/etc/passwd与/etc/shadow的差异:前者可能只有占位符,真正哈希在 shadow;两者都要看。 - 只 grep 配置文件而漏了二进制:大量凭据被编译进可执行文件,必须对
/usr/bin、/usr/sbin做strings排查。 - 在未授权设备上操作:本文方法仅适用于自建设备、公开 CTF 靶场与自己拥有或书面授权的硬件,其余情形属违法。
小结
固件逆向的核心是「入口决定路线,分层逐级剥离」。拿到整镜像就 binwalk 解包,拿到升级包先解容器,只有串口就进 bootloader;解包后按「文件系统 → 二进制 → 硬件接口 → 协议」四层推进,每层都有成熟的工具与方法。真正拉开差距的不是工具,而是对嵌入式系统整体结构的理解——知道 /proc/mtd 会告诉你分区、知道 U-Boot 的 printenv 里有出厂配置、知道 MIPS 的端序陷阱。
仿真这一环近年进步最大:FirmAE 这类框架把「让固件跑起来」从数天的调试压缩到几十分钟,使得嵌入式设备也能享受常规的漏洞扫描与动态测试。但仿真成功本身也暴露了一个事实——许多设备的固件缺少硬件绑定校验,这既是攻击者的便利,也是防守方需要补的课。
最后回到防御:IoT 安全的问题很少出在「算法不够强」,而是出在「出厂凭据不唯一」「调试接口没关」「固件更新不验签」这些工程细节上。这些问题的修复成本极低,收益极高,是设备厂商最应该优先投入的方向。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。