news 2026/10/2 7:25:33

固件分析实战:从平台识别到分区解析与版本提取

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
固件分析实战:从平台识别到分区解析与版本提取

1. 先聊聊:为什么一份固件会“没人讲得清”

1.1 固件不是文件,是一整套设备的“施工图”

我做嵌入式维护和刷机支援这几年,最怕的不是设备坏,而是接手一份说不清来源的固件。上个月一批设备送来,甲方只扔给我几个文件,名字分别是“update.zip”“full.bin”“8H26_固件.zip”,连版本号都对不上。他们原话是:上一个负责的同事离职了,文档一点没留。

这种场面在行内实在太常见了。固件听起来是一个文件,但实际它就是一台设备的微型操作系统,包含引导程序、内核、文件系统、应用数据、厂商私有配置。不同芯片平台的固件格式差异极大,Amlogic、Rockchip、Allwinner、Hisilicon、MTK、Realtek、Espressif,每家都有自己的打包习惯,甚至同一家芯片在不同代工批次下,分区布局都会变。更别提很多硬件厂商还会在固件头部加一段私有结构,用来存放版本号、校验和、签名,或者干脆把分区表藏起来。

拿我手里这批文件举例。有一个叫“b860av1.1t-s905sm-b-nand非高安版安卓7.1固件”的文件,光看文件名,信息很全:中兴盒子、晶晨S905M-B芯片、NAND闪存、非高安版、安卓7.1。可一旦拆开看,分区顺序和另一个版本的同一机型完全不一样,名字只能当参考,不能当依据。还有一个创维电视的主程序包“8H26 m9系列 v020.008.260”,文件名里包含主板型号和版本号,但实际它有U盘强刷和串口烧录两种模式,对应两种不同的刷写方式,搞混了直接不开机。

所以“没人讲得清”一点也不夸张。固件本身不会说话,但它每一条二进制记录都在描述自己的身份。问题是,你得有办法让它开口。

1.2 我要做的工具,解决哪几件事

被这类问题反复折磨之后,我决定做一个统一入口的小工具,把它当成一个“固件翻译器”来用。目标很明确,就解决五件事。

第一,自动识别平台和固件类型。拿到一个文件,先别管它叫什么名字,工具必须能从文件头、字符串、魔数里判断这是哪家芯片的方案,是完整全量包、OTA差分包、还是编程器备份的固件。

第二,提取关键信息,包括版本号、编译时间、厂商标识、内核版本、分区表。这些信息散落在二进制深处,靠肉眼翻十六进制不现实。

第三,解析分区和文件系统。识别出这个固件分为哪几个区,每个区在文件里的偏移和大小,对应的文件系统格式是什么。

第四,拆包和重打包。把固件解包成可浏览的目录,让人能看里面的应用、配置和脚本;如果需要,还能按原结构重新打包,同时更新必要的校验和。

第五,生成一份人类能读的报告,无论是给甲方看,还是留给后来接手的人,都省心。

我一开始也考虑过是不是直接用现成工具就够了。binwalk、瑞芯微官方工具、Amlogic工具都很好,但问题是它们各自为政,而且对厂商私有的头部结构经常无能为力。我需要的是一个把它们串起来、还能记录经验的一体化脚本,而不是每次都在十几个工具之间手工切换。

1.3 工具选型:为什么用 Python 加外部工具

工具本体我用 Python 3 写,这东西做原型和文件分析太顺手了。外部命令全部用 subprocess 调用,保持灵活。核心依赖是 binwalk、file、unsquashfs、jefferson,再加几个分区工具。

为什么不是全部自己造轮子?因为固件格式太多了,像SquashFS、JFFS2、UBIFS这类文件系统,成熟工具已经很稳定,完全没必要重新实现。我的工具重点是做“决策和汇总”,而不是做文件系统驱动。打个比方,我做的相当于一个懂行的调度的哥,能判断该叫哪一路车,而不是自己造一台车把所有货都拉完。

这个选择在后面的调试过程中也证明是对的。binwalk负责扫描签名,file负责初步类型识别,自研代码负责解析头部、算偏移、提取版本、生成报告,各司其职。

2. 拿到固件后的第一课:怎么让它自己“开口”

2.1 先看文件头,别管文件名怎么写

拿到固件,我第一件事永远是看文件头,文件名一律视为不可信信息。

具体做法很简单:用十六进制编辑器打开文件前几十个字节,或者直接在终端跑一下 file 命令。file 在很多情况下会直接告诉你一些有用的初步信息,比如“Android bootimg”“gzip compressed data”“Zip archive data”之类。但更多时候,输出的只是“data”,遇到这种情况不要慌,继续往下挖。

接着用 binwalk -e 对整个文件做签名扫描。binwalk 会从文件里扫出可识别的文件签名,并且标注每个签名块在文件里的偏移量。这一步能看到的信息非常关键。

我常用的几个魔数给大家做一个速查:

内容魔数/特征含义
Android Boot Image“ANDROID!”安卓内核引导镜像
SquashFS“hsqs”常见于机顶盒/system分区
U-Boot Image0x27051956老式U-Boot打包镜像
DTB设备树0xd00dfeed设备树二进制
UBIFS卷头“UBI#”NAND设备文件系统
ext系列超级块0x53EFext2/3/4文件系统

打个比方,文件头就像一个人的身份证号前几位,能确定大体归属地;binwalk扫出来的签名就是路上的路牌,能告诉你这条路径上会经过哪些关键地点。

遇到update.zip这类文件,先别急着解包,先看压缩包里有什么。如果里面是payload.bin,那是新版安卓OTA包,主要数据全部在payload里,需要专门的payload工具处理。如果里面是META-INF目录加一堆.img,那是老式recovery卡刷包,可以直接拆。

2.2 平台特征:芯片厂商的“软件签名”

芯片厂商都会有自己独特的固件标记。有些写在明文头字段里,有些藏在引导程序里,需要字符串扫描才能发现。

以我常遇到的晶晨方案为例,Amlogic的固件里经常能找到“AML”字样的标识,bootloader就是U-Boot,根文件系统可能是ext4也可能是SquashFS。Rockchip方案可以用rk开发工具识别,固件头有它自己的一套结构。Allwinner平台用sunxi系列工具能解开头部。Hisilicon方案的uboot会输出厂商信息字符串,很多文件里直接能看到“hisilicon”字样。

ESP32这类MCU平台没有传统意义上的芯片私有大头文件,但它有固定的flash布局约定。用esptool.py的read_flash命令把整个flash备份下来,bootloader在0x1000位置,分区表在0x8000位置附近,应用区通常在0x10000,这些位置虽然会因为配置而变,但默认布局足够应付多数备份和还原场景。

三星手机的刷机固件则常见为.tar.md5文件,解开后是boot.img、system.img、vendor.img等一组镜像,bifrost工具就是围绕这套格式做的。

这些平台的识别经验,我全部沉淀到了工具里。工具会先扫文件头,再扫字符串,最后结合binwalk结果,给出一个“平台候选得分”,得分最高的就排在最前面。

2.3 分区表与文件系统:固件的内部格局

识别完平台,下一层就是看分区。

一个固件文件,本质上就是多个分区的线性拼接。每个分区的偏移和长度,有的写死在bootloader的DTS里,有的存在专门的env或misc分区里,还有一些是固定偏移。找到分区表是解包的前提。

Amlogic平台的固件,分区布局经常能从U-Boot环境变量里翻出来。中兴B860AV1.1T这类盒子,常见的分区包括bootloader、env、recovery、boot、system、data、cache,不同版本顺序还会变,所以不能用死偏移。

文件系统这一层相对好办。ext4镜像可以直接mount -o loop挂载来分析;SquashFS用 unsquashfs 解包;JFFS2用jefferson工具;UBIFS用ubi_reader加nandsim模拟挂载。这里最容易踩的坑是UBIFS,它依赖NAND模拟参数,直接离线解包经常会失败,需要先确认页面大小和块大小等参数。

我还是建议一个小习惯:解包之后立刻生成一份PARTITIONS.md,记录每个分区的偏移、大小、文件系统类型、挂载点。这份文件比固件本身值钱得多,后面所有人都能看懂。

2.4 版本信息到底藏在哪

版本号是所有固件信息里最容易被问到、也最容易被找错的地方。

常见位置有这么几处:一是安卓设备里build.prop文件,路径通常为/system/build.prop,里面ro.build.version.release、ro.build.display.id这些字段就是版本身份;二是内核cmdline或dtb里;三是固件头部的厂商自定义结构,比如有的厂商会直接把编译时间戳写进头部;四是二进制文件中的可读字符串附近,用strings加grep搜索关键字最快。

还有很重要的一个场景:升级包如果是增量包,版本信息常常放在META-INF/com/android/metadata里。它记录了build fingerprint和SDK级别,指纹比对能直接判断这个包能不能刷到某一台设备上。

我在工具里实现了一个自动搜集逻辑:解包后优先找build.prop,其次在二进制字符串里找带“version”或“build”的字样,最后再结合文件名做人工交叉验证。有一次,一个文件名上写V2.3的固件,解包后build.prop里却是V2.1,后来一查,是打包的人把文件名写错了。结论是永远相信二进制内部的内容,不信文件名。

3. 固件加密与安全分析:能干与不能干的边界

3.1 加密和签名到底在防什么

固件加密和签名,是设备安全体系里的两条线。加密的目的是防“看”,签名的目的是防“改”。

厂商给固件做加密,很大程度是防止固件被直接提取后复制到竞品硬件上,或者防止别人从里面挖出商业机密、算法、密钥。而签名是用来保证固件在升级传输过程中没有被篡改,设备启动时验签通过才运行,一旦签名不匹配,系统直接拒载。这就是为什么很多物联网设备被硬改固件后开不了机,因为它不是系统损坏,而是签名校验没通过。

对普通维护者来说,理解加密和签名最大的意义在于:遇到一份固件,先判断它能不能直接拆改。如果只是头部有厂商特征码,没有完整签名机制,那么拆包改包是有可能的。如果有完整签名校验并且公钥已经烧死在芯片里,那就不要费劲去绕过,直接找官方工具和授权才是正路。

3.2 怎么判断一份固件是否加密或签名

判断加密最朴素的方法是算熵值。binwalk -E 选项可以直接输出文件各区域的熵,如果某个区域熵接近1.0,那基本可以判定为压缩或加密数据。正常明文代码和结构头部的熵值通常在0.5到0.8之间,突然出现高熵区,就要打起精神。

判断签名相对简单一些。很多固件会在文件尾部附一段固定长度的字节,常见的RSA-2048签名是256字节,RSA-4096签名是512字节,尾部签名后面或者前面经常有固定结构。你可以用openssl asn1parse对尾部数据做个解析,看看能不能解出签名结构。

另外,很多厂商为了兼容自己的升级工具,会在头部放一个“是否加密”的标记字段,或者用几个字节的魔数表示加密算法。我的工具会上报这些标记位,方便后续判断。

做安全分析时,我自己有一条很清晰的工作规矩:只对自己拥有的设备、在合法授权范围内做备份和恢复。研究固件结构可以,复制别人的固件不行;分析自己设备的加密机制可以,破解别人的服务不行。固件安全是用来保护设备的,不是用来制造漏洞的。尤其现在智能硬件数量庞大,固件一旦被恶意篡改,很可能被利用发起网络攻击,所以分析的底线必须守住。

3.3 合规底线

这一点我专门写一个小节,是因为在社区里经常看到有新手把固件安全理解成“破解加密包”,然后对着别人的付费固件使劲。方向从一开始就错了。

固件分析的正确场景是什么?一是自己买的设备,做备份和还原;二是开源硬件项目,比如ESP32上自己编译的固件、或者公开的固件源码,随便怎么分析都行;三是安全研究人员在厂商授权范围内做漏洞挖掘;四是给客户做售后维护,用官方工具处理官方固件。除此之外,把矛头对准商业固件的加密和签名,既没有技术价值,也容易把自己卷进麻烦。

4. 我的fwbox是怎么落地的

4.1 工具的功能清单

工具我取名fwbox,代码已经跑了一段时间,核心命令就是这五条:

  • fwbox info:输出固件的基本信息,包括平台、格式、分区表、版本候选。
  • fwbox extract:自动解包,生成分区目录和PARTITIONS.md。
  • fwbox diff:比较两份固件的差异。
  • fwbox repack:按分区表重新打包,支持更新校验和,但检测到强签名时会主动提示风险。
  • fwbox check:计算MD5/SHA256,检查完整性,并输出签名识别结果。

设计原则很简单:任何操作都不改动原始文件;所有中间产物统一放到out目录;外部工具用subprocess调用,有异常时输出完整日志。这套东西不是为了替代现有工具,而是为了让所有信息集中在一个地方,减少重复劳动。

4.2 核心流程和关键代码

工具的完整流程分五步。第一步读取文件头,判断基础文件类型。第二步调用binwalk扫描签名,拿到关键结构的偏移表。第三步根据偏移表,用Python读文件对应区间,进一步判断每个分区的内容类型。第四步如果发现是常见的镜像格式,就调用解包工具拆分。第五步汇总所有信息,生成报告。

这里放一段最核心的binwalk调用逻辑,其实并不复杂:

import subprocess import re def scan_with_binwalk(path): """调用binwalk扫描签名,返回结构列表""" result = subprocess.run( ["binwalk", path], capture_output=True, text=True, encoding="utf-8", errors="ignore" ) structures = [] for line in result.stdout.splitlines(): match = re.match(r"(\d+)\s+0x[0-9A-Fa-f]+\s+(.*)", line.strip()) if match: offset = int(match.group(1)) desc = match.group(2) structures.append({"offset": offset, "desc": desc}) return structures

核心就是解析binwalk的标准输出,把偏移和描述提取出来。真正麻烦的是后续的决策逻辑:当你看到一系列分区时,要判断哪个是bootloader、哪个是kernel、哪个是rootfs。这一层我用的是本地配置库加规则引擎,规则里存着各家平台的典型分区顺序。

判断平台特征的部分,我写了一个简单的字符串搜索函数:

def probe_platform(data): """根据头部和特征字符串判断芯片平台""" head = data[:4096] if b"AML" in head or b"Amlogic" in head: return "amlogic" if b"hisilicon" in data[:65536]: return "hisilicon" if b"RKFW" in head or b"Rockchip" in head: return "rockchip" if data[:8] == b"ANDROID!": return "android-bootimg" return "unknown"

这只是简化版,真实脚本里会做更多校验,比如先看魔数再看字符串,还会结合binwalk结果做交叉评分。

4.3 用四个真实案例验证工具

工具写完不是拿来摆着看的,我找了几类典型固件做验证。

第一个是中兴B860AV1.1T,S905M-B芯片NAND非高安版。收过来时是一整个.img文件,大概1.5GB。fwbox info识别出晶晨平台,并提示分区表可能在U-Boot env里。解包后system分区是ext4,挂载进去直接看到了预置应用列表和build.prop,版本号、Android版本一目了然。这个案例的价值在于:平台识别准,之后所有偏移计算都不会跑偏。

第二个是ESP32-HID设备。一个开源的蓝牙HID项目,用esptool.py把整个flash读出来。fwbox识别出bootloader、partition table、nvs、ota_0、ota_1、spiffs等区块,分区表直接解析成表格。后来设备配置丢失,我用备份的nvs分区恢复了参数,整个操作十几分钟就完成。

第三个是蓝牙耳机主控1562A刷固件后变砖。坦率说,这种小主控的固件很多是厂商工具配合专用DFU升级流程,一般binwalk扫不出文件系统,因为就是单纯的代码段。工具在这里做的事主要是把刷机前的原固件完整导出留底,以及验证刷入的bin文件哈希是否和官方一致,防止下载到损坏半截的包。救砖时进入DFU模式重新刷原版固件,重点在于确认主控批次和固件版本的匹配关系,乱刷同型号不同批次的包照样不开机。

第四个是N1盒子刷YYF固件后存储显示异常。设备显示系统已用110G、剩余4G,但用户并没有往里面装什么大文件。用fwbox把固件解包后,重点检查了分区表里data分区的挂载配置,再用df -h和du排查实际占用。结果发现是某个缓存目录被日志写满,清理之后空间恢复正常,跟扩容操作本身没有关系。这个案例说明一个道理:存储显示异常时,先不要急着重刷,先看分区表和日志。

5. 高频问题与避坑实录

5.1 新手最容易踩的五个坑

第一个坑:拿文件名当版本号。文件名可以随手改,但二进制内容不会骗人。凡是重要固件,一律以解包后build.prop或头部字段的版本为准,再和文件名交叉比对。

第二个坑:用十六进制编辑器看到一半就开始改。新手最容易在文件里找到一段版本号字符串,改成自己想要的数字,写回去,刷机,变砖。因为字符串长度一旦不同,整个布局就偏了。改固件字段前一定要先确认文件结构和校验方式。

第三个坑:把编程器固件当成普通升级包直接整包刷。编程器固件是整个flash的完整备份,包含坏块标记、保留区、bootloader,很多东西不能直接刷到另一台同型号机器上。正确做法是先按分区表裁剪出每个分区,再逐分区处理。

第四个坑:以为所有固件拆了就能原样装回去。支持签名的系统,boot和system分区都验签的,你改了任何一个字节,签名校验就失败。工具在repack前会检查尾部签名区,如果有强签名,我会主动提示这条路径走不通。

第五个坑:忽略OTA包的增量性质。很多update.zip里根本没有完整system镜像,只有patch脚本和二进制差异块。拿到这种包,在里面找完整system.img是不存在的,得先搞清楚它是全量包还是增量包。

5.2 问题速查表

现象可能原因处理办法
binwalk扫不出任何东西固件被加密或头部有偏移先看熵值,再找厂商专属工具
解包后全是乱码JFFS2或UBIFS工具不对用jefferson、ubi_reader配合nandsim
修改固件后无法启动签名或校验和失效用官方全量包,不要硬改
存储空间显示异常日志写满或分区挂载异常用df/du定位,再决定是否重刷
刷机后变砖平台批次不匹配进DFU/短接模式,刷回原版对应批次固件
固件文件过大且重复多分区拼接在一起按分区表裁剪后逐区分析

这张表是我这一年使用频率最高的参考,每遇到一起故障,先查表定位方向,再上手操作。

5.3 一些值得长期坚持的习惯

最后分享几个实在的经验。第一,所有原始固件,收进来先算MD5和SHA256,记录下来。后面任何一次分析、刷机、对比,都以这个哈希为基准,能避免很多乌龙。

第二,建立自己的固件指纹库。每个固件分析完,把平台、分区数、文件大小、CRC、关键字符串特征存一份。这个库越攒越值钱,因为许多新固件都是基于老固件改的,特征会反复出现。我这套工具现在已经积累了上百条指纹记录,遇到陌生文件,先和库里的历史数据比对,识别的准确率高了一大截。

第三,分析固件前先看熵值。高熵区块优先判断为加密或压缩,低熵区块优先找明文结构,这个顺序能省下大量盲扫时间。

第四,改固件之前,一定要先确认自己手里有救砖方案。可能是编程器,可能是短接触点,可能是官方恢复工具。我见过很多在测试固件上翻车的同行,最后都是靠刷机前留下的备份把设备救回来的。备用固件、救砖方法这两样东西,任何时候都不能缺。

第五,遇到解不开的固件,先搜文件里的字符串,看有没有厂商内部工具名。很多时候厂商自己的升级软件就是最好的分析器,它能刷进去,就能告诉你这是什么固件。我不止一次靠这个线索打开了局面,甚至有的固件直接用官方工具就能解包,比自己写脚本高效得多。

做固件分析这件事,说难也难,说易也易。难在格式杂乱、资料稀少;易在只要抓住“识别平台、解析分区、定位版本、验证完整”这条主线,大部分问题都能拆解。我个人现在再接到“没人讲得清的固件”,心态已经和从前完全不同了——文件再乱,也总有迹可循。工具只是帮我把这些痕迹串起来而已,真正值钱的,是对着二进制也能耐下心、一层层往下挖的耐心。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 7:23:42

智能制造MES系统简介:从工单到追溯,车间执行层数字化怎么落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:22:44

openrig 配置指南:统一管理 Claude Code 与 Codex 的 AI 编码工具链

1. 从 openrig 说起:一个被低估的 AI 编码工具配置层第一次看到openrig这个名字,我下意识以为是某个开源钻机项目——毕竟 rig 在工业领域就是钻井平台的意思。直到我在几个 Claude Code 和 Codex 的讨论串里反复撞见它,才意识到这是个跟 AI …

作者头像 李华
网站建设 2026/10/2 7:22:23

LabVIEW中Float转十六进制全指南:从原理到大小端字节序处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:21:59

计算机网络怎么学?一套从数据包旅程到分层的认知框架

学计算机的人,几乎没有谁没被《计算机网络》折磨过。我记得大三那会儿翻开教材,第一章讲互联网发展史还能看进去,第二章OSI七层模型一出来,满页的"物理层、数据链路层、网络层、传输层"直接把我劝退。后来期末复习我只能…

作者头像 李华
网站建设 2026/10/2 7:21:07

Type-C OTG协议芯片选型与CC引脚电路设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华