ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android 9.0 system分区读写挂载详解:绕过dm-verity与SELinux实现remount

Android 9.0 system分区读写挂载详解:绕过dm-verity与SELinux实现remount 如果你被Windows那句“你需要来自system的权限才能对此文件夹进行更改”折磨过那Android 9.0的system文件夹读写体验会让你重新认识“权限”这两个字。刚接触Android系统定制的时候我在一台Android 9.0设备上执行adb remount结果直接被一句Permission denied怼回来连mount -o rw,remount /system都提示只读文件系统。折腾了一整天才明白Android 9.0里挂载system文件夹读写这件事跟早期Android版本完全是两种玩法。这篇内容适合所有做ROM移植、系统裁剪、预装应用、音频调试甚至只是想在/system下塞个脚本的朋友。我会从Android 9.0为什么把system分区“焊死”讲起把完整的挂载流程、失败排查、替代方案和实际应用一次说透。1. Android 9.0把system“焊死”三种只看不改的机制先说结论Android 9.0上挂载system文件夹读写之所以难不全是因为你命令敲错了而是这一代系统在架构上做了三个决定性调整。不理解这三件事后面所有操作都是在碰运气。1.1 system-as-root架构根目录本身就是systemAndroid 9.0之前在大部分设备上还是传统架构/system分区作为一个独立分区挂载根目录/是ramdisk。你执行mount -o rw,remount /system的时候内核知道你说的“/system”是一个独立分区操作起来很直接。Android 9.0开始普遍采用system-as-root方案system.img不再单独挂载到/system而是在启动阶段直接作为根文件系统挂载/。也就是说你在文件管理器里看到的/system目录只是根分区下的一个普通子目录跟/在同一个挂载单元里。这意味着第一你不能再想当然地remount /system因为很多设备上根本没有独立的/system挂载点第二即使你强制mount -o rw,remount /也要先过内核的安全校验。我第一次在这类设备上执行adb shell mount | grep system时输出里只有一行/挂载记录当时就意识到这代的处理方式不一样了。1.2 AVB 2.0与dm-verity改一个字节就重启还原Android 9.0全量引入了Android Verified Boot 2.0这套机制的作用是启动时逐级校验分区的哈希只要发现system分区有任何一个字节跟出厂不一致就拒绝挂载或者虽然挂载了但是以只读方式运行又或者直接把设备打回recovery让你没法正常开机。底层配合的是dm-verity。这是一个Linux内核的块设备层安全机制它在挂载system这类受保护分区时会对每个数据块计算哈希并和分区末尾存储的哈希表比对。如果写入的数据块跟预期不一致I/O直接返回I/O error。所以你在Android 9.0上即使拿到了root直接强行remount成rw后往里拷贝文件也会遇到乱七八糟的写入失败或重启后一切还原。很多人以为“挂载失败”是命令语法问题其实大部分是dm-verity在拦路。我们要做的不是绕过它而是让它在接下来的操作期间“放假”。1.3 user / userdebug / eng三种构建类型的权限差异第三个隐形门槛是构建类型。同样一个系统编译的时候选的build flavor不同后面能做的事完全不同user版正式出货版ro.debuggable为0adb root不可用adb remount直接被禁用Accessibility调试、SELinux还处于Enforcing且基本不给医。userdebug版用于调试和测试ro.debuggable为1支持adb root很多设备也支持adb remountSELinux可以临时设为Permissive。eng版工程构建所有调试功能全开适合底层开发但日常用会有性能和稳定性问题。如果你想在一台正式出货的user版手机上直接做system挂载读写几乎没有可能。最快的路径是先确认手头设备有没有官方或第三方提供的userdebug镜像或者至少能解锁Bootloader让你自己刷入其他镜像。2. 动手前的准备判断设备状态和备份搞清楚原理之后建议先老老实实走一遍准备流程。这一步省时间也省砖头尤其是备份千万不要跳。2.1 判断你的设备能不能走这条路先把设备连上电脑确认四个基础信息。运行下面的命令并逐条核对# 确认系统版本必须是Android 9.0及以后才需要注意下面的坑 adb shell getprop ro.build.version.release # 确认构建类型user / userdebug / eng 一目了然 adb shell getprop ro.build.type # 确认SELinux当前模式 adb shell getenforce # 确认当前是否有root权限有些命令会直接失败 adb shell id如果ro.build.type是user而且adb root执行后显示adbd cannot run as root in production builds那基本可以断定这条路走不通建议直接跳到第5章的替代方案。如果是userdebug或eng准备继续。2.2 解锁Bootloader和关闭dm-verity验证即使你是userdebug设备原生状态下AVB依然会在启动时检查system分区完整性。所以正式挂载之前要先把验证关掉。不同厂家的解锁命令不同Pixel系列是fastboot oem unlock部分高通平台是fastboot flashing unlock。以Pixel 3为例整个流程是这样# 关机后按住音量下电源进入fastboot adb reboot bootloader # 执行解锁会清空用户数据提前备份 fastboot flashing unlock解锁成功并且重启开机后在Android里执行# 重新连接后确认能拿到root adb root # 关闭dm-verity完整校验部分设备用avbctl adb disable-verity # 或者执行 adb shell avbctl disable-verification adb rebootadb disable-verity这个命令在不少设备上是直接生效的它会把vbmeta分区和dm-verity的配置改成“不校验”状态。执行完会提示需要重启重启后你会发现后面的挂载操作顺畅很多。这里要提醒一下命令执行过程如果没报错不代表“一定能写”因为SELinux还会单独拦一道后面第4章详说。2.3 分区级备份必须做的事改system分区最坏的结果是设备卡在开机logo救都救不回来。所以动手之前我强烈建议把system分区完整备份出来。拿root权限后执行# 先确认system分区block设备路径 adb shell ls -l /dev/block/by-name/system # 直接备份整个分区到/sdcard下注意手机剩余空间要充足 adb shell dd if/dev/block/by-name/system of/sdcard/system_bak.img bs1M如果你不知道by-name路径可以执行adb shell cat /proc/partitions看看有没有明显的system、vendor、boot分区或者用adb shell ls -l /dev/block/platform/*/by-name/找到具体路径。备份完成之后用adb pull /sdcard/system_bak.img拉回电脑存好。这个备份文件不一定要真的用来恢复但它给了你一次后悔的机会。我见过太多人在操作前嫌麻烦不备份最后只能刷回官方完整包重新再折腾一遍数据。3. 最直接的挂载流程从adb root到remount写rw准备工作做完现在进入正题。这一套流程我在多台Android 9.0设备上走过按照这个顺序做成功率最高。3.1 先拿到root权限如果是userdebug或eng镜像并且已经关闭验证adb root一般不会再报错。执行后adb会自动以root身份重启adbd守护进程终端会重新出现设备列表。adb root adb wait-for-device adb shell id如果输出uid0(root)说明你已经有了root。这时SELinux如果还处于Enforcing建议先临时切到Permissive模式避免后续写入时被SELinux拒绝adb shell setenforce 03.2 找到真正的挂载点是“/”还是“/system”这一步非常关键。Android 9.0的system-as-root设备上有些系统仍会显示单独的/system挂载有些则显示整个根分区就是system。在用命令前先看一下自己的挂载表adb shell mount | grep -E (^| )/ | /system输出有可能长这样不同设备有差异/dev/block/sda14 / ext4 ro,seclabel,relatime 0 0这说明system分区作为根挂载/system不是独立子分区。此时正确的操作对象是/。如果输出里明确有一行/dev/block/sda14 /system ext4 ro,seclabel,relatime 0 0那直接对/system执行remount就好。我个人的判断习惯是只要输出里根分区的设备路径跟你列出来的system分区block一致就当整个根是system来操作。3.3 remount成rw并验证找到了真正的挂载点执行对应的remount命令# 如果挂载点是 / adb shell mount -o rw,remount / # 如果挂载点是 /system adb shell mount -o rw,remount /system没有报错的话再用命令验证一下实际状态adb shell mount | grep -E / | /system这时输出的挂载选项里ro应当变成rw。如果ro没变说明还有更底层的校验在拦着去第4章排查。验证通过后就可以直接在/system下做写入测试了。比如在/system下创建一个临时测试文件adb shell touch /system/test_write.tmp adb shell ls /system/test_write.tmp能创建、能列出说明system文件夹读写路线已经打通。确认不需要这个临时文件后删掉它。这套流程走完之后文件会真实写进system分区只要不关闭dm-verity或者改成“校验但放行”模式重启后会保留。但要注意如果前期没有正确关掉验证重启后就会被刚才说的dm-verity机制判定为“系统被篡改”要么进入恢复模式要么自动把改动还原。所以第2.2步的关闭验证一定不能省。4. 挂载失败的坑Permission denied、AVB报错、SELinux拦截排查这一章是这篇文章最值钱的部分。我在论坛和群聊里见过太多人卡在同一个错误上反复锤电脑。下面按实际排查顺序写。4.1 典型错误一remount failed: Permission denied这个错误出现的情况非常多但原因往往不同最常见的三种adbd没有root权限。user版系统直接这样adb root也是无效的。SELinux处于Enforcing拒绝了mount系统调用。dm-verity校验未关闭内核层的安全策略拒绝写挂载。排查顺序建议先确认身份adb shell id输出uid0(root)再继续否则先去解决root问题。然后看SELinuxadb shell getenforce如果是Enforcing先setenforce 0再重试。如果还不行检查第2.2步的验证是不是已经关闭或者尝试直接编辑vbmeta。4.2 典型错误二avb_slot_verify.c相关的刷机失败或开机失败如果你在fastboot刷入修改后的system镜像有时候会看到类似avb_slot_verify.c [line:xxx] ERROR: vbmeta: Verify vbmeta image failed的错误。这本质上是AVB发现镜像签名或哈希校验不过。解决方式有两条用刚刚第2.2步关闭验证之后的vbmeta分区一并刷入。比如在fastboot里刷入原始system时同时刷一个禁用了校验的vbmeta镜像。把修改后的system重新签名。这需要目标平台的AVB密钥一般设备厂商不公开普通玩家基本不可行。所以我的建议是能不用fastboot直接刷system镜像就别刷优先用第2.2步的disable-verity加上第3章的remount写入方式。这样只在运行时关验证不涉及重新签名镜像的复杂环节。4.3 典型错误三能挂载但写不进去或者写入报Operation not permitted这个问题最坑因为表面上挂载表已经显示rw了但touch或cp还是报Operation not permitted。十有八九是SELinux在文件级做了拦截。Linux的mount系统调用通过SELinux检查之后写入时还会再走一遍file类型策略尤其是/system目录挂着seclabel选项文件的SELinux标签、类型、关联规则全部要匹配。排查方法# 查看SELinux的审计日志定位被哪个策略挡住 adb shell dmesg | grep -i avc adb shell dmesg | grep avc: denied如果真的命中了SELinux拒绝要么把SELinux临时设为Permissive再试验证问题时可以用长期不建议要么针对具体文件路径编写SELinux policy。新手建议直接临时Permissive把操作做完后重启设备只要不影响开机可以接受。4.4 写入成功但重启后一切还原这一条很多老Android玩家最容易踩。早期版本没有dm-veritymount -o rw,remount /system之后写入的内容重启仍在。但Android 9.0上如果你没有提前关闭dm-verity系统启动时会把system分区重新恢复为出厂内容。如果遇到“改了但重启还原”的现象优先确认第2.2步的adb disable-verity是不是真的成功了adb shell avbctl get-verification显示disabled才算干净。此外还要确认写入到的文件是否真的在system分区上。有些设备有overlayfs机制/system里的写操作被透明定向到了/data下看着写成功了实际上没有改动底层分区。这种情况会需要用mount检查是否有overlay挂载覆盖了/system有的话得把overlay拆掉或者继续用Magisk的systemless方案代替。5. 不折腾分区的替代方案userdebug镜像、Magisk systemless和打包刷入不是所有人都需要真刀真枪写system分区。如果你只是改几个文件完全有更优雅的替代方案。5.1 直接刷一个userdebug版boot或系统镜像购买量产机之后如果能找到官方或开发者社区发布的userdebug版boot镜像只需要在fastboot模式下刷入boot分区就能让原厂user版系统临时获得调试权限。很多情况下不需要刷整个系统adb reboot bootloader fastboot flash boot userdebug_boot.img fastboot reboot刷入后adb root和adb disable-verity通常就能用了。不过这种方法只适合那些有同款设备开发版镜像的机型比如Pixel系列、部分高通公版方案设备。华为、小米等厂商不一定对外发布需要自己找来源。5.2 Magisk systemless不写system也能“改”systemMagisk是这类需求里最优雅的解法。它不直接写system分区而是在启动阶段用overlay方式把/data/adb/modules/某个模块/system目录下的文件临时映射到对应的/system路径上。具体思路是你创建一个模块目录把要改的文件放进对应路径比如想改/system/build.prop就创建/data/adb/modules/mymod/system/build.prop并写入修改内容。重启后应用层看到的/system/build.prop就是模块里的版本而底层system分区完全没动。这对系统的完整性校验完全无感也不怕刷砖。实际操作步骤# 手机里创建模块目录 adb shell mkdir -p /data/adb/modules/mymod/system # 将修改后的文件推入示例是build.prop adb push build.prop /data/adb/modules/mymod/system/build.prop # 创建模块描述文件name和description随便写 adb shell echo idmymod /data/adb/modules/mymod/module.prop adb shell echo descriptionmodify system via overlay /data/adb/modules/mymod/module.prop重启之后可以用adb shell cat /system/build.prop确认是否已经生效。这个方法适合预装应用、改音频参数、加脚本并且不担心重启后失效。缺点是无法修改那些在启动早期就被读取且不走overlay的系统关键文件比如bootloader链路上的东西。5.3 把整个system分区打包成镜像再刷入说到底层改动有一种“笨但彻底”的方法先在Linux环境下把system镜像里的文件改好再重新打包刷入。这个过程麻烦但在某些场景下比在Android里直接写更可控。常规步骤如下以Linux电脑为例# 1. 从手机里pull出raw格式的system镜像 adb shell dd if/dev/block/by-name/system of/sdcard/system_raw.img bs1M adb pull /sdcard/system_raw.img # 2. 在Linux下挂载镜像 mkdir -p /tmp/sys_mnt sudo mount -o loop system_raw.img /tmp/sys_mnt # 3. 修改镜像文件 sudo cp /path/to/new_app.apk /tmp/sys_mnt/app/NewApp/ sudo chmod 644 /tmp/sys_mnt/app/NewApp/new_app.apk # 4. 卸载镜像 sudo umount /tmp/sys_mnt # 5. 刷回设备注意AVB验证问题需要用第2.2步关闭验证后的vbmeta配合 fastboot flash system system_raw.img这个方案最大的坑在于AVB校验。直接刷修改后的镜像即使刷进去了开机也会被AVB拦。所以通常会被揉进第4.2节提到的vbmeta禁用方案里联合使用。6. 挂载后的经典应用以音频配置“多应用同时录音”为例系统分区读写打通之后最有价值的场景之一就是改系统音频配置。热搜词里提到的“android audio - 支持多应用同时录音_android9.0修改方法”正好就是这个方向我拿它展开讲大家能直观看到一个真实的修改流程。6.1 修改mixer_paths.xmlAndroid音频架构中codec的输入输出路径一般由/vendor/etc/mixer_paths.xml或/system/etc/mixer_paths.xml定义。多应用同时录音通用的思路是让录音通路支持多路并发回采而不是只有一个app独占声卡设备。流程如下# 确认system可写后找到音频配置文件 adb shell ls -l /system/etc/mixer_paths.xml adb shell ls -l /vendor/etc/mixer_paths.xml # 拉下来修改这里以常见的/system/etc/mixer_paths.xml为例 adb pull /system/etc/mixer_paths.xml # 备份原始文件到本地留着后悔药 cp mixer_paths.xml mixer_paths.xml.bak用文本编辑器打开mixer_paths.xml找到对应的mic path或record path节点修改或者增加“同时录音使能”相关的ctl配置。不同codec的写法差异很大高通的常见做法是在AZALIA或WCD节点下增加对多个后端设备的支持并用ctl命令配置成并发模式。具体参数建议以设备对应的codec数据手册为准不能通用复制。6.2 验证生效修改完成推回原路径adb push mixer_paths.xml /system/etc/mixer_paths.xml adb shell chmod 644 /system/etc/mixer_paths.xml adb shell sync adb reboot重启后可以用系统录音机、第三方录音app、VoIP通话同时测试录音确认是否真能并发。如果发现有的app仍无法占用麦克风多半是策略层还有限制再检查/system/etc/audio_policy_configuration.xml里的audio policy规则给新场景加适当的路由。这种改法如果没有Magisk就必须依赖第3章的挂载操作不然文件直接替换进只读system分区根本不可能。所以它是一个“挂载system读写”这个基础能力的典型受益场景。七、最后说点实操心得在我自己折腾Android 9.0挂载system读写这套流程时体会最深的一点是这不再是“一个命令解决所有问题”的时代了。现在的系统安全性设计是分层的架构层、内核层、SELinux层、构建类型层每一层都可能成为卡点。遇到问题不要盲目重试先判断自己卡在哪一层再对症处理。再分享几个比较零碎但实用的经验改完系统文件后执行一下adb shell sync再重启能减少因缓存没落盘导致的“改了等于没改”的玄学问题在正式改文件之前把所有配置类文件拉到电脑留一份备份比以后从网上找原始固件提取要省心一百倍如果你不确定当前配置是否能开机优先用Magisk systemless方案做临时验证确认可行后再洗到system分区里。系统改机这条路准备越充分后期踩坑越少。希望这篇能帮你省下我在Android 9.0上熬过的那些夜。
返回列表