☰
Android system.img解包重打包工具链实战指南
2026/10/16 8:14:32 网站建设 项目流程

简介:本资源是一套开箱即用的Android系统镜像处理工具集,专为Android系统开发者、ROM定制者及嵌入式调试工程师设计,解决系统镜像解包、打包、稀疏格式转换与ext4文件系统构建等核心需求。压缩包共含4个已编译可执行文件:img2simg(.img转.simg稀疏镜像)、simg2img(反向转换)、make_ext4fs(生成ext4分区镜像)及mkuserimg.sh(整合用户镜像的Shell脚本),覆盖从单一分区操作到完整系统镜像组装的全流程。包体仅136KB,轻量高效,无需额外编译或依赖环境,适配主流Linux平台。目前已有2951人学习下载,是快速上手Android底层镜像分析与定制的实用型工具包,尤其适合需要反复验证分区结构、调试刷机流程或逆向分析厂商固件的技术人员。

1. 这不是“安卓刷机包解包神器”,而是一套被反复验证过的、能真正跑通从 system.img 解包到重打包全流程的底层工具链

你是不是也试过:下载一个号称“一键解包”的GUI工具,双击运行后弹出报错libstdc++.so.6: version GLIBCXX_3.4.29 not found;或者用某脚本把system.img拆成目录,改完build.prop后却卡在make_ext4fs报Invalid argument,查半天发现是 block size 不匹配;又或者simg2img转出来的镜像用mount -o loop挂载失败,dmesg里只有一行VFS: Can't find ext4 filesystem—— 其实根本不是文件系统损坏,而是你手里的simg2img版本压根不支持 Android 12+ 引入的sparse format v2。这套工具集不是玩具,它包含四个经过真实产线验证的二进制:img2simg(将普通 ext4 镜像转为 sparse 格式)、make_ext4fs(生成标准 ext4 镜像)、mkuserimg.sh(Android 官方推荐的用户镜像构建封装脚本)、simg2img(将 sparse 镜像还原为原始 ext4)。它们全部来自 AOSP 某个稳定分支的预编译产物,静态链接,无依赖,x86_64 Linux 下开箱即用。适合正在调试 OTA 升级逻辑、定制 recovery 行为、或需要离线分析 OEM 系统镜像的固件工程师、安全研究员和 ROM 开发者。如果你还在用e2fsdroid或自己编译make_ext4fs,那这组二进制就是你省下三小时编译+排错时间的后悔药。

2. 工具链功能边界与选型依据:为什么必须同时存在 img2simg、make_ext4fs 和 mkuserimg.sh?

2.1 四个工具的本质分工:从“数据容器”到“可启动镜像”的分层构建逻辑

Android 系统镜像不是单一格式,而是一套分层结构:最底层是块设备抽象(sparse vs raw),中间是文件系统格式(ext4),上层是 Android 特有的元数据封装(如android_sparse_header、ext4_file_header、verity签名区)。simg2img和img2simg只负责最底层的 sparse ↔ raw 转换,不碰文件系统内容;make_ext4fs负责在 raw 块设备上格式化并写入 ext4 文件系统,但不处理 Android 的 sparse 封装;mkuserimg.sh则是 Google 官方提供的“胶水脚本”,它内部调用make_ext4fs生成 raw 镜像,再调用img2simg封装为 sparse 格式,并可选加入verity、avb等签名头。四者缺一不可——就像你不能只靠dd和mkfs.ext4就声称能生成合规的system.img。我见过太多人用make_ext4fs -s直接生成 sparse 镜像,结果烧录后 bootloop,原因就是-s参数仅做简单封装,不写入android_sparse_header中必需的file_hdr_sz和chunk_hdr_sz字段,导致 bootloader 解析失败。

2.2 版本兼容性实测:AOSP 11~14 对应的工具链行为差异

不同 Android 大版本对镜像格式有隐性要求。我们用同一份out/target/product/generic_x86_64/obj/PACKAGING/systemimage_intermediates/system.img(AOSP 13 编译产出)做反向验证:

Android 版本推荐 simg2img 版本是否支持 v2 sparsemkuserimg.sh 调用 make_ext4fs 方式关键参数差异
AOSP 11prebuilts/misc/linux-x86/simg2img否(v1 only)make_ext4fs -l $SIZE ...-l指定逻辑大小,不预留 reserve blocks
AOSP 12platform/system/extras是(v2 header)make_ext4fs -s -l $SIZE ...-s启用 sparse 封装,但需配合img2simg才完整
AOSP 13+platform/system/core/libsparse是(v2 + chunk checksum)mkuserimg.sh ... ext4 ...必须用mkuserimg.sh,因make_ext4fs已移除-s参数

提示:本工具集中的simg2img来自platform/system/core/libsparse的 2023-Q3 commit,已通过sparse_read_file()对 v2 header 的chunk_checksum_algorithm字段校验,可安全处理 Pixel 7(Android 13)及后续机型的system.img。

2.3 为什么不用e2fsdroid?一次真实的 OTA 签名校验翻车记录

某次为某跨平台系统适配 OTA,开发团队坚持用e2fsdroid(Android 早期工具)生成system.img,理由是“代码开源好调试”。结果在update_engine验证阶段失败,日志显示Failed to verify payload hash。抓包发现e2fsdroid生成的 ext4 superblock 中s_feature_ro_compat位未置EXT4_FEATURE_RO_COMPAT_VERITY,而update_engine的PayloadVerifier强制要求该位开启(即使未启用 verity)。而本工具集中的make_ext4fs来自 AOSP 13,其ext4_create_fs()函数在ext4_super_block初始化时会根据-R参数自动设置s_feature_ro_compat,且mkuserimg.sh默认传入-R。这不是玄学,是 Google 在system/extras/ext4_utils/Makefile里写死的行为契约。

3. 实战:从 unpack 到 repack 的完整闭环操作(含参数详解与命令链)

3.1 解包:simg2img → mount → 提取文件系统内容

第一步永远是确认输入镜像类型。别急着simg2img,先用file命令看本质:

$ file system.img system.img: Android sparse image, version: 2.0, File size: 3221225472, Block size: 4096, Total blocks: 786432, Total chunks: 128

如果输出含Android sparse image,说明是 sparse 格式,必须先转 raw:

$ ./simg2img system.img system_raw.img # 成功时无输出,失败则报错如 "Invalid sparse file format at header"(说明是 v1 或损坏)

逻辑说明:simg2img读取 sparse header,按chunk_hdr类型(CHUNK_TYPE_RAW/CHUNK_TYPE_FILL/CHUNK_TYPE_DONT_CARE)顺序将数据块拼接为连续 raw 镜像。system_raw.img大小 =Block size × Total blocks(此处为 4096 × 786432 = 3.2GB),而非原始system.img的 1.2GB。

接着挂载 raw 镜像提取内容:

$ sudo mkdir -p /mnt/system $ sudo mount -t ext4 -o loop,ro system_raw.img /mnt/system # 注意:-o ro 是必须的!避免误写导致镜像损坏 $ ls /mnt/system bin build.prop etc framework lib priv-app system_ext vendor

参数说明:-o loop启用回环设备;-o ro强制只读,防止mount过程中 ext4 自动写入 journal 导致system_raw.img被修改;-t ext4明确指定文件系统类型,避免mount自动探测失败。

3.2 修改与重建:mkuserimg.sh 封装流程与关键参数控制

假设你已修改/mnt/system/build.prop并卸载:

$ sudo umount /mnt/system

现在进入重建阶段。不要直接用make_ext4fs,必须走mkuserimg.sh流程:

$ ./mkuserimg.sh \ -s \ # 启用 sparse 输出(生成 .img 而非 .raw) -v \ # 输出详细日志(排错必备) --verity \ # 启用 dm-verity(OTA 必需) --avb \ # 启用 AVB 2.0(Pixel 等设备必需) /mnt/system \ # 源目录(注意:必须是已卸载的挂载点,且无 open fd) system_new.img \ # 输出镜像路径 ext4 \ # 文件系统类型 system \ # 分区名(影响 fstab 解析) 3221225472 # 总大小(字节),必须等于原镜像 Block size × Total blocks

逻辑说明:mkuserimg.sh内部执行三步:① 调用make_ext4fs -l 3221225472 ...生成临时 raw 镜像;② 若--verity,调用system/extras/verity/apply_verity.sh注入 verity root hash;③ 若--avb,调用external/avb/avbtool添加 vbmeta footer。最后调用img2simg封装为 sparse 格式。

参数说明:-s是开关,决定最终输出是否为 sparse;--verity和--avb是布尔开关,不带值;3221225472必须精确,差 1 字节都会导致make_ext4fs报Invalid argument(因为 ext4 要求 size 是 block size 的整数倍)。

3.3 验证:用 adb reboot fastboot + fastboot flash system 验证可启动性

生成system_new.img后,别急着刷机,先本地验证镜像结构:

$ ./simg2img system_new.img system_new_raw.img 2>/dev/null $ file system_new_raw.img system_new_raw.img: Linux rev 1.0 ext4 filesystem data, UUID=..., volume name "system" ... $ sudo fsck.ext4 -n system_new_raw.img e2fsck 1.46.5 (30-Dec-2021) system_new_raw.img: clean, 12345/2097152 files, 678901/8388608 blocks

逻辑说明:fsck.ext4 -n是只读检查,输出clean表示文件系统无错误;UUID和volume name应与原镜像一致(mkuserimg.sh会继承源目录的fstab配置)。

最后推送到设备验证:

$ adb reboot fastboot $ fastboot flash system system_new.img $ fastboot reboot # 观察串口 logcat,重点看 init 进程是否成功加载 /system/bin/sh

4. 避坑:五个血泪经验总结的高频报错与根因定位

4.1 现象:simg2img报错Invalid sparse file format at header

原因:输入文件并非 sparse 格式,而是 raw ext4 镜像(file命令显示Linux ext4 filesystem data),或 sparse header 被破坏(如用dd截断了前 24 字节)。
解决:先file system.img确认格式;若为 raw,跳过simg2img直接mount -o loop;若 header 损坏,尝试用hexdump -C system.img | head -20查看前 24 字节是否为0x45 0x46 0x49 0x53 0x00 0x00 0x00 0x01(sparse magic + v1),或0x45 0x46 0x49 0x53 0x00 0x00 0x00 0x02(v2)。

4.2 现象:mkuserimg.sh报错make_ext4fs: Invalid argument

原因:传入的 size 参数(如3221225472)不是 block size(通常 4096)的整数倍,或源目录/mnt/system仍有进程占用(如ls未退出)。
解决:用echo $((3221225472 % 4096))验证余数为 0;用lsof +D /mnt/system检查并kill占用进程;确保umount后无残留。

4.3 现象:fastboot flash system成功,但设备 bootloop,串口 log 显示init: Failed to mount '/system'

原因:mkuserimg.sh未传--verity,但设备 fstab 中system分区配置了verifyflag(如system /system ext4 ro,barrier=1,discard wait,verify),导致 init 尝试挂载 verity 设备失败。
解决:检查设备/vendor/etc/fstab.*或/odm/etc/fstab.*中system行的 flags;若含verify,重建时必须加--verity;若不含,则检查system_new.img的s_feature_ro_compat位:sudo dumpe2fs -h system_new_raw.img | grep "Filesystem features"应含verity。

4.4 现象:mount -o loop system_raw.img /mnt/system报错wrong fs type, bad option, bad superblock

原因:system_raw.img实际是 ext4,但内核未加载 ext4 模块,或镜像被simg2img截断(如输入 sparse 镜像末尾有 padding)。
解决:sudo modprobe ext4加载模块;用stat system_raw.img确认大小是否等于Block size × Total blocks;若不等,用truncate -s $(($BLOCK_SIZE * $TOTAL_BLOCKS)) system_raw.img修复。

4.5 现象:mkuserimg.sh生成的system_new.img比原镜像大 1MB,OTA 升级失败

原因:mkuserimg.sh默认启用AVB时会添加vbmetafooter(约 64KB),且make_ext4fs的reserved blocks percentage(默认 0.5%)计算方式与原镜像不同。
解决:用--no-avb禁用 AVB;或用make_ext4fs -M 0(-M指定 reserved blocks 数量为 0)替代mkuserimg.sh,但需自行处理 verity;更稳妥的是用diff -u <(du -b original.img) <(du -b system_new.img)确认增量来源。

5. 进阶技巧:如何用这组工具链逆向分析 OEM 系统镜像的分区策略与签名机制

5.1 从 system.img 提取 fstab 与 avb_pubkey,定位 OEM 定制点

OEM 厂商常在system.img中嵌入私有fstab和avb_pubkey,这是 OTA 适配的关键。我们用工具链直接提取:

# 步骤1:解包 system.img 得到 raw 镜像 $ ./simg2img system_oem.img system_oem_raw.img # 步骤2:挂载并查找 fstab $ sudo mkdir -p /mnt/oem $ sudo mount -o loop,ro system_oem_raw.img /mnt/oem $ find /mnt/oem -name "fstab*" -type f 2>/dev/null /mnt/oem/etc/fstab.qcom /mnt/oem/vendor/etc/fstab.sony # 步骤3:提取 avb_pubkey(通常在 /vbmeta.img 或 /system/etc/avb/) $ find /mnt/oem -name "avb_pubkey*" -o -name "vbmeta.img" 2>/dev/null /mnt/oem/vbmeta.img $ sudo umount /mnt/oem # 步骤4:用 avbtool 解析 vbmeta(需额外安装 avbtool) $ avbtool info_image --image vbmeta.img Minimum libavb version: 1.0 Header Block: 256 bytes Authentication Block: 512 bytes Auxiliary Block: 1024 bytes Rollback Index: 0 Flags: 0 Release String: 'avbtool 1.2.0' Descriptors: Hash descriptor: Image Size: 3221225472 bytes Hash Algorithm: sha256 Partition Name: system Salt: a1b2c3... Digest: d4e5f6...

逻辑说明:fstab.qcom中system行的avbflag 值(如avb,avb_hashtree,avb_hash)决定了init加载 verity 的方式;avbtool info_image输出的Digest是system.img的 root hash,可用于生成 OTA payload 的hash_tree。

5.2 构建最小可启动镜像:剔除 vendor_dlkm 等非必要模块的瘦身实践

某些 OEM 镜像体积膨胀源于vendor_dlkm(Dynamic Loadable Kernel Module)等目录。我们用mkuserimg.sh的-d参数精准控制:

# 创建白名单列表(只保留必要目录) $ cat > include_list.txt << 'EOF' bin build.prop etc framework lib priv-app system_ext vendor EOF # 用 rsync 按白名单同步(比 cp 更安全) $ rsync -a --delete --files-from=include_list.txt /mnt/oem/ /tmp/system_min/ # 重建镜像(指定更小的 size) $ ./mkuserimg.sh -s -v /tmp/system_min system_min.img ext4 system 2147483648

参数说明:2147483648 = 2GB,比原 3.2GB 小;mkuserimg.sh会自动计算实际所需 blocks,若指定 size 过小会报错Not enough space,此时需增大 size 或删减include_list.txt。

5.3 验证 OTA payload 兼容性:用 payload_generator.py 生成测试 payload

真正的落地价值在于 OTA。我们用 Google 官方payload_generator.py(需 Python 3.8+)验证镜像是否符合 payload 规范:

# 下载 payload_generator.py(来自 AOSP platform/tools/releasetools) $ wget https://android.googlesource.com/platform/tools/releasetools/+/refs/heads/master/payload_generator.py?format=TEXT # 生成 delta payload(old.img → new.img) $ python3 payload_generator.py \ --old_image system_oem.img \ --new_image system_min.img \ --output_payload payload_test.bin \ --block_size 4096 # 验证 payload 结构 $ python3 -c " import sys sys.path.insert(0, '.') from payload_generator import Payload p = Payload('payload_test.bin') print(f'Payload version: {p.manifest.version}') print(f'System partition: {p.manifest.partitions[0].partition_name}') "

逻辑说明:payload_generator.py会调用bsdiff计算二进制差异,并写入manifest;若system_oem.img和system_min.img的sparse header或ext4 superblock不兼容(如s_log_block_size不同),payload_generator.py会直接退出并报Incompatible images。

从那以后我每次拿到新 OEM 镜像,第一件事就是file看格式、simg2img转 raw、mount -o ro提取 fstab,绝不跳过这三步。因为 90% 的 OTA 失败,根源都在system.img的底层格式没吃透。这套工具链不是万能钥匙,但它把sparse ↔ raw、ext4 生成、Android 封装这三层黑匣子全摊开给你看——参数错了,报错明明白白;逻辑断了,日志清清楚楚。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询