简介:这是一款面向 Android 系统底层定制与 ROM 开发场景的 SYSTEM.IMG 解包/打包工具,主要服务于需要修改系统应用、权限配置或内核组件的开发者与高级用户。资源包共 245 个文件,压缩后约 78.72MB;文件以 XML 配置、EXE 可执行程序、DLL 动态库为主,另含 shell 脚本、JAR 工具、busybox 及 update-binary 等组件,便于在 Windows 或类 Unix 环境中完成镜像解包、内容修改与重新打包。已有 504 人浏览学习。借助该工具,用户可提取 SYSTEM.IMG 内部结构,对系统 App、服务或脚本进行替换和调优,再重新生成镜像并刷入设备,适合用于制作自定义 ROM、精简预装应用或修复系统问题。资源同时附带必要的运行库与脚本说明,可降低环境配置门槛,但需具备基本命令行和 Android 分区知识,操作前建议备份原镜像以避免设备异常。
1. SYSTEM.IMG到底是什么?先搞懂你手里拿的是什么东西
做ROM定制、系统精简或者固件逆向的人,对SYSTEM.IMG这个名字应该都不陌生。它是Android系统分区镜像里最关键的一个,系统应用、框架服务、系统库全部装在里面,相当于整个操作系统的C盘。刷机包里那一大堆IMG文件,system、boot、vendor、product各管一摊,而SYSTEM.IMG就是最常被折腾的那一个。
解包和打包SYSTEM.IMG,说白了就是把这个镜像文件拆开,改里面的东西,再重新封装回去。常见的需求无非这么几类:精简系统预装应用、替换系统字体和壁纸、注入Root或Magisk模块、定制开机动画、把一个ROM从A设备移植到B设备。折腾过刷机的人应该都有体会,有时候Google官方镜像或者第三方ROM包里的系统分区有我们不需要的App,直接删掉能省不少空间,也能让系统跑得更清爽。还有一类人做移植,需要把另一个型号设备的驱动和系统文件搬进来,不改SYSTEM.IMG根本没法干活。
但这里有个容易劝退新手的点:SYSTEM.IMG不是一种固定格式的文件。不同厂商、不同Android版本、不同打包方式,出来的镜像格式可能完全不同。常见的有raw ext4镜像、sparse稀疏镜像、erofs只读压缩镜像,还有Android 10开始Google强推的动态分区格式(super.img里套着system)。格式不对,工具用错,轻则解不出来,重则解包后打包回去刷进去直接变砖。
所以第一个建议是:拿到一个SYSTEM.IMG,先别急着找工具乱解,花一分钟搞清楚它的格式。怎么判断?Linux下直接执行file system.img,Windows下用Hex编辑器看文件头。raw ext4的镜像开头是0x53 0xEF(ext4超级块的魔数),sparse镜像开头是0x3A 0xFF 0x26 0xED(Android sparse magic),erofs的开头特征则是0xE2 0xE1 0xF5 0xE0。格式识别对了,后面的操作基本就顺了。识别错了,解出来一堆乱码数据,打包回去刷进去无限重启,那才叫真的头疼。
这篇文章我就把你需要知道的SYSTEM.IMG解包、打包完整流程捋一遍,从环境准备、工具选择、实操命令到各种坑,尽量一次说透。
2. 环境准备:Linux才是玩镜像的主场
有了上面的基础,接下来第一步就是准备一个可用的操作环境。这一步看起来基础,但其实很多人卡在这里。实话实说,解包和打包SYSTEM.IMG这件事,主战场是Linux,其次是macOS,Windows只能算勉强能用。不是Windows不能做,而是各种工具链交叉编译、权限管理、文件系统支持在Windows上都是麻烦事,光一个make_ext4fs在Windows下就够你折腾半天的。
如果你手上只有Windows电脑,我建议两条路选一条:装个WSL2(Windows Subsystem for Linux),或者直接VirtualBox装一个Ubuntu 22.04 LTS。我个人更推荐WSL2,理由很简单:文件系统互通方便,镜像文件放在Windows目录下,WSL里直接/mnt/c/就能访问,不用来回拷贝。实测下来(我在WSL2 Ubuntu下做了大量解包打包操作),性能损失完全可以忽略,对于1~2GB的system.img来说,读写速度非常理想。不过需要留意的是,WSL2在访问Windows NTFS路径时性能明显偏低,建议把镜像实际放在WSL2的ext4文件系统内(比如~目录),收到Windows侧目录下先拷贝过去再操作。
主流的工具链有这么几个,按用途分类:
- simg2img / img2simg(Android自带工具):转换sparse镜像和raw镜像,属于Android源码
system/core/libsparse的一部分,网上能找到编译好的独立二进制。 - make_ext4fs / mkfs.ext4:打包ext4格式的system镜像。前者是Android老牌打包工具,后者是Linux原生工具,两者可以互相替代但使用上有细微差别。
- erofs-utils:处理erofs镜像的工具包,包含
mkfs.erofs和fsck.erofs。新机型(Android 12+)大量使用erofs分区,这套工具必须要装。 - Android SDK的ext4magic、debugfs:用于在ext4镜像内部操作文件、恢复已删除文件,也可以用来读取镜像内容。
- 7-Zip、WinRAR:解包某些厂商定制格式的分区镜像(不过这只是临时办法)。
如果你是新手,我建议直接从Ubuntu apt源安装基础工具,然后去GitHub下载几个独立工具。比如在Ubuntu下执行:
sudo apt update sudo apt install android-sdk-libsparse-utils erofs-utils e2fsprogs这条命令装完,simg2img、mkfs.erofs、fsck.ext4基本上就齐了,剩下的就是找个顺手的地方放工具脚本。还有一个高频工具需要单独获取:make_ext4fs在较新版本的Ubuntu仓库里不太好找,需要从GitHub上找编译好的release,或者从Magisk的仓库里拿(Magisk自己附带了一个编译好的二进制)。我常用的是GitHub上multipath项目下的prebuilt版本,具体不细说了,搜make_ext4fs prebuilt就能找到。
环境准备好之后,最好先快速验证一下工具是否能正常运行:
simg2img 2>&1 | head -5 mkfs.erofs -V看到版本信息或者帮助输出,说明链路OK,就可以正式开始解包了。
3. 解包实战:从raw到文件的全流程
前面的准备工作完成后,就进入正题了。解包这个动作,分两种情况:拿到的是raw ext4镜像,或者拿到的是sparse镜像。还有目前新机型越来越常见的erofs。
3.1 先处理sparse镜像
很多从官方刷机包(特别是Pixel的factory image、小米的线刷包)里解压出来的system.img,其实是sparse格式。判断方法很简单,文件头是3A FF 26 ED就是sparse。为什么厂商要打包成sparse格式?因为raw的ext4镜像里有很多空洞(没有数据的分区块),打包成sparse可以跳过大段全零数据,体积缩小不少,刷机的时候传输也快。
sparse镜像不能直接挂载或者解包,要先转换成raw格式。命令非常简单:
simg2img system.img system_raw.img如果你的system.img本身就是raw格式,simg2img也能正常处理,它会原样输出,不会报错。所以保险起见,不管什么格式,先跑一遍simg2img再说。
转换成功后,用file system_raw.img确认一下已经是Linux rev 1.0 ext4 filesystem data,就可以下一步了。
3.2 挂载ext4镜像直接读取
Raw ext4镜像最方便的处理方式就是loop挂载,不需要额外解包工具,直接把镜像当作一块磁盘挂到系统上,然后像操作普通文件夹一样访问里面的全部内容。
mkdir -p /mnt/system sudo mount -o loop system_raw.img /mnt/system挂载后进入/mnt/system,你就能看到完整的Android系统目录结构:app、priv-app、framework、lib、media等。
这里有一个重点:如果你只是想读取文件(比如提取某个APK),挂载就够了,到此为止,不用继续往下了。但如果你要解包出来,对文件做修改,建议直接在当前挂载目录里面改,改完再统一打包,这个方案最不容易出错。那些先把所有文件复制出来、修改、再拷回去的搞法,会引入文件权限和时间戳的混乱,后期打包更容易翻车。
注意:部分镜像的selinux上下文和文件owner是特殊设置的。挂载时如果发现系统因为权限拒绝写入,可以加
-o rw,loop重新挂载,如果有特殊文件(比如只在Android环境下有效的符号链接),检查一下链接目标是否完好。
那如果挂载失败怎么办?比如镜像损坏或者有特殊情况。这时候可以用debugfs来救命。debugfs是一种直接读取ext4文件系统结构的工具,不需要内核挂载支持:
debugfs -R "ls -l /" system_raw.img也可以进入交互模式。这个工具在镜像无法挂载时特别好用,至少能救回大部分文件(不过操作体验一般,适合应急)。
3.3 erofs镜像的解包方式
如果你拿到的是新机型的SYSTEM.IMG,且文件系统显示为erofs,那挂载方式就不同了。Erofs是华为和OPPO、vivo等厂商从Android 12开始大量使用的一种只读压缩文件系统,优点是体积小、读取性能好。它的存在越来越多,不掌握erofs的处理方法,你基本没法玩新机型。
解包erofs镜像,有两种主流思路:
第一种,内核支持直接挂载。较新版本的Linux内核(5.4+)默认开启了EROFS_FS支持,可以直接挂载:
sudo mount -t erofs -o loop system.img /mnt/system如果报unknown filesystem type 'erofs',说明当前内核没开erofs支持,最常见场景是WSL2默认内核不带(WSL2内核默认没有CONFIG_EROFS)。看你的环境而定,如果没开的话,建议直接用第二种方式。
第二种,用erofs-fuse工具解包。GitHub上有erofs相关的fuse工具,编译后通过用户态挂载:
erofsfuse system.img /mnt/system同时,fsck.erofs也可以用来检查镜像是否完整。不过实测下来,最直接的方式还是用extract.erofs这类的独立解包工具,比如GitHub上erofs-utils新版本已经自带了dump.erofs,可以把整个镜像导出为目录:
dump.erofs -i system.img -x /mnt/system这里-x参数就是把所有文件提取到指定目录里。这个方案非常稳,我在几个不同厂商的镜像上都实测过,没有任何问题。更老的方案是用extract.erofs(Python脚本)来解包,但新版内核和erofs格式更新后,那个脚本对某些特性支持不是很好,不太推荐再用了。
3.4 解包完成后的检查
无论哪种方式解包出来,我都建议先检查一下目录中的关键文件是否齐全。最核心的是看build.prop,在/mnt/system(或解包目录)的system/下:
cat /mnt/system/system/build.prop | head -20正常情况下会输出一堆ro.开头的系统属性。如果这个文件存在,说明解包过程基本没问题。再就是检查framework和app目录,看看APK文件有没有完整解出来,文件大小是否合理。
到这一步,解包工作就算完成了。接下来就是你要做的修改:删APK、替换文件、改配置。这些操作就是普通的文件操作,没什么好细说的。但是切记,不要随意改变目录结构和原有文件的权限,后面打包的时候会有影响。
4. 打包回去:重建一个能刷的SYSTEM.IMG
如果说解包是开胃菜,那打包才算正餐。很多人解包很顺利,一打包就翻车:刷进去开机卡在logo、无限重启、甚至直接变砖。为什么?因为打包SYSTEM.IMG不是单纯把文件塞回一个镜像,你得保证文件系统的结构、大小、SELinux上下文、inode数量等符合设备的需求。
4.1 打包前的核心参数确认
打包前要弄清楚几个关键参数:目标文件系统格式、镜像大小、inode数量。这些参数不匹配,轻则刷机校验失败,重则开机后系统不认分区。
镜像大小:可以沿用原始镜像的大小,也可以自行设定。如果你想精简后减小体积,注意别比原始system分区还大(那会直接报错)。如果磁盘空间够,大一点没太大关系,系统挂载时会正常处理;但如果你的镜像小到连原有内容都放不下,打包就会直接失败。一般来说,我建议保持原大小,或者稍微加大5%左右留点余量。在解包前先记录原始文件大小:
ls -l system.imginode数量:inode是文件系统索引节点的数量,决定了这个分区最多能存放多少文件。如果你把原来系统里的几百个APK都解包出来,再打包回去,文件数量基本不变,用默认值就行。但如果你打算额外往系统里塞很多新文件(比如添加Google服务包、一组GApps),就需要增加inode数量,否则打包报No space left on device。分配公式很简单:文件数再加15%~20%的余量,就是合理的inode数。
4.2 打包ext4镜像
如果你解包出来的目录里有完整文件,比如system/子目录,打包ext4镜像用make_ext4fs,下面是我常用的一套参数:
make_ext4fs -l 3000M -s -a system system_new.img /mnt/system/system解释一下:
-l:镜像大小,单位M或G,写你需要的目标大小。-s:生成sparse镜像,如果要直接刷机或打包进zip,这一步建议开启,生成的镜像体积小很多。-a:指定Android根目录挂载点,最常见的是system。这个参数会关系到镜像内的文件属主和SELinux上下文的处理,必须和原系统一致。- 最后一个参数是源目录路径。
如果你用原生mkfs.ext4来做,需要多一步:先创建空镜像,再挂载,再拷贝文件,再卸载。这种方式更方便塞文件,也能更精确地控制参数,但要额外注意文件权限。相比之下,make_ext4fs一步到位,很适合批量操作,所以这个工具在定制ROM圈子里一直没被淘汰。
一个容易踩的坑是:make_ext4fs在文件较多时会比较慢,并且它会自动把部分文件优化存放在连续区块,导致最终镜像比实际文件总大小大很多,这属于正常现象。但如果-l给的过大,生成的镜像会膨胀到几个GB,这时候要检查一下源文件是否正常,排除有超大文件混进来的情况。
4.3 打包erofs镜像
如果你的机型用的是erofs文件系统,打包工具就得换成mkfs.erofs。命令示例:
mkfs.erofs -zlz4hc,12 system_new.img /mnt/system/这里-z指定压缩算法。Erofs支持lz4、lz4hc等算法,其中lz4hc压缩率更高,代价是打包时间变长。根据机器性能不同,一个2GB的镜像可能要跑几分钟。我在低配服务器上打包过,CPU占满的情况下大约需要5~10分钟,属于正常波动范围。
打包erofs最关键的一点:源目录必须是清理过的、状态干净的系统目录。不要在目录里留临时文件,不要把挂载点本身当作源目录打包(会打包进一堆挂载运行时的玩意儿)。我每次都在一个独立的stage目录里做文件修改和整理,最后再执行打包。
4.4 重新打包成刷机包zip
如果你手里的是Magisk模块、Recovery刷机包(update.zip),那打包成镜像后还需要重新打包成zip,并做签名。这里有个常用的打包命令:
zip -r rom_update.zip META-INF/ boot.img system_new.img注意刷机脚本META-INF/com/google/android/updater-script里如果写死了package_extract_file("system.img", "/dev/block/..."),你替换同名文件就行,如果改了文件名,updater-script也得同步改。不过现代TWRP一般也会自动检测分区类型,不一定要求SPARSE格式,具体看你选的包结构。
刷入后如果只是替换系统镜像,理论上不用wipe data。但如果你是改了系统框架级别的文件(比如framework、服务jar包),强烈建议刷完后wipe cache/dalvik,否则可能出现各种神奇的小问题,比如设置闪退、桌面异常、偶发应用崩溃。这问题不是镜像打包错误,是旧的ART缓存还保留着,跟你新镜像里的代码不匹配。
5. 实操中的常见问题与排坑实录
镜像我前前后后处理了几十次,踩过的坑不少。挑几个最典型的写出来,你如果遇到类似情况,直接照着排查,能省下大半天时间。
5.1 打包后刷入无限重启
这是出现频率最高的问题。绝大多数情况下,是SELinux上下文(file_contexts)没有正确处理。厂商固件在发布时,每个文件都带有一个security.selinux属性(比如u:object_r:system_file:s0),如果你换成make_ext4fs重新打包,它的-a system参数会自动帮你设置通用上下文,但如果你用了mkfs.ext4,这个属性会被清空。解决方法是打包前用ls -Z看下原解包目录的文件上下文,然后在打包脚本里显式处理。
另一个原因是文件权限变了。比如原来的系统文件很多是0644,目录是0755,如果你解包时用了Windows工具(比如在Windows里解压),权限位会被改成0777,这种情况下,打包后系统挂载检查文件许可时就会拒绝执行关键服务,造成滚屏重启。我的习惯是解包后不经过Windows中转,全程在Linux下操作,这样权限会保持得很干净;万一不得不在Windows弄,打包前用chmod和chown统一修复。
5.2 打包时报No space left on device
镜像大小不够或者inode耗尽,两者都会报这个错。如果你加了好几个大文件,先确认-l给的大小是不是够。检查源目录总大小:
du -sh /mnt/system/再对比你设置的镜像大小,留出至少15%余量。如果你塞了大量小文件,报错信息里会提示inode相关,这时候把-i参数加上,手动指定inode数量:
make_ext4fs -i 1048576 -l 3000M -s -a system system_new.img /mnt/system-i的单位是字节,表示每隔多少字节分配一个inode。这个值越小,能容纳的文件数越多,但相应文件系统元数据占用的空间也越大。一般一个系统分区有30万~50万inode就够用了,具体按文件总数来估算。
5.3 sparse镜像直接被recovery拒绝
有些Recovery不认raw格式,有些刷机包脚本又要求sparse格式。如果make_ext4fs没加-s,生成的就是raw镜像。刷入时如果报invalid sparse file format或者failed to execute updater script,说明格式不匹配。用img2simg转换成sparse:
img2simg system_raw.img system_sparse.img转换后文件会明显变小(这是正常的,是文件镜像是稀疏存储),刷机流程就能顺利走完。
5.4 镜像挂载后看不到app目录
有人解包后挂载镜像,进入/mnt/system后发现app目录是空的,或者直接看ls -l权限都是?。这种情况极可能是镜像本身是个加密的、或者厂商魔改了文件系统结构。比如早期EMUI的system镜像有特殊的分区表,直接挂载会得到不可读的内容。遇到这种情况,就不要再尝试硬挂载了,直接用debugfs、7-Zip这类能按文件系统结构读取的工具走一遍,确认是不是真的加密。如果是加密镜像,里面文件多半是厂商签名过的,解密本身超出本文范围,不过这场景在日常刷机里已经很罕见了。
5.5 打包后签名问题
很多人改了system后会重新签名整个刷机包,但忘了老设备的bootloader有签名校验。如果设备已经解锁(unlocked bootloader),签名校验通常可以绕过,你不用太担心。如果你的设备没解锁,改了system之后直接刷,大概率直接被bootloader拒收。说实话,不解锁bootloader的情况下,做系统分区修改基本等于白忙活,所以动手前先确认设备bootloader状态,这是个老生常谈但永远有人踩的坑。
5.6 不同大小镜像对刷机工具的影响
ODIN、Fastboot、厂商专用工具对system.img的体积都很敏感。有的工具要求镜像必须小于等于分区大小,有的工具还要求按block对齐(比如4096字节)。如果你的镜像加了一堆东西导致体积膨胀,刷机工具可能报错。建议打包完成后,用blockdev --getsize64确认下分区实际大小,镜像别顶着上限走,留点余量总归是好的。
6. 一些进阶用法和我的心得体会
除了常规的解包、修改、打包,SYSTEM.IMG这块还有很多进阶玩法。这里分享几个我自己试过且验证可行的思路:
制作多卡刷包(Multi-ROM):Android 10之后动态分区是大趋势,直接把system、vendor、product做成一个super.img再刷入,需要用到lpmake工具(Android dynamic partition工具链)。这个玩法复杂度高一些,需要把三个子分区镜像合并成super,还要保证生成的super.img能被设备识别。我做过一次,过程比较繁琐,但刷完后的灵活性是传统方式比不了的。
系统瘦身的最优解:与其手动删APK,不如用debloat.list策略。把不需要的包名列成清单,写脚本批量删除。这个方式的好处是可追溯、可复用,重新刷机后再次执行即可。脚本本身也很简单:
while read pkg; do rm -rf /mnt/system/system/app/$pkg rm -rf /mnt/system/system/priv-app/$pkg done < debloat-list.txt分区镜像备份恢复:刷机之前建议把原系统的system、boot、vendor分区全部备份下来,打包成tar.gz存好。万一折腾坏了,恢复原厂镜像比重新下载整个刷机包快得多,而且还能保证是原厂没破坏过的干净环境。
我个人实际操作中最大的体会是:解包打包SYSTEM.IMG这件事,本质上比的是耐性和细致,不是技术难度。每次动手前,先梳理清楚自己改了什么,改了哪些文件,原始的备份放在哪里。一旦出了问题,至少能知道怎么往回退。像我就吃过亏,解包完顺手把原始镜像删了,改坏了之后只能重新下刷机包,那感觉真心酸爽。
最后再分享一个小技巧:如果你只是想改改系统里的某个APK,不需要完整打包整个system镜像,可以考虑直接把APK提取出来,用apktool解包、修改、重打包、签名,然后用adb remount和adb push直接推到设备上。这样省去了整个镜像打包刷机的过程,调试效率提高不少。只有当你需要批量修改或者做定制ROM时,才需要走完整的镜像解包打包流程,明确自己的目标再选工具,往往事半功倍。
本文还有配套的精品资源,点击获取