Binwalk固件分析实战:熵值扫描、签名识别与分层提取
简介:本资源是面向CTF参赛者、信息安全初学者及Linux工具迁移需求者的binwalk Windows适配实践包,解决原生binwalk仅支持Linux环境导致的二进制分析工具使用门槛问题。压缩包共189个文件,含89个Python源码(覆盖核心扫描、解析与插件逻辑)、11个典型样本文件(firmware、squashfs、jffs2等固件镜像)、4个binwalk可执行脚本及配套bat/sh启动文件,另有conf、md文档、测试用compressed/ebml/misc等格式样本,完整支撑从环境部署、命令调用到结果验证的全流程实践,包体大小为81.74MB。已有3046人学习下载,资源直接提供Windows下通过WSL/Cygwin双路径运行binwalk的实操基础,包含可即用的批处理脚本、配置模板与多类型固件样例,目录结构按功能模块分层组织,便于快速定位分析入口、理解文件头识别规则与嵌套提取逻辑,是开展固件逆向、隐写分析和CTF MISC题型实战的高复用性工具集。
1. Binwalk 不是“解压神器”,而是嵌入式固件的 X 光机:它不帮你解压,但能告诉你压缩包里到底藏了几层 ZIP、几个 SquashFS、一段加密密钥,甚至一段被 base64 编码的 shellcode
你拿到一个.bin文件,双击打不开,file命令只回一句“data”,strings扫出几百行乱码——这是嵌入式设备固件、CTF MISC 题目、IoT 设备取证中最典型的“黑匣子”现场。Binwalk 就是专治这种黑匣子的底层探针:它不依赖文件头签名做粗暴匹配,而是用熵值分析定位数据块边界,用多级签名扫描(包括自定义签名)识别嵌套结构,再调用对应工具(如dd、unsquashfs、7z)自动提取。它不是万能解包器,但它是你决定“下一步该用什么工具”的决策引擎。适合刚接触固件逆向的 CTF 新手、需要快速拆解路由器/摄像头固件的安全工程师、以及在 Linux 下做嵌入式取证的一线人员。它跑得快、可脚本化、支持离线签名库更新,但对加壳、混淆、自定义加密格式完全无感——这点必须 upfront 说清,否则你会在某个看似正常的 LZMA 块里翻车三小时。
2. Binwalk 的核心能力不是“解包”,而是“分层测绘”:从熵值图到签名匹配,理解它为什么比binwalk -e多出 80% 的有效信息
2.1 熵值分析:为什么binwalk -E比strings更早发现隐藏分区?
Binwalk 的-E(entropy)模式本质是滑动窗口计算字节分布的香农熵。当窗口扫过纯文本区,熵值低(≈4.5);扫过加密或压缩数据,熵值陡升(>7.2)。这不是玄学阈值,而是基于真实固件样本统计得出的经验区间。执行以下命令:
binwalk -E -J firmware.bin-E启用熵值分析-J生成 JPEG 图像(熵值热力图),横轴为偏移量,纵轴为窗口大小(默认 512B~64KB),颜色越亮表示该偏移+窗口组合下熵值越高
提示:热力图中出现连续高亮带,大概率对应一个完整压缩块(如 SquashFS);若高亮呈离散点状,可能是加密密钥或硬编码字符串。不要直接信任单点峰值——我一般会把峰值偏移 ±1MB 范围内所有高熵段都记下来,后续逐个验证。
生成的firmware.bin-entropy.png可用eog或feh查看。注意:该图不显示具体数据内容,只提示“这里值得深挖”。真正的内容识别靠下一阶段。
2.2 签名扫描:为什么binwalk -B扫出的 offset 和file命令结果经常打架?
binwalk -B(binary signature scan)使用的是预编译的magic数据库(位于/usr/share/binwalk/magic/),但它比系统file命令更激进:
- 支持重叠扫描(同一字节可能命中多个签名)
- 内置 300+ 固件专用签名(如 TRX header、Broadcom CFE、U-Boot env)
- 可识别非标准偏移处的签名(如 padding 后的 LZMA)
执行标准扫描:
binwalk -B firmware.bin典型输出:
DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 TRX firmware header, little endian, image size: 6553600 bytes, CRC32: 0x1A2B3C4D 28 0x1C LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 12345678 bytes 1048576 0x100000 Squashfs filesystem, little endian, non-standard signature, 12 inodes关键点解析:
- offset
0x1C:TRX header 占用前 28 字节,LZMA 实际从第 29 字节开始——这解释了为何dd if=firmware.bin of=lzma.bin bs=1 skip=28才能正确提取 - Squashfs 的
non-standard signature:Binwalk 检测到 magic0x73717368("sqsh")但位置不在标准 offset(通常为 0x0 或 0x10000),说明该文件系统被故意错位存放,unsquashfs默认会失败,需手动指定-o 0x100000
注意:签名扫描结果只是假设,不是结论。我每次看到
non-standard signature都会立刻用hexdump -C firmware.bin | head -n 200对照 offset 验证 magic 字节是否真实存在。
2.3 自定义签名:如何为某款私有协议固件添加专属识别规则?
Binwalk 允许用户扩展签名库。假设你逆向出某设备固件头部含固定字符串MAGIC_V2_2023(ASCII),后跟 4 字节长度字段,再跟加密 payload。创建自定义签名文件my_magic.sign:
# My Custom Firmware Signature 0 string MAGIC_V2_2023 MyCustomFirmware v2 (len: %d) 4 lelong >0 # length field at offset 4然后扫描:
binwalk -m my_magic.sign firmware.bin- 第一行
0 string ...表示从 offset 0 开始匹配字符串 %d会自动替换为 offset 4 处读取的 4 字节小端整数(即 payload 长度)lelong >0是条件约束,避免误匹配全零区域
血泪经验:自定义签名务必加
>0类条件,否则 Binwalk 会在每个MAGIC_V2_2023出现处都触发,哪怕后面是垃圾数据。我曾因漏写条件,在 16MB 固件里扫出 237 个假阳性,浪费两小时。
3. 自动提取不是“一键解包”,而是可控流水线:-e参数背后的策略、限制与必须手动干预的 3 个节点
3.1-e的真实行为:它只做三件事——定位、切片、调用外部工具
执行binwalk -e firmware.bin时,Binwalk 实际执行流程如下:
- 运行完整签名扫描(等同于
-B) - 对每个识别出的区块,按类型调用对应工具:
- ZIP/PKZIP →
7z e - SquashFS →
unsquashfs -f -d <dir> - LZMA →
lzma -dc - Base64 →
base64 -d
- ZIP/PKZIP →
- 将提取结果存入
_firmware.bin.extracted/目录
但它绝不做以下事:
- ✗ 自动修复损坏的 ZIP 结构(如 missing EOCD)
- ✗ 解密 AES 加密的 LZMA 块(即使密钥在固件中明文存储)
- ✗ 处理嵌套过深的结构(如 ZIP 里套 TAR,TAR 里再套 SquashFS)
因此,-e是高效起点,不是终点。我习惯先运行binwalk -e -C /tmp/extract --run-as=root firmware.bin(-C指定临时目录,--run-as解决权限问题),再进_firmware.bin.extracted/手动验证。
3.2 关键参数详解:-M、-r、-C如何避免 90% 的提取失败
| 参数 | 作用 | 典型场景 | 避坑说明 |
|---|---|---|---|
-M(matryoshka) | 启用递归扫描:对每个提取出的文件再次运行 binwalk | 固件中嵌套 ZIP → TAR → BIN → SquashFS | 必须配合-C使用,否则递归生成的子目录会污染原目录;默认只递归 3 层,超深嵌套需-M --depth=5 |
-r(recover) | 强制恢复损坏的文件头(如截断的 ZIP) | CTF 题目故意删掉 ZIP 的 EOCD 记录 | 仅对 ZIP/7z 有效;可能产生大量垃圾文件,建议先binwalk -r -y zip firmware.bin单独测试 |
-C <dir> | 指定工作目录,所有提取操作在此目录下进行 | 多人协作时避免覆盖/tmp/下他人文件 | 若不指定,Binwalk 默认在当前目录建_*.extracted,容易和项目文件混在一起 |
实际命令组合:
# 安全递归提取(限 4 层,工作目录隔离) binwalk -e -M --depth=4 -C /tmp/fw_analysis firmware.bin # 仅恢复 ZIP 并提取(跳过其他类型,提速) binwalk -e -r -y zip firmware.bin3.3 提取后必做的三步验证:为什么ls _*.extracted/看起来成功,其实全是空壳?
自动提取完成后,绝不能直接认为任务结束。我强制执行以下三步:
Step 1:检查提取目录结构是否合理
find _firmware.bin.extracted/ -type f | wc -l # 正常固件应有数百文件;若只有 3~5 个,大概率提取失败Step 2:验证关键文件是否可读
# 检查 SquashFS 是否真解出 ls _firmware.bin.extracted/squashfs-root/etc/passwd 2>/dev/null || echo "⚠ SquashFS 提取失败" # 检查 LZMA 是否解压出合理大小 du -sh _firmware.bin.extracted/_*.lzma.extracted/* 2>/dev/null | awk '$1 > 1000 {print}' # 若解出文件均 <1KB,说明 LZMA 解压失败(可能需指定 dict size)Step 3:用file和readelf交叉验证
# 对提取出的疑似 ELF 文件验证 file _firmware.bin.extracted/_*.bin.extracted/* 2>/dev/null | grep "ELF" # 对 ARM 架构固件,确认是否含可执行段 readelf -h _firmware.bin.extracted/_*.bin.extracted/vmlinux 2>/dev/null | grep "Machine:"注意:
binwalk -e提取的文件名含随机哈希(如_firmware.bin.extracted/12345678.lzma.extracted),这是为避免命名冲突。我习惯用ls -t _firmware.bin.extracted/ | head -5快速定位最新提取目录。
4. 避坑:CTF 和固件分析中 Binwalk 最常翻车的 5 个现象及根因解决方案
4.1 现象:binwalk -B扫出 LZMA 块,但binwalk -e提取为空,lzma -dc报错Input format not recognized
- 原因:Binwalk 识别的 LZMA 块缺少标准 LZMA header(5 字节 properties + 8 字节 uncompressed size),常见于嵌入式固件中被裁剪的 LZMA 流。Binwalk 仅凭 magic
0x5D匹配,但lzma工具要求完整 header。 - 解决:手动补 header。先用
xxd -l 16 firmware.bin | grep "5d"定位 block 起始,再用 Python 补:
然后# lzma_fix.py with open("firmware.bin", "rb") as f: data = f.read() lzma_start = 0x1C # 替换为实际 offset # 添加 LZMA header: [props=0x5D][dict_size=8MB][uncompressed_size=0x12345678] header = b'\x5d\x00\x00\x80\x00\x78\x56\x34\x12\x00\x00\x00\x00' fixed_data = data[:lzma_start] + header + data[lzma_start:] with open("fixed.lzma", "wb") as f: f.write(fixed_data)lzma -dc fixed.lzma > kernel.bin
4.2 现象:binwalk -e提取出的 SquashFS 目录为空(squashfs-root/下无文件)
- 原因:
unsquashfs默认只解压 root inode,但某些固件 SquashFS 使用非标准 superblock offset(如0x100000),或启用了noI(no inodes)选项导致 inode 表不可见。 - 解决:强制指定 offset 并启用全部解压选项:
unsquashfs -f -d squashfs-root -o 0x100000 -i firmware.bin # `-i` 参数强制读取所有 inodes,绕过 superblock 校验
4.3 现象:熵值图(-E -J)显示大片高熵区,但binwalk -B完全没扫出任何签名
- 原因:该区域是强加密数据(如 AES-CBC),无固定 magic,或使用私有压缩算法(如某厂商定制的 LZ77 变种)。Binwalk 的签名库对此类数据完全静默。
- 解决:放弃自动识别,转人工分析。用
dd切出高熵段:dd if=firmware.bin of=high_entropy.bin bs=1 skip=1048576 count=2097152 # 然后尝试:strings(找密钥)、xxd(找重复 pattern)、ent(验证是否真随机) ent high_entropy.bin # 若 Entropy ≈ 7.999,基本可判定为加密
4.4 现象:binwalk -M递归扫描卡死,CPU 占用 100%,30 分钟无输出
- 原因:某层提取出的文件实为大块二进制垃圾(如未初始化的 flash 区域),Binwalk 对其反复扫描签名,陷入无限循环。
- 解决:限制递归深度和文件大小:
binwalk -e -M --depth=3 --size=10M firmware.bin # `--size=10M` 表示跳过 >10MB 的提取文件,避免处理垃圾数据
4.5 现象:CTF 题目中binwalk -B扫出Base64 encoded data,但binwalk -e提取的文件是乱码
- 原因:Base64 数据被截断(非 4 字节对齐)、含非法字符(如
0x00插入)、或末尾填充=被破坏。base64 -d遇到错误直接退出。 - 解决:用 Python 宽容解码:
import base64 with open("_firmware.bin.extracted/base64.bin", "rb") as f: raw = f.read() # 移除所有非 base64 字符(保留 A-Z a-z 0-9 + / =) clean = bytes([b for b in raw if b in b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=']) # 补齐 = 符号 while len(clean) % 4 != 0: clean += b'=' try: decoded = base64.b64decode(clean) with open("decoded.bin", "wb") as f: f.write(decoded) except Exception as e: print("Base64 decode failed:", e)
5. 进阶技巧:用 Binwalk 构建固件差异分析流水线——从两个版本固件中自动定位新增后门模块
5.1 场景还原:某次固件升级后设备异常外连,需快速定位变更点
你手上有firmware_v1.0.bin和firmware_v2.1.bin,厂商宣称只修复了 UI bug。但网络流量显示 v2.1 版本多出一个192.168.100.100:443的 HTTPS 连接。目标:不反汇编整个固件,而用 Binwalk 快速定位新增的可疑模块。
5.2 核心思路:将固件视为“文件系统快照”,用提取结果做 diff
Binwalk 本身不提供 diff 功能,但它的提取结果天然适配diff。流程分三步:
- 对两个固件分别执行标准化提取(确保目录结构一致)
- 提取关键子系统(如
/bin/,/sbin/,/lib/)并计算文件指纹 - 对比指纹差异,聚焦高风险路径
步骤 1:标准化提取(关键!避免随机哈希干扰 diff)
# 创建统一提取目录 mkdir -p fw_v1 fw_v2 # 对 v1 提取,强制使用固定目录名(禁用随机哈希) binwalk -e -C fw_v1 --run-as=root firmware_v1.bin # 手动重命名提取目录(SquashFS 总在 squasfs-root) mv fw_v1/_firmware_v1.bin.extracted/squashfs-root fw_v1/rootfs # 对 v2 同样操作 binwalk -e -C fw_v2 --run-as=root firmware_v2.bin mv fw_v2/_firmware_v2.bin.extracted/squashfs-root fw_v2/rootfs步骤 2:聚焦高风险路径,生成指纹清单
# 生成 /bin/ 下所有文件的 SHA256(忽略时间戳) cd fw_v1 && find rootfs/bin -type f -exec sha256sum {} \; | sort > bin_v1.sha256 cd ../fw_v2 && find rootfs/bin -type f -exec sha256sum {} \; | sort > bin_v2.sha256 # 同样处理 /sbin/, /lib/ for d in sbin lib; do cd ../fw_v1 && find rootfs/$d -type f -exec sha256sum {} \; | sort > ${d}_v1.sha256 cd ../fw_v2 && find rootfs/$d -type f -exec sha256sum {} \; | sort > ${d}_v2.sha256 done步骤 3:用comm找出 v2 新增文件(即只在 v2 存在的 SHA256)
# 对比 /bin/ 目录 comm -13 <(cut -d' ' -f1 bin_v1.sha256 | sort) <(cut -d' ' -f1 bin_v2.sha256 | sort) \ | while read hash; do # 在 v2 中找出对应文件路径 grep "$hash" bin_v2.sha256 | cut -d' ' -f2- done | grep -E "\.(so|bin)$"典型输出:
rootfs/bin/hidden_monitor rootfs/lib/libcrypto_backdoor.so这就是你要的后门!
hidden_monitor是新进程,libcrypto_backdoor.so是劫持 OpenSSL 的动态库。接下来只需strings或readelf -d分析即可确认。
5.3 为什么这个方法比直接diff firmware_v1.bin firmware_v2.bin有效?
- 直接二进制 diff 会产生海量差异(时间戳、校验和、padding 变化),淹没真正逻辑变更
- Binwalk 提取后,你对比的是“语义层”(可执行文件、配置、库),而非“比特层”
- 固件中 90% 的变更集中在
/bin//sbin//lib/,此法精准聚焦
从那以后我每次分析固件升级包,都强制走一遍这个binwalk → extract → sha256 → comm流水线。它不保证找到所有后门,但能以 5 分钟代价筛掉 80% 的无效分析。希望帮到你。
本文还有配套的精品资源,点击获取