上个月处理一个路由器固件时又踩了老坑:binwalk扫描能清清楚楚列出一堆偏移和签名,真正执行binwalk -e解包却只吐出一个外层文件系统,内层私有的东西纹丝不动。后来把binwalk/config/extract.conf打开逐行改了一遍才算解决。关于这个配置文件的说明文档少得可怜,多数教程只留下一句“编辑extract.conf可以扩展解包”,但字段含义、匹配逻辑、WSL环境下的坑基本靠猜。这篇文章就把我实际修改extract.conf的过程、验证方法和回滚方案完整记下来,给同样在搞固件分析、数据恢复或者CTF题解的朋友一个可直接照做的思路。
先交代一下适用范围:本文内容围绕binwalk 2.x版本展开,运行环境以WSL(Ubuntu)为主,也兼容普通Linux。你不需要对binwalk源码多熟悉,但至少用过binwalk -e,知道它能把嵌入文件抽出来。如果连binwalk怎么装都还没搞定,建议先跑一遍sudo apt install binwalk,再回来看下文。
1. binwalk能识别却解不开:extract.conf在解包链路里的真正作用
1.1 签名扫描和解包是两套独立机制
很多人会有一个误解:binwalk既然能识别出文件里的某个格式,提取时就一定会把它解出来。实际上,binwalk的“识别”和“解包”是两个完全不同的阶段。
识别阶段靠的是magic签名数据库。binwalk会扫描文件里的字节流,把符合特征的位置标记出来,然后告诉你“这里有个gzip压缩数据”“那边有个SquashFS文件系统”。这一步不依赖任何外部工具,纯靠字节匹配就能完成。
解包阶段就复杂多了。binwalk知道这里有gzip数据,但要不要解压、用什么工具解压、解压后怎么放,需要看外部工具是否存在、配置规则是否匹配。这里起关键作用的就是extract.conf。这个文件的本质是一张“规则表”,告诉binwalk:当扫描到某个签名时,执行哪条外部解包命令,把结果放到哪里。
换句话说,如果某个格式能被识别,但没有对应规则,binwalk只能把那段数据当作普通数据抠出来,不会帮你展开内部结构。这就是大量“识别到了却解不开”问题的根源。
1.2 默认配置覆盖范围比你想象的小
默认自带的extract.conf覆盖的都是社区里最常见、最标准的格式,比如gzip、zip、tar、7z、SquashFS、JFFS2这一类。只要目标固件用的是这些通用算法,binwalk -e基本能跑通。
一旦碰到下面几种场景,默认规则就不够用了:
- 私有固件头,厂商自己打包、魔数也是自定义字节;
- 某些游戏ROM镜像(比如NDS类封包),binwalk能识别出数据段,但不会调用专用解包指令;
- 加密压缩包,或者需要在解包前做额外解密的格式;
- 需要特殊参数才能解开的变种文件系统,比如非标准SquashFS。
处理这些情况时,要么按老办法手动dd切数据,再用其他工具继续,要么就扩展extract.conf,让binwalk碰到对应魔数时自动调用你指定的脚本或第三方解包器。
1.3 “解一层就停住”也跟extract.conf有关
还有一个常见现象:binwalk -e确实解出了一层,但解出的结果里嵌套了另一个文件系统,binwalk却没有继续往下解。这不一定是规则没写,更多时候是因为你在命令行里只用了-e,没有加递归参数。
binwalk的-e默认不会对解包结果里的文件再次做深入提取,-M(递归扫描)和-e配合才会一层层剥洋葱。很多教程随口一句“binwalk -e自动递归”其实不严谨。
我提到这个是想提醒你,写规则的时候要考虑清楚:你的规则是要“解一层”还是“持续递归”。如果只是解一层,交给binwalk的默认行为就行;如果要递归解嵌套结构,最好把递归逻辑写进你自己调用的脚本里,而不是依赖extract.conf单行规则。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 改配置之前先做三件事:定位、读注释、备份
2.1 先弄清楚当前binwalk实际加载的是哪个extract.conf
这个文件最坑的地方在于:系统里可能不止一个。包管理器安装的、pip安装的、源码运行的可能各带一份,路径也不同。
我常遇到的情况是:在WSL里用sudo apt install binwalk装完后,配置文件在/etc/binwalk/config/extract.conf;但如果后来用pip在虚拟环境里装过另一个版本,运行时加载的可能是虚拟环境site-packages下面的版本。
如果当前路径不确定,最快的办法是直接搜索:
bash复制sudo find / -name extract.conf -type f 2>/dev/null
找到后别急着改,先用下面命令确认当前加载的文件是哪一个:
bash复制python3 -c "import binwalk, pathlib; print(pathlib.Path(binwalk.__file__).parent / 'config' / 'extract.conf')"
这个方法不一定能覆盖所有binwalk版本,但它能给出一个有效线索。如果binwalk是通过源码仓库运行的,那么文件路径也和标题里的binwalk/config/extract.conf对得上,也就是源码目录下的config子目录。
2.2 打开文件先读注释,字段顺序以当前版本为准
很多人一上来就按网上的规则模板往文件里追加内容,结果binwalk报错或者安静地忽略掉,然后怀疑人生。我一般会先花两分钟看文件顶部的注释,因为不同版本的extract.conf对字段的定义不完全一样。
以我在2.3.2版本上看到的配置为例,核心规则会涉及“匹配哪个签名”“输出扩展名”“执行哪条外部命令”这几件事,同时会有一组占位符变量,常见的有:
%p:当前输入文件的路径;%d:binwalk为这次提取分配的目录。
每个版本的注释都会写清楚字段顺序和可用变量,你照着注释里给的例子复制一行再改,比自己瞎写要稳得多。
这里特别提醒WSL用户:不要在Windows的记事本或写字板里编辑Linux侧的文件后再存回去。记事本默认的编码、换行符和Linux习惯完全不一样,保存出来的文件经常带\r回车符,导致binwalk解析配置时匹配失败。在WSL里直接用nano或者vim编辑最省事:
bash复制sudo nano /etc/binwalk/config/extract.conf
顺带一提,如果发现配置里莫名其妙解析不了,可以先检查一下文件有没有Windows换行符:
bash复制file /etc/binwalk/config/extract.conf
如果显示with CRLF line terminators,跑一行清理命令就好:
bash复制sudo sed -i 's/\r$//' /etc/binwalk/config/extract.conf
2.3 任何修改都要先备份
extract.conf这个文件很小,改动却很容易把整个解包流程搞挂。我有一个雷打不动的习惯:修改前先把原文件备份一份,备份名带上日期。
bash复制sudo cp /etc/binwalk/config/extract.conf /etc/binwalk/config/extract.conf.bak.$(date +%F)
这样做的好处是调整没生效时可以快速回滚,不用去网上重新下载默认配置。
另外,修改策略上也建议“末尾追加”而不是“原地改动”。默认配置里规则的先后顺序可能会影响匹配,追加新规则的风险最小。如果你要修改的某条默认规则本身有问题,也建议把被覆盖的行注释掉,而不是直接删除,保留原始面貌对排查很有帮助。
3. 三个常见扩展场景:从NDS镜像到自定义固件包
3.1 场景A:让binwalk识别到某个签名时自动调用专用解包工具
第一个场景很典型。假设我拿到一个老式嵌入式设备固件,厂商自定义了一个魔数CSTM,binwalk的magic数据库里已经有这个魔数(或者我自己加过),所以binwalk firmware.bin能识别出对应的段。但binwalk -e解出来的只是一堆没有意义的原始数据,因为后面没有对应的解包器。
手头正好有一个写好的解包脚本custom_unpacker,它能从该段数据里还原出真正的文件系统。此时就能在extract.conf里追加一条规则,大意是:当binwalk识别到名为CSTM的签名时,执行custom_unpacker处理当前文件,并输出到%d目录。
text复制# 示意规则:实际字段顺序以本机extract.conf顶部注释为准
CSTM custom custom_unpacker %p %d
为了尽可能减少字段对不上的问题,我的做法是:先复制默认配置里同样处理“自定义封包”的那一行,改成自己的签名名和命令。即使字段顺序有出入,拿现成模板改也比凭空写靠谱。
规则添加后重新执行:
bash复制binwalk -e --run-as=root firmware.bin
为什么要加--run-as=root?因为不少解包器在提取文件系统时需要创建设备节点或修改文件权限,普通用户执行会报错。WSL默认用户如果不是root,需要配合sudo使用。
3.2 场景B:单次任务里的快速替代方案——binwalk --dd
不是所有单次提取都值得专门改extract.conf。有些时候我只是想看看某个偏移处的数据究竟是什么,手动先抠出来再处理,效率反而更高。
binwalk提供了一个--dd参数,它允许你跳过默认的extract规则,直接把某类签名对应的数据从文件中扣出来。比如我想把所有gzip数据抠出来:
bash复制binwalk -e --dd='gzip:gzip:0' firmware.bin
括号里的三段内容大体表示“匹配类型:输出文件后缀:偏移条件”。如果规则复杂,建议先binwalk firmware.bin查看偏移位置,再直接用dd命令手工把那一整段数据抠出来:
bash复制dd if=firmware.bin of=segment.bin bs=1 skip=123456 count=7890
严格来说这不算改extract.conf,但它是我确认新规则是否可行之前常用的探路手段。先手动抠出一段数据,跑一遍目标解包工具,确认工具能正常工作,再回到配置文件里写规则,这样排查范围小很多。
3.3 场景C:写一个递归解包包装器,再通过extract.conf接入
遇到嵌套多层、且每一层都要跑binwalk的情况,单条规则很难优雅解决。我习惯把“递归解包”封装成一个独立shell脚本,然后在extract.conf里把命令指向这个脚本。
脚本大致长这样:
bash复制#!/usr/bin/env bash
set -euo pipefail
INPUT_FILE="$1"
OUTPUT_DIR="$2"
# 第一次解包
binwalk -e "$INPUT_FILE" -C "$OUTPUT_DIR"
# 对解包结果中的文件继续执行binwalk,限制深度防止死循环
MAX_DEPTH=3
for ((i=0; i<MAX_DEPTH; i++)); do
FOUND=0
while IFS= read -r -d '' f; do
FOUND=1
binwalk -e "$f" -C "$(dirname "$f")" || true
done < <(find "$OUTPUT_DIR" -type f -print0)
if [ "$FOUND" -eq 0 ]; then
break
fi
done
脚本写得不算优雅,但足够应付大多数嵌套固件。需要注意两点:一是为了防止解包循环把磁盘写满,必须设最大深度;二是在脚本里调用binwalk -e和extract.conf里的规则可能会互相触发,如果没有收敛条件,可能无限循环下去。
写完后把脚本放到/usr/local/bin/并赋予执行权限:
bash复制sudo cp recursive_unpack.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/recursive_unpack.sh
然后在extract.conf里追加一条规则,让binwalk识别到目标签名时调用这个脚本。这样处理的好处是把复杂的递归逻辑留在脚本里,配置文件只负责“签名到命令”的映射,职责清晰,排查也方便。
4. WSL下跑binwalk的隐藏坑与收尾习惯
4.1 路径性能问题:拖慢解包的头号因素
很多人第一次在WSL里用binwalk,会直接对/mnt/c/Users/xxx/Downloads/firmware.bin执行扫描。这样做不是不行,只是Windows文件系统在WSL里的IO性能比Linux原生文件系统慢很多,尤其在大型固件上,扫描时间可能差出好几倍。
我的建议是先把文件复制到Linux侧再处理:
bash复制cp /mnt/c/Users/xxx/Downloads/firmware.bin ~/firmware/
cd ~/firmware
binwalk -e firmware.bin
另外,binwalk -e解包会产生大量碎片文件,输出目录默认就在当前目录下的_firmware.bin.extracted里,如果文件较大,建议提前确认磁盘剩余空间,避免解到一半磁盘写满。可以把临时目录切到空间充裕的位置:
bash复制mkdir -p /tmp/binwalk_tmp
TMPDIR=/tmp/binwalk_tmp binwalk -M -e firmware.bin
4.2 外部解包器缺失:报错信息不会直接告诉你缺哪个
我在WSL里最常遇到的报错长这样:
text复制WARNING: Extractor.execute failed to run external extractor 'sasquatch': [Errno 2] No such file or directory
看到这种信息第一反应不是去改extract.conf,而是先确认外部命令装了没有。sasquatch是SquashFS变种解包工具的常见名字,jefferson是JFFS2解包工具,ubi_reader负责UBI镜像,这些在很多WSL默认环境里根本没有。
逐个检查最直接:
bash复制which 7z
which unsquashfs
which sasquatch
which jefferson
which ubireader_extract_files
缺什么就补什么。Ubuntu/Debian系的WSL可以先用包管理器搜索:
bash复制apt-cache search sasquatch
apt-cache search jefferson
apt-cache search ubi
有些工具打包在发行版仓库里,能apt install直接装;有些需要去GitHub源码编译。源码编译的依赖相对繁琐,但好在网上资料不少,这里不展开。
4.3 WSL里编辑extract.conf最容易忽略的CrLf问题
在Linux原生环境里,很少有人会遇到回车符问题。但WSL环境特殊,很多人习惯用Windows工具编辑文件,或者把Windows下载的配置直接拷贝进来。一旦文件带了CRLF,binwalk解析时可能把\r当成规则内容的一部分,导致签名匹配不上。
典型现象:明明规则写对了,binwalk -e -v却没有任何输出,或者匹配到后命令参数错误。检查方法用刚才说过的file命令。如果我改了配置文件后发现binwalk像瞎了一样完全不执行,第一反应就是查换行符。
4.4 配置了新的规则还是没反应,先看签名名称是否对得上
这个问题不仅出现在WSL里,但实际使用中非常致命。
extract.conf里的匹配条件依赖binwalk输出里的“描述名称”。假如binwalk在扫描时输出的是NDS ROM image,而你在规则里写的是NDS rom,哪怕只是大小写不一致,也可能匹配不上。
所以写规则前,我习惯先跑一次纯扫描,把binwalk显示的签名描述原样记下来,再回头写规则:
bash复制binwalk test.bin
如果规则里用到的描述名和实际输出不一致,binwalk会在日志里静默跳过,看起来就像规则完全没生效。
5. 规则加了不生效?按这套流程定位和回滚
5.1 打开verbose模式看提取过程
binwalk -e在正常模式下非常沉默,成功了不输出细节,失败了也只给一行WARNING。调试时建议加上-v参数:
bash复制binwalk -e -v firmware.bin
verbose模式下能清楚看到binwalk尝试调用了解包器、执行了什么命令、命令的退出码是多少。如果看到命令执行了但输出为空,问题多半在命令参数;如果看到“Executing extractor”后面跟着命令名,说明extract.conf里的规则已经成功匹配,只是工具本身没能完成任务。
还有一种情况是规则匹配了,命令执行了,解包结果却不在预期目录里。这时候检查规则里有没有把输出目录写好,很多解包器默认把结果输出到当前目录,而不是%d目录。
5.2 常见新增规则报错对照
我整理了一份高频问题对照表,遇到问题按图索骥即可:
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
日志中出现No such file or directory |
规则指定的外部工具未安装或不在PATH里 | 用which确认工具路径,缺则安装 |
| 日志中没有任何规则执行痕迹 | 签名描述名与规则不匹配,或文件换行符异常 | 核对binwalk扫描输出,检查CRLF |
| 命令执行了但输出目录为空 | 参数里的%d没生效或工具需要手动建目录 |
先手动执行一次规则命令,确认工具参数 |
| 程序解包到一半卡死 | 递归层数过深或磁盘空间不足 | 在脚本里加递归深度限制,检查磁盘空间 |
| 部分文件权限错误 | 非root用户执行解包器 | 加上--run-as=root或改用sudo运行 |
表格里的问题我基本都遇到过,其中最隐蔽的就是第一类:工具未安装但binwalk没直接说是缺工具,而是笼统报个“failed to run external extractor”。
5.3 回滚方案:一分钟恢复到修改前
改坏了不要慌,只要之前做过备份,回滚就是复制粘贴的事。例如前面备份为extract.conf.bak.2025-01-01,现在恢复:
bash复制sudo cp /etc/binwalk/config/extract.conf.bak.2025-01-01 /etc/binwalk/config/extract.conf
如果没有备份,可以尝试卸载重装binwalk来恢复默认配置,但这样成本更高。我个人现在会用git去管理/etc/binwalk/config/整个目录,每次修改前提交一次,改坏了直接git checkout回来,比手动备份更清晰。
如果是源码仓库里的binwalk/config/extract.conf,用git diff看改动也很方便,至少能定位自己到底改了什么。
5.4 一套适合大多数场景的“最小验证法”
最后分享一个我自己固定的验证流程,新规则写完一定按这个顺序走:
- 先跑
binwalk test.bin,记录目标签名在输出里的完整描述名称; - 手动单独执行规则里的命令,确认命令本身能正常工作;
- 在
extract.conf末尾追加规则,先不启用递归,只用-e解一层; - 运行
binwalk -e -v test.bin,观察是否执行了对应命令; - 确认解包结果与第2步手动执行的结果一致;
- 再考虑是否改成递归方案或加入更多文件名处理逻辑。
第2步最容易被跳过,但它恰恰是最能节省时间的一步。如果命令本身就不支持这个输入文件,后面无论配置文件怎么写都不会有结果。
整套流程走下来,绝大多数“识别了但解不开”的固件都能通过扩展extract.conf解决。改动时记住一个原则:外部工具负责实际解包,extract.conf只负责把binwalk扫描到的签名和外部工具对接起来。搞明白这层关系,以后再遇到冷门格式,你的第一反应就不再是到处找万能工具,而是冷静地拆解格式、写解包器、加规则,形成自己的解包工作链。
