安卓ROM定制:从payload提取到镜像重打包的完整工具链 玩安卓玩到一定阶段总会碰到一个绕不开的需求想删掉厂商塞进去的几个推广应用想把默认壁纸换掉想给系统预置一张 WiFi 配置或者干脆想把手头的 ROM 换个内核。下载一个别人提前做好的精简包是省事但包到底改了什么、安不安全没人替你背书。这时候最稳妥的办法就是自己把安卓 ROM 解包、改完、重新打包。这条链路的起点几乎都是拿到厂商发布的完整 OTA 包然后从 payload.bin 里把各分区镜像提取出来。听起来不复杂实际走下去你会发现payload 提取只是热身真正折磨人的是 sparse、erofs、super 这些镜像格式以及 fastboot 刷回去时的校验逻辑。这篇文章就把我自己从 payload 提取到镜像重打包的完整工具链和踩坑记录捋一遍适合所有想系统学习安卓 ROM 定制、想在解包打包这件事上少走弯路的人。1. 在动手之前先把这条工具链的完整地图记在心里1.1 四种典型场景对应四种不同的工具路径先说清楚为什么要碰这条链。ROM 定制的需求看着五花八门拆开其实就几种只想 root 或者换内核只需要提取 boot.img用 Magisk 修补或者直接替换 kernel重新打包 boot 分区刷回去。这个路径最短根本不需要碰 system。想去预装、加预装 APK、改 build.prop至少需要解包 system部分新机型还要连带 product、vendor因为厂商预装散落在多个分区里。给老设备移植新系统需要同时处理 boot、system、vendor、dtbo、vbmeta 多个分区是最完整也最容易翻车的一条线。做第三方 ROM 发布或者二次分发要走到重新生成 OTA 包那一步涉及 target_files 和签名工作量跟前面的完全不是一个量级。所以动手之前先想清楚你到底卡在哪个需求上目标越明确越不会做无用功。我见过太多人一上来就整个 system 解包改了半天 selinux 权限最后发现自己只想 root白忙一场。1.2 整条工具链的四段流水线把上面这些场景抽象一下所有操作都可以归纳成四段流水线环节核心工具产出物从 OTA 提取分区镜像payload-dumper-go、payload_dumperboot.img、system.img、vbmeta.img 等镜像格式识别与转换file、simg2img、lpunpack、lpdump可挂载的 raw ext4/erofs 镜像解包、修改、重打包loop mount、magiskboot、make_ext4fs、mke2fs修改后的新镜像刷机与验证fastboot、avbtool能正常开机的设备注意第四段不是终点刷完还要验证尤其是 AVB 校验状态。我后面会专门讲很多人在第三段折腾半天结果卡在第四段的vbmeta关闭校验上。2. payload.bin 提取镜像整包最重的一步其实只是开始2.1 payload.bin 是什么为什么不能直接解压厂商发布的完整 OTA 包通常是一个 zip里面除了META-INF、payload_properties.txt真正的大头是一个叫payload.bin的二进制文件。这个文件不是普通 zip 里的镜像,而是 Google 的 update_engine 生成的增量/全量镜像格式文件头部固定是crAU四个字符。它内部用 protobuf 记录了一个 manifest,包含分区名、大小、哈希、数据块位置等信息后面跟着一串数据块。很多人第一次拿到 payload.bin习惯性用解压软件去解解出来一堆没头没尾的二进制自然一脸懵。正确的姿势是专门用工具去解析 manifest把里面的分区镜像一个个还原出来。另外要特别留意全量包和增量包的区别。全量包包含完整分区数据提取出来就能用增量包只包含两个版本之间的差异块要基于指定的旧版本 base 包才能还原乱提取会出现分区不完整或者直接报错。判断方法很简单增量包一般体积小而且 payload 提取工具会提示需要 base 版本。2.2 提取工具选型我为什么长期用 Go 版提取 payload 的工具不少我用过的有三类工具语言特点适用场景payload-dumper-goGo并发提取速度快对存量/增量包兼容好日常主力extract_android_ota_payloadPython功能全能做增量还原但单线程慢分析 manifest、增量测试payload_dumper旧版Python实现简单只支持全量包老教程常见不推荐我自己长期用 Go 版核心原因是速度。一个三四十 GB 的全量包旧的 Python 版可能要跑半小时Go 版并发读分区数据块几分钟就能提取完。而且它对新一点的分区命名兼容更好比如vendor_boot、vbmeta_system这些最近几代安卓才有的分区老工具经常不识别。2.3 提取实操全量提取和按需提取假设我已经拿到一个全量 OTA 包ota.zip先把 payload.bin 解出来也可以直接让工具识别 zip 内路径。以我常用的方式为例# 解出完整 OTA zip unzip ota.zip -d ./ota # 提取所有分区镜像 payload-dumper-go -o ./extracted ./ota/payload.bin # 只要 boot 和 vendor_boot省时间省磁盘 payload-dumper-go -o ./extracted_boot -p boot,vendor_boot ./ota/payload.bin不同版本的参数名可能略有差异不确定时先跑一下payload-dumper-go --help以实际输出为准。按需提取这个习惯特别有用有时候我只想做个 root几百 MB 的 boot 提取出来就够了完全没必要把整个 system 拉下来占几十 GB 磁盘。提取完先别急着走打开payload_properties.txt里面有FILE_HASH等校验信息花一分钟核对一下提取出来的镜像大小和哈希能省掉后面一堆莫名其妙的问题。这类文件下载损坏是家常便饭我现在每次都先做这一步。2.4 提取阶段的常见报错报 invalid payload / metadata 校验失败多半是增量包没有配 base或者文件下载不完整。先重新下载别急着换工具。提取过程中 crc 校验失败大概率是磁盘坏块或者传输损坏重下再试。磁盘空间不足全量包提取通常需要 10GB 到 30GB 以上提前留足空间。这也是我推荐按需提取的原因之一。3. 镜像格式识别与转换sparse、ext4、erofs、super别急着挂载3.1 先看格式再决定工具file 命令是第一步从 payload 里提取出来的镜像文件名都叫.img但内部格式五花八门。我见过太多人拿到system.img直接mount -o loop结果报错 wrong fs type原因就是没先识别格式。拿到任意一个镜像第一件事永远是跑filefile system.img常见的输出大概有这几种file 输出特征真实格式该用什么工具Android sparse image, version: 1.0sparse 稀疏镜像simg2img 转 rawLinux rev 1.0 ext4 filesystem dataraw ext4 镜像直接 loop 挂载erofs filesystemerofs 只读镜像dump.erofs / fsck.erofs 查看data有时是乱码可能是 super 容器、boot header 或加密镜像lpdump 或其他专用工具继续判断千万别跳过这步。识别错了工具后面每一步都是白费力气。3.2 sparse 镜像的结构以及为什么不能直接挂载sparse 是 Android 特有的稀疏镜像格式设计目的是减少传输大小、跳过全零块。文件头 magic 是0xED26FF3A你在 hexdump 里会看到前几个字节是3a ff 26 ed。它内部按 chunk 组织常见四种 chunkRAW 块带原始数据FILL 块表示重复填充DONT_CARE 块表示这些区域全是空洞可以直接跳过。因为 sparse 不是真实磁盘文件系统布局,mount自然不认。需要先用simg2img转成 raw ext4simg2img system_sparse.img system_raw.img file system_raw.img这才是真正可挂载的镜像。反过来如果你想把修改完的 raw 镜像刷回设备或者给 fastboot 用可以再用img2simg打回 sparseimg2simg system_raw.img system_new_sparse.img我个人的习惯是凡是解包打包流程内用的镜像一律用 raw 格式凡是最后要刷机的镜像再考虑是否转 sparse。fastboot 对 raw 和 sparse 都认识但有些小工具只认其中一种统一用 raw 做中间格式最保险。3.3 super 动态分区别再当普通镜像挂载了Android 10 开始系统引入了动态分区把 system、vendor、product、odm 这些只读分区全部装进一个叫super的分区里实际分区表里只有一个 super逻辑分区由 userspace 的 dm 机制映射出来。所以提取出super.img之后你没法直接挂载它看到 system 里的文件得先用lpdump看再用lpunpack拆# 查看 super 里有那些逻辑分区 lpdump --slot 0 super.img # 解出所有逻辑分区镜像 lpunpack super.img ./extracted_logical拆出来的system.img、vendor.img可能还是 sparse 格式再走一遍simg2img。这个多套一层的操作容易让人懵但理解了动态分区机制就很自然super 是物理容器逻辑分区才是真正操作对象。3.4 一个完整的入口转换流程把从 OTA 到可挂载镜像的流程串起来大概是这样的payload-dumper-go -o ./mnt_exports payload.bin file ./mnt_exports/super.img lpdump --slot 0 ./mnt_exports/super.img lpunpack ./mnt_exports/super.img ./logs simg2img ./logs/system.img ./logs/system_raw.img file ./logs/system_raw.img到这一步你已经拿到了能直接修改的 raw 镜像。接下来改哪个分区取决于你的目标。4. boot.img 的解包、修补与重打包Magisk 只是其中一个分支4.1 boot image 的结构以及 v3/v4 带来的变化boot.img 和 system.img 完全不同它不是文件系统镜像而是一个固定头部加数据块的组合包头部记录内核大小、ramdisk 大小、页大小等后面依次是内核kernel、ramdisk、dtb 等。Android 10 之后 boot header 进入 v3/v4头部更精简ramdisk 和内核还是照样打包在一起。v3/v4 还引入了vendor_boot分区厂商的 ramdisk、dtb 挪到了 vendor_boot 里boot.img 只保留内核和一部分通用 ramdisk。这意味着修改 boot 时不一定只改 boot.img 就够很多设备需要连同 vendor_boot 一起处理。我就是在这个地方翻过车只刷了 boot开不了机后来发现关键 ramdisk 还在 vendor_boot 里。4.2 magiskboot 的基本操作unpack / repack处理 boot 镜像最顺手的工具是 Magisk 自带的magiskboot它认识各个版本的 boot header而且能自动处理打包对齐。核心就两个命令# 解包 magiskboot unpack boot.img # 解包后目录里会出现 kernel、ramdisk.cpio 等文件 # 修改完成后重打包 magiskboot repack boot.img # 生成 new-boot.imgunpack之后如果你想检查 ramdisk 内容可以用magiskboot cpio子命令magiskboot cpio ramdisk.cpio test magiskboot cpio ramdisk.cpio extract inittest可以列出 cpio 里的文件清单extract可以单独抽出某个文件方便你确认 ramdisk 里到底有什么。这套逻辑很像把 zip 拆开再装回去但 boot 分区对对齐和头部参数要求更严格手工拼很容易翻车用 magiskboot 替你处理这些细节最稳。4.3 换内核和改 ramdisk 的正确姿势换内核是最常见的操作把解出来的kernel文件直接覆盖成你的新内核然后 repack刷入。注意保持文件权限和内核格式有的内核是Image.gz-dtb有的带 dtb 分离打包先看清楚再动。改 ramdisk 稍微复杂。ramdisk 本质是 cpio 归档里面的 init 脚本、rc 文件、属性配置都可能影响开机。magiskboot cpio 支持直接增减文件magiskboot cpio ramdisk.cpio add 0750 init.extra ./myinit文件权限必须写对ramdisk 里权限和内容错了最常见的结果就是开机卡在 logologcat 都来不及抓。所以我的建议是每一步修改都尽量小步验证改完一个文件先刷一次确认没问题再改下一个。4.4 Magisk 修补的本质就是重打包很多人用 Magisk App 里的修补 boot 镜像功能生成一个magisk_patched-xxx.img其实本质就是 magiskboot 解包、修改 ramdisk把 Magisk 的代码注入进去、再重打包。知道这一点之后如果你只想在官方 boot 基础上 root完全不用自己命令行操作App 妥妥够用。但如果你要同时换内核又要 root或者要往 ramdisk 里加自己的东西那就必须手动走 magiskboot 流程。刷完 boot 之后可以通过这几个命令验证状态adb shell getprop ro.boot.verifiedbootstate adb shell getprop ro.boot.slot_suffix第一个属性会显示验证状态第二个能看到当前启动的是 a 槽还是 b 槽。如果设备开了 AVB修改后的 boot 会让验证状态变成orange甚至直接无法启动这个问题下一章专门讲。5. system/vendor 等只读分区的解包、修改与重打包5.1 解包 raw ext4 镜像最稳的还是 loop 挂载拿到 raw 格式的 system 镜像我开始用的是 7z 直接解压它能读出一部分 ext4 内容但符号链接、特殊权限、selinux 标签都会丢恢复不回去。后来我改用 Linux 的 loop 挂载稳定太多sudo mkdir /mnt/system sudo mount -o loop system_raw.img /mnt/system # 直接查看/修改挂载点里的文件 sudo cp -a /mnt/system ./system_tree sudo umount /mnt/system需要提醒的是挂载修改最好在原镜像副本上操作别直接改原始提取文件。一旦改崩了重新提取一次分分钟想哭。5.2 预装 APK、去预装、改 build.prop 的具体操作这几个需求都不复杂关键是放对位置加预装 APK普通应用放system/app/应用名/应用名.apk需要系统权限的应用放system/priv-app/目录和 apk 权限设置成 755 和 644。去预装直接删掉对应目录即可最好先确认包名别删到系统关键组件。改 build.prop注意保持行尾是 LF别用 Windows 记事本改成 CRLF不然很多属性解析会出问题。改完可以先在挂载点里 grep 一遍确认。这部分本身不难真正难的是重打包时把文件 owner 和 selinux 上下文带回去这才是重打包里面最容易被忽略的坑。5.3 重打包成 ext4 的三种思路对比我试过三种重打包思路各有优劣方法操作优点缺点解树后重新 make_ext4fs解出完整文件树重新生成镜像能调整分区大小owner/selinux context 容易丢直接挂载原镜像覆盖文件挂载 raw 镜像按需增删改文件umountowner/context 几乎不变无法调整分区大小e2fsdroid file_contexts 重打用原 file_contexts 重新打 tag 打包最接近厂商打包流程需要特定工具链和 context 文件我的实战经验是大多数场景下方法二最省心。你只需要挂载镜像把 APK 复制进去、删掉不要的目录、改好 build.prop最后 umount镜像就已经是修改好的成品。这个过程中文件 owner 和 selinux 标签都会保留原有的不会出现打包后系统服务起不来、应用疯狂闪退的惨剧。如果你确实需要调整分区大小或者修改后空间不够了那才需要走企业级打包流程用mke2fs创建新镜像、e2fsdroid -S file_contexts打标签、再拷贝回来。这需要目标设备固件里带 file_contexts 文件不同厂商差异很大。5.4 erofs 只读镜像怎么处理新一点的安卓设备system/vendor 大量使用 erofs 格式。它比 ext4 压缩率高、性能好但它是只读文件系统没法像 ext4 那样直接挂载写。处理方式有两种用dump.erofs、fsck.erofs把内容导出修改后用mkfs.erofs重新打包。不碰只读分区把修改内容放到 Magisk 模块的 overlay 里走/system动态覆盖。对绝大多数定制需求第二种方案性价比高得多。厂商的只读分区能不动就不动通过 Magisk 模块实现预装 APK、改属性升级 OTA 也不会被冲掉反而更优雅。5.5 打包回 sparse 和镜像瘦身修改完的 raw 镜像如果要刷机注意观察大小。如果原分区只有 4GB你重打出来 4.5GB刷进去必然报空间不足。解决办法要么删掉一些不必要的文件要么把 raw 镜像转成 sparse 格式利用稀疏特性跳过空洞img2simg system_raw.img system_new_sparse.imgsparse 刷写时 fastboot 会按 chunk 写入不存在的块直接跳过所以实际传输量远小于 raw 镜像体积。刷机前用du -h看一眼实际大小比看镜像逻辑大小更靠谱。6. 重打包后的刷机与验证fastboot 坑、AVB 校验与最常翻车的四个环节6.1 fastboot 刷写要注意 A/B 槽和动态分区现在的新设备基本都是 A/B 无缝升级boot、system、vendor 这些分区都有_a和_b后缀。很多人的噩梦是从这里开始的刷写了 boot但设备从另一个槽启动自然还是旧系统。用 fastboot 刷写时默认操作的是当前活跃槽。如果之前手动切过槽最好显式指定fastboot flash boot_a new-boot.img fastboot flash vendor_boot_a new-vendor_boot.img动态分区设备还有一个特性你直接fastboot flash systemflash 到的是当前槽位的逻辑分区这没问题但如果你无聊地fastboot erase super整个逻辑分区表就没了。这是真正的灾难级操作没事绝对不要去 erase super。万一真发生了需要用厂商的 empty super 镜像恢复逻辑分区表那套流程非常痛苦。6.2 AVB 校验机制以及为什么改了镜像会卡在验证阶段从 Android 7 开始引入 Verified Boot后来演变成 AVBAndroid Verified Boot。设备启动时 bootloader 会校验 boot、system、vendor、vbmeta 这些分区的哈希。你改了任何一个被 AVB 保护的分区校验就过不了轻则启动到 recovery 提示状态异常重则直接卡死在开机 logo。处理手段就是修改 vbmeta 分区的验证标志fastboot flash vbmeta vbmeta.img --disable-verity --disable-verification这条命令会把 vbmeta 里的 verity 和 verification 标志清掉让 bootloader 不再对链上分区做严格哈希校验。对新一点的设备如果改了 vendor_boot 或 vbmeta_system可能还需要一并处理fastboot flash vbmeta_system vbmeta_system.img --disable-verity --disable-verification这里必须说清楚关闭校验只适合个人定制和调试场景相当于把自己的设备置于不验证启动镜像来源的状态。我对这个操作的态度是自己玩没问题但别拿别人的固件乱刷也别把关闭校验后的设备当主力机安全边界你自己要清楚。刷完以后验证一下adb shell getprop ro.boot.verifiedbootstate # 正常会输出 green / yellow / orangegreen代表完全验证通过orange代表 bootloader 检测到镜像被修改但允许启动。看到orange别慌说明 AVB 关闭生效了。6.3 最常翻车的四个环节以及对应的排查链路这四类问题几乎覆盖了 90% 的翻车现场每个我都踩过第一类直接挂载 sparse 镜像。现象mount -o loop system.img报wrong fs type。 排查file system.img发现是 Android sparse image。 解决simg2img转 raw 再挂载。这问题最基础但永远有人犯。第二类改完 boot 无限重启。现象刷入新 boot 后设备不断重启或卡在 logo。 排查先用fastboot flash boot刷回原版 boot如果能开机说明问题在 boot 内容如果还是重启再看 vbmeta 有没有关闭校验。 解决关掉 vbmeta 里的 verity/verification再刷修改过的 boot。第三类解树重打包后应用疯狂闪退。现象系统能开机但大量应用打不开logcat 里全是 selinux denial。 排查十有八九是重打包时文件 owner 全部变成 rootselinux context 变成u:object_r:unlabeled。 解决不要再走解树重打改用挂载原镜像直接覆盖文件的方式如果必须重打备齐 file_contexts 用 e2fsdroid 打标签。第四类super 分区被误操作设备彻底没系统。现象开机进 fastboot 模式或者卡在 bootloader所有分区列表消失。 排查大概率是执行过fastboot erase super或刷错了 super 镜像。 解决这已经不是单分区刷写能救回来的需要从厂商工具里恢复超级分区表。所以我反复强调super 只做读取和解包不要乱刷。6.4 想把这个修改后的镜像做成 OTA 包先掂量一下如果你做定制只是为了自己用fastboot 线刷足够了。但如果你想把修改后的 ROM 分发给别人做成卡刷 OTA 包就是另一套工程量了。正规流程是这样的把所有分区镜像整理成 target_files 目录用ota_from_target_files脚本生成新的 OTA 包同时还要有对应的签名 key。这一套涉及厂商签名、增量包生成逻辑复杂度比解包打包高一个量级。作为个人博主我的建议是先别碰 OTA 重打包把 fastboot 线刷玩熟了就足够覆盖 95% 的定制需求。真要深入研究等你对分区、AVB、签名机制有完整理解之后再上不迟。做了这么多轮解包打包我自己最大的体会是流程越长越要克制动手的欲望。每到一个环节先停下来想一个问题——这一步我到底在改什么这个改动会不会触发校验改坏了能不能刷回去很多人翻车不是因为不会用工具而是因为改得太急跳过了验证步骤。另外尽量别走解树重打的路线能用挂载直接覆盖解决的就不要让 selinux context 和 owner 权限背锅。最后再分享一个小习惯每次刷机前先把原版 boot、vbmeta、system 备份到电脑上。真出问题的时候这三件套就是你的后悔药。