最近好几个朋友都在折腾第三方ROM包时卡在system.img这一步,跑来问我能不能写一篇能落地的教程。说实话,这玩意儿初次接触确实劝退,命令看着吓人,还动不动就在打包环节翻车。但拆过几次之后你会发现,它本质上就是一份可以整体挂载的Linux文件系统,解压和修改本身并不难,难点全在于“改完还能刷回去正常开机”。这篇教程我会把Windows和Linux两条路线一次性讲透,从镜像格式识别、解压挂载、文件修改,到重新打包、刷回设备,每一步都按实战记录来写,中间穿插我踩过的坑和排查思路。想自己精简系统应用、改开机动画、研究系统内部结构的同学,这篇可以直接照着操作。
1. 拆system.img前,先搞清楚你面对的是个什么文件
1.1 system.img到底是干什么用的
Android设备内部存储会被划分成多个分区,每个分区各管一摊事情。system分区就是存放操作系统主躯体的地方,包括Android框架层代码、系统应用、核心库文件、字体、系统铃声、权限配置等。system.img就是整个/system目录的完整镜像文件,相当于把一台服务器的系统盘整体打包成了一个文件。刷机时,fastboot flash system system.img就是把这个镜像原样写入设备的system分区。
可以把它理解成一个特殊格式的压缩包,但它不像zip那样随便就能解开,因为它内部是一个完整的Linux文件系统,默认是ext4格式,新设备上越来越多地采用erofs等只读文件系统。它里面的权限、属主、SELinux安全上下文信息都极其敏感,改错一个字段,轻则某个应用闪退,重则直接开不了机。
网上下载的第三方ROM包(zip格式)解开之后,你会发现里面有system.img、boot.img、vendor.img等一堆镜像文件。boot.img管内核和启动流程,vendor.img管硬件厂商相关代码,而我们常说的“精简系统”“去掉预装应用”“改默认桌面”,绝大多数要动的地方都在system.img里。
1.2 什么情况下你才会想起去改它
日常使用中你基本不会碰system.img,因为它属于只读分区,普通用户没有写入权限。但遇到下面这些需求,你就不得不把它掏出来:
- 精简系统:去掉运营商预装应用、厂商全家桶,这些应用很多存在
system/app或system/priv-app目录下。 - 修改系统级配置:比如改
build.prop里的型号名称、DPI密度、默认时区、开机动画里的bootanimation.zip。 - 替换系统应用:把某个系统应用换成同样包名的修改版,或者往
priv-app里塞需要系统权限的工具。 - 移植和逆向研究:研究某款系统的内置功能、从别的ROM里提取某个应用或库文件放进当前系统。
- 学习文件系统结构:想彻底搞明白Android系统启动后加载了哪些东西。
需要注意的是,现在很多新手机对system分区有完整性校验(Android Verified Boot,简称AVB),系统启动时会验证镜像的数字签名。如果你的设备bootloader已经解锁,通常可以通过fastboot命令附带--disable-verity --disable-verification参数关闭校验,或者直接使用userdebug版本的固件,否则就算你成功修改了镜像,刷进去也大概率卡在开机界面。这部分后面我会专门说。
2. 不同镜像格式决定解压工具:先识别再动手
2.1 三种主流格式的识别方法
很多教程一上来就教你挂载,但忽略了一个前提:你手里的system.img到底是什么格式?不同格式用的工具完全不同,强行操作只会浪费一晚上时间。
最常见的是下面三种:
| 格式 | 特点 | 常见场景 |
|---|---|---|
| raw ext4 | 最原始的ext4文件系统镜像,结构完整 | 老设备、部分卡刷包解包后的镜像 |
| sparse ext4 | 带sparse稀疏头部的ext4镜像,文件大小更小 | 官方ROM刷机包最常见 |
| erofs | 只读压缩文件系统,读取性能好 | Android 10以后越来越多新设备采用 |
在Linux终端里,用一条file命令就能识别:
file system.img输出大致会是这样:
Android sparse image, version: 1.0说明是sparse格式。Linux rev 1.0 ext4 filesystem data, UUID=...说明是raw ext4格式。erofs filesystem说明是erofs格式。
Windows下没有file命令,但你可以用Notepad++或任何十六进制编辑器打开文件头部看magic字节。ext4的magic是53 ef,erofs的magic是e0 f5 81 76,sparse的magic是3a ff 26 ed。不过说实话,逐个看十六进制太麻烦了,我更建议Windows用户装一个WSL,进去直接执行file命令,一下就能判断。
2.2 为什么格式判断这么重要
因为raw ext4可以直接挂载或解包,sparse需要先转换成raw才能操作,而erofs用的又是一套完全不同的工具链。如果你把sparse格式直接丢给挂载命令,内核会报错找不到文件系统;如果你把erofs格式当成ext4去挂载,同样会失败。
我一开始折腾的时候不知道还有sparse这回事,拿一个sparse镜像反复执行mount命令,系统一直提示wrong fs type,我还以为是镜像文件损坏了,重新下载了三遍。后来才明白,先做格式识别,然后simg2img转成raw,一分钟就搞定了。
实际上,官方ROM包里的system.img基本都是sparse格式,因为Google和厂商希望尽量减少刷机包体积。而第三方作者做的精简包,有些会直接使用raw格式方便用户修改。所以拿到镜像文件后第一件事永远是file system.img,不要跳过。
3. Linux平台实操:挂载修改的完整流程
3.1 搭建Linux侧解压环境
如果你本来就是Linux用户,这一步最简单。以Ubuntu/Debian为例,主要需要这几样工具:
sudo apt update sudo apt install -y simg2img e2fsprogs erofs-utilssimg2img:将sparse镜像转换成raw镜像。e2fsprogs:提供mount、fsck、debugfs等ext4文件系统工具。erofs-utils:提供fsck.erofs、dump.erofs等erofs工具。
有的发行版包名可能不太一样,比如simg2img在部分老仓库里叫android-tools-fsutils。如果安装时提示找不到包,可以用apt search simg2img或apt search sparse搜一下。
如果机器是Arch系,用pacman装对应包即可。无论哪个发行版,核心原理都一样:先把sparse转raw,然后挂载到某个目录,像操作普通文件夹一样操作里面的文件。
3.2 识别、转换并挂载system.img
第一步是识别格式,第二步是转换(如果是sparse),第三步是挂载。
# 1. 查看文件类型 file system.img # 2. 如果是sparse sparse image,转换成raw simg2img system.img system.raw.img file system.raw.img # 确认转换成功 # 3. 挂载raw镜像到目录 sudo mkdir -p /mnt/system sudo mount -o loop system.raw.img /mnt/system # 4. 查看结果 ls /mnt/system关键点在于mount -o loop里的loop参数,它告诉内核把这个普通文件当作块设备来处理。成功挂载后,/mnt/system里就会看到熟悉的app、priv-app、framework、build.prop等目录和文件。
修改完文件后,卸载时记得先确保没有进程占用目录,否则会提示target is busy:
sudo umount /mnt/system如果提示busy,用lsof /mnt/system看看是哪个进程占用了,关掉后再卸载。卸载之后最好跑一遍文件系统检查:
e2fsck -f system.raw.img这一步能发现你在修改过程中有没有搞坏文件系统结构,如果报错,建议重新从原始镜像开始,不要带着错误继续打包。
3.3 修改文件时最容易忽略的权限与上下文问题
镜像挂载后,你会发现自己用普通用户身份访问时提示权限不足,因为Android镜像里的文件属主都是root,权限也设置了限制。这时候用sudo操作就行:
sudo rm -rf /mnt/system/app/SomeBloatware sudo cp /path/to/SystemApp.apk /mnt/system/priv-app/但这里藏着两个大坑,新手十有八九都会栽进去。
第一个坑是文件权限。你从桌面环境复制进去的文件,权限往往继承自你当前的umask,比如644甚至666,但Android系统应用目录里的文件通常有严格的权限要求。比如priv-app目录权限一般是0755,应用文件权限一般要求0644,可执行文件要求0755。如果你复制的文件权限不对,系统启动时应用会被拒绝加载,或者直接导致SystemServer崩溃。
第二个坑是SELinux上下文。Android系统的SELinux策略非常严格,每个文件都有自己的安全上下文标签,比如系统应用的文件应该是u:object_r:system_file:s0,可执行文件可能是u:object_r:system_file:s0或更具体的类型。你新复制进去的文件默认上下文很可能完全不对,刷机后就会触发avc: denied日志,系统表现是各种诡异——应用闪退、服务起不来、设置界面空白。这时候再把镜像挂载回来,用ls -Z查看文件上下文,发现和周围的文件完全不同,就知道问题出在哪了。
正确做法是在修改文件时保留原有的属主、权限和SELinux上下文:
# 先查看原文件的属主、权限、上下文 ls -lZ /mnt/system/priv-app/SystemApp/SystemApp.apk # 复制新文件后,手动调整 sudo chown root:root /mnt/system/priv-app/NewApp/NewApp.apk sudo chmod 0755 /mnt/system/priv-app/NewApp/ sudo chmod 0644 /mnt/system/priv-app/NewApp/NewApp.apk sudo chcon -u system_u -r object_r -t system_file /mnt/system/priv-app/NewApp/NewApp.apk如果是替换已有的应用,最好的办法是保留原应用的文件权限和上下文不变,直接把新APK内容覆盖进去。更多情况下,重新打包时会用file_contexts文件来自动设置上下文,这个我在第5节展开讲。
4. Windows平台实操:WSL挂载与纯Windows工具
4.1 为什么不推荐直接在Windows上用7-Zip解压
很多Windows用户第一反应是用7-Zip直接打开system.img,以为像打开zip一样方便。实际上,7-Zip对ext4镜像的支持非常有限,虽然能列出部分文件结构,但会遇到这些问题:
- 文件权限、属主信息丢失,根本没地方显示。
- 部分ext4特性(如UUID、扩展属性、SELinux上下文)无法处理。
- 只能解压提取,不能修改后重新生成合法的镜像文件。
- 遇到sparse格式或erofs格式时基本无能为力。
所以,7-Zip只适合你“看一眼”镜像里有什么文件,不适合做完整解包和重新打包。真要在Windows上完成“解压→修改→重打包”这个闭环,我推荐下面两条路。
4.2 方案一:WSL里完成整条修改链
WSL(Windows Subsystem for Linux)是微软官方的Windows子系统,你不需要装虚拟机或者物理Linux环境,直接在Windows里跑一个Ubuntu终端,然后Linux能干的活儿它基本都能干。
安装完WSL后,把system.img放到Windows的某个盘符里,比如D:\rom\system.img,然后进入WSL终端,访问Windows盘符上的文件:
cd /mnt/d/rom file system.img如果遇到权限问题,或者WSL跨文件系统操作太慢,更好的做法是把镜像复制到WSL的Linux文件系统里:
cp /mnt/d/rom/system.img ~/rom/ cd ~/rom/然后按照第3节的步骤执行simg2img、mount、umount就行。修改完的镜像再复制回Windows盘:
cp ~/rom/system_new.img /mnt/d/rom/WSL方案最舒服的地方在于,你不用额外装任何Windows专用软件,所有Linux工具都能直接用。而且WSL里跑的是真正的Linux内核,挂载ext4、erofs都没有兼容性问题。装WSL2以后性能也足够,处理大镜像文件不会卡到怀疑人生。
4.3 方案二:用第三方驱动把ext4镜像变成Windows盘符
不想用WSL的话,Windows上也有专门处理Linux文件系统的工具。早期比较流行的是ext2explore,但它只能读取,不能写入,改不了文件。后来像我实际用过比较稳的是Paragon公司的Linux File Systems for Windows,装上以后Windows资源管理器里能直接挂载ext4镜像,把镜像变成一个虚拟盘符,读写都没问题。类似的工具还有DiskInternals Linux Reader,但那个偏读取查看,写入支持不稳定。
这类工具的优点是对Windows用户友好,鼠标点一点就能操作文件。但缺点也很明显:一是很多工具是付费的,免费版功能受限;二是它们只解决“读写ext4”的问题,不解决“重新打包img”的后续步骤。你改完文件之后,还是需要某种方式把整个目录重新制作成镜像,这通常还是得回到Linux环境去执行make_ext4fs或mke2fs命令,否则只能把修改过文件的raw镜像直接保存。所以我个人还是强烈推荐WSL路线——与其装一堆半吊子的图形工具,不如一步到位用WSL闭环解决。
4.4 从Windows里识别镜像格式
在Windows上执行file命令不方便,但如果你装好了WSL,这个问题自然就解决了。进入WSL后执行:
file system.img如果你还没装WSL,临时用7-Zip打开镜像,看7-Zip能不能识别文件系统结构,或者用十六进制工具查看文件头部的magic字节,也能有一个初步判断。不过准确率最高的还是file命令,因为它会完整解析文件头部信息并匹配既有格式库。
5. 重新打包:把改完的目录变回可刷入的system.img
5.1 打包工具怎么选
很多人解压修改完,卡在最后一步不知道怎么把目录重新做成镜像。打包工具的选择取决于你原始镜像的格式:
- ext4镜像:用
make_ext4fs或者更标准的mke2fs+e2fsdroid。 - erofs镜像:用
mkfs.erofs。 - sparse格式:打包成完整raw镜像后,再用
img2simg转成sparse,或者打包时直接加sparse参数。
make_ext4fs是老牌工具,在Android 4.x到8.x时代非常流行,一条命令就能把目录打包成ext4镜像,适合快速实验。但它对SELinux上下文处理得不够完善,新版Android系统镜像里如果缺少正确的file_contexts,刷进去很容易出问题。
更标准的方式是使用AOSP工具链里的mke2fs+e2fsdroid。mke2fs负责创建空的ext4文件系统,e2fsdroid负责把目录内容写进镜像,并在写的过程中根据file_contexts文件设置每个文件的安全上下文。这个方案在Android 9以上已经成了官方默认做法。
5.2 ext4镜像的重新打包过程
先用最简单的make_ext4fs法说明思路。假设你已经把修改后的目录放到了/mnt/system_new,现在要生成新的镜像文件:
make_ext4fs -s -l 512M -a system system_new.img /mnt/system_new参数含义:
-s:生成sparse格式镜像。如果你的设备能接受raw格式,可以去掉这个参数。-l 512M:指定分区大小。这里必须和你原始system分区大小一致或者略小。如果设置得比原始分区大不少,刷入时可能超出分区实际容量。-a system:指定挂载点,同时让工具在打包时按Android的挂载规范设置文件路径。
如果使用mke2fs+e2fsdroid的组合,命令会长一些,但更标准:
# 1. 创建一个空的ext4镜像文件 # 512M 是对应分区大小 mke2fs -t ext4 -L system -b 4096 -I 256 -O has_journal system_new.ext4.img 512M # 2. 把目录内容写入镜像,并设置SELinux上下文 e2fsdroid -f /mnt/system_new -a /system -S file_contexts system_new.ext4.imgfile_contexts文件通常可以从原ROM包中提取,或者从AOSP源码的system/sepolicy目录里找到。如果你手头没有这个文件,也可以用原镜像挂载后根据ls -Z导出的信息手工整理一个,但这个工作量比较大,适合对SELinux机制很清楚的人。
最终生成system_new.ext4.img后,如果原始镜像本身是sparse格式,再用img2simg转换:
img2simg system_new.ext4.img system_new_sparse.img5.3 erofs镜像的打包示例
如果你的设备比较新,原始system.img是erofs格式,打包工具是mkfs.erofs。erofs的设计目标就是只读文件系统,所以打包流程和ext4思路完全不同,它不需要走“挂载目录→写入文件→设置上下文”的流程,而是直接把指定目录编译成一个压缩只读镜像:
mkfs.erofs -zlz4hc system_new.img /mnt/system_new常用参数:
-z lz4hc:指定压缩算法,erofs常见压缩算法有lz4、lz4hc,后者压缩率更高。-E uuid=...:可以指定UUID,但一般不需要。-V:显示版本信息。
erofs镜像不能像ext4那样随意挂载修改后重新打包,因为文件系统的设计初衷就是只读,而且它的元数据存储方式更紧凑。修改时通常需要先用fsck.erofs或dump.erofs解析,但这方面工具少很多。如果对erofs不熟,我会建议优先考虑用安卓解包工具把erofs镜像先转成ext4或者直接解出文件系统目录,修改后再用erofs打包,或者干脆在打包前把分区格式转成ext4(前提是设备内核支持)。
5.4 打包后验证与刷回设备
打包完成不等于万事大吉,我强烈建议你先验证再刷机,否则很容易白忙活一场。验证分四步:
- 确认新镜像文件类型正确:
file system_new.img。 - 挂载新镜像检查内容:
sudo mount -o loop system_new.img /mnt/system_check,进去看一下文件数量、关键文件是否存在、修改是否生效。 - 卸载后跑一遍文件系统检查:
e2fsck -f system_new.img,如果报错就要重新打包。 - 有条件的话,用一个支持动态分区解析的模拟器或twrp里mount试试,确认能正常识别。
刷回设备的命令是:
fastboot flash system system_new.img如果设备启用了AVB校验,可能还需要同时刷入关闭校验的vbmeta:
fastboot flash vbmeta vbmeta.img --disable-verity --disable-verification注意,这条命令会关闭系统的verity校验,意味着设备的安全性有一定下降。在这个前提下,修改后的系统才更容易启动。
还有一条常识性提醒:刷机前一定要备份当前系统,尤其是Data分区里的照片、聊天记录、应用数据。system分区被刷后是没法直接回滚到原状态的,除非你留着原始镜像并重新刷回。我个人的习惯是把原始system.img先复制一份到电脑单独存放,改坏了还能随时刷回去。
6. 高频踩坑记录与排查思路
6.1 常见问题速查表
下面这个表格是我折腾过程中遇到最多的几类问题,以及对应的排查和解决思路:
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
mount提示wrong fs type | 镜像可能是sparse格式或erofs格式 | 先执行file识别格式,sparse转raw;erofs用专用工具处理 |
| 修改后刷机卡第一屏 | SELinux上下文丢失或AVB校验失败 | 用file_contexts重新打包;关闭verity;检查vbmeta |
| 系统应用闪退 | 应用权限不对或依赖缺失 | 检查APK权限是否为0644、目录是否为0755;确认所需lib已放入对应目录 |
| 分区空间不够,打包失败 | 修改后内容超出了原system分区大小 | 删掉更多无用应用;减小打包镜像的-l分区大小 |
打包后刷入提示size too large | 镜像实际大小超过设备分区 | 确认-l参数指定的分区大小;不要超过原始分区容量 |
卸载目录时报target is busy | 有进程正在访问挂载目录 | 用lsof或fuser找到占用进程并关闭 |
| 无法用普通用户访问挂载目录 | ext4镜像里的文件属主是root | 使用sudo操作,或者在挂载时加-o uid=你的用户临时改变默认属主 |
| Windows下用7-Zip打开但看不到完整文件 | spase/ext4特有结构不支持 | 不要依赖7-Zip,用WSL挂载或专用Linux文件系统工具 |
6.2 几条能救命的实战经验
第一,尽量使用同一个工具链完成解压和打包。早期精简单system的时候,我用Linux挂载解压,然后拿到Windows上用工具打包,折腾了三天,各种启动崩溃,后来发现是两套工具对文件属性的理解不一致,导致部分文件的归属地和SELinux标签被修改了。同一套工具链(比如全部用AOSP工具)能最大程度减少这类问题。
第二,修改system.img的时候,尽量用“复制出一个新目录再修改”的思路,不要直接在挂载后的原镜像上操作。比如先cp -a把挂载目录整个复制出来,然后在副本上改,改完再打包。这样就算打包失败,原始镜像还是完好的,还能从头再来。
第三,如果修改完后系统能在Recovery模式下正常启动,但在正常启动时卡logo,优先怀疑SELinux上下文问题。进Recovery挂载system分区,用ls -Z对比几个系统应用文件的上下文,发现不对就重新从原镜像提取正确的file_contexts,再打包一次。大多数莫名其妙的启动问题都能通过这一步解决。
第四,对于不熟悉的设备,不要贪快直接刷入新镜像。先用fastboot boot而不是fastboot flash测试:把新镜像临时加载到内存启动,不写入分区。不过这个功能不是所有设备都支持,支持的设备上非常管用,安全系数高很多。
第五,erofs格式的设备虽然打包比较麻烦,但你仍然可以先用simg2img把sparse erofs镜像转成raw erofs镜像,然后用erofs工具挂载或解包;如果工具不顺手,就把镜像丢给fsck.erofs --extract=目录这种方式提取文件,修改后再整体打包。原理上都绕不开“解出文件目录→修改→重新编译成镜像”这三步,工具差异只是路径不同。
最后再分享一个小技巧。我实际操作中习惯把整套解包、修改、打包的常用命令写成一个小的shell脚本,每次拿到新ROM包就自动帮我完成格式识别、sparse转换、挂载目录创建这几个重复动作。毕竟这活儿偶尔才做一次,命令记不全很正常,脚本化之后就能稳定复现,不用每次重新翻教程。
如果你只是想删几个系统应用,不考虑这么深的原理,直接用magisk模块配合系统less模式也能做到类似效果,但想彻底掌控system分区,学会手动处理system.img依然是绕不开的一课。