ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 8.1 强制开启 adb remount:解包修改 boot.img 完整实战

Android 8.1 强制开启 adb remount:解包修改 boot.img 完整实战 拿到一台 Android 8.1 的设备想快速改个系统文件习惯性敲下adb remount大概率会撞上这么一串提示adb: unable to connect for root: closed或者干脆一句remount not permitted。标题里我故意写成了 Anroid因为搜这个问题的人十个里有八个都是这么拼的但问题本身是真的——Android 从 8.1 开始把 remount 这条路收得越来越紧尤其是 user 版本几乎等同于焊死。这篇东西就是我在这台 8.1 设备上“强制修改系统、让 adb remount 可用”的完整记录包括为什么会被拦、改哪里、怎么改、改完还是不行怎么查。适合搞 ROM 定制、BSP 开发、系统安全测试的同学参考小白也能照着操作但前提是你得能进 fastboot、会刷机而且手头要有能解锁的设备千万别拿日常主力机练手。1. 为什么 Android 8.1 上 adb remount 会被“三重门”拦死很多人以为adb remount就是一条命令的事真正在 8.1 上动手后才发现这背后其实串了三道关卡adbd 运行权限、dm-verity/AVB 校验、SELinux 和挂载状态检查。任何一道不过remount 就失败。搞明白这三层后面改起来才有方向。1.1 第一道门adbd 是以什么身份启动的adb remount要生效前提是 adbd 能以 root 权限运行因为 remount 本质上是修改挂载标志、重新挂载 system 分区这属于特权操作。而 adbd 的启动权限由两个属性决定ro.debuggable和ro.secure。在 Android 8.1 上这三个版本的默认属性差别很大版本类型ro.debuggablero.secure默认 adbd 状态可 remountuser01shell非 root否userdebug11shell但可 adb root需先关闭验证eng10root是同样受校验限制ro.debuggable0时adbd 会直接拒绝 root 请求报错就是 “adbd cannot run as root in production builds”。ro.secure1时即使 debuggable1adb root 后也只是从 shell 切换成 root而不是一开始就以 root 运行。所以如果你想强制修改 user 版本的 8.1 系统首先就要让 adbd 具备 root 能力这就必须从属性层面下手。这些属性在 8.1 上主要来自 boot 镜像里 ramdisk 的default.prop部分厂商还会在/system/build.prop、/vendor/build.prop里额外声明甚至从内核 cmdline 里传androidboot.debuggable。后面实操部分我会专门讲怎么改 boot 镜像因为这是让 user 版本开门的第一把钥匙。1.2 第二道门dm-verity 把 system 分区焊死属性过关只是第一步。Android 8.0 开始推行 system-as-rootsystem 分区不再是/system挂载点而是直接作为根文件系统的一部分同时强制启用 dm-verity 校验。dm-verity 做的事情简单说就是把分区内容变成一块只读的快照每次读取都会走 hash 树校验一旦检测到文件被改动就会触发 I/O 错误或者直接重启。到了 8.1验证链路里又多了一个关键角色——vbmeta 分区。Android Verified BootAVB会通过 vbmeta 保存 boot、system、vendor 等分区的 hash 期望值任何镜像的改动都会导致校验失败。所以就算你把 adbd 权限改好了直接对 system 分区执行写操作dm-verity 也会把写请求拒之门外。这也是为什么大家会先跑adb disable-verity再adb remount。disable-verity本质上是往设备里写一个“关闭验证”的标记下次启动时 fs_mgr 就不再对 system/vendor 做完整性校验。但这里有个死锁user 版本默认 adb root 都不通adb disable-verity根本执行不了所以必须先从 boot 层面打开权限。1.3 第三道门SELinux 和挂载状态检查就算前面两道门都过了8.1 上的 remount 还可能被 SELinux 拦下来。adb remount不是简单调一个mount(2)就完事adbd 会通过 fs_mgr 的fs_mgr_remount函数去重新挂载分区这个过程中需要访问 block 设备节点、需要 relabel 相关文件而 adbd 进程在 SELinux 里通常被限制在adbddomain不一定有权限对/dev/block/bootdevice/by-name/system这样的节点做写操作。常见表现是adb root通了adb disable-verity也没报错重启后adb remount却返回Permission denied看dmesg会看到一堆avc: denied { write } for ...的记录。这时候最直接的验证方法就是adb shell setenforce 0临时切到 permissive再试一次 remount如果成功了基本可以断定是 SELinux 策略的问题。这三道门加在一起给我的感觉就像进一栋大楼ro.debuggable是门禁卡dm-verity是保险柜钢板SELinux 是最后一道防盗门。想强制修改系统就得一层层打通。2. 动手前先选路线改 boot.img、build.prop 还是重编内核明确了拦截点接下来要回答一个很现实的问题到底改哪里我见过不少人直接去改/system/build.prop结果发现根本没有写权限这就成了“鸡生蛋”的死循环——我想 remount 改 system但改 system 需要 remount。实际可选的路线有三条各有各的适用场景。2.1 三条路线横向对比修改路线修改对象适用场景难度风险路线 Aboot.img 内的 default.prop 和 fstabuser/userdebug 版本有 fastboot 且能解锁中变砖风险中等需要刷 boot路线 B/system/build.prop 追加属性已经能读写 system 的 userdebug/eng 版本低低但无法绕过 dm-verity路线 C内核 defconfig/DTS重新编译有完整 BSP 源码的工程师高高改动面大路线 C 我没法给你通用步骤因为每个平台的 DTS 结构差异太大了而且对多数人来说根本没有源码。路线 B 则需要你先有写 system 分区的权限这在 user 版本上不成立。所以现实中真正走得通、也最常用的是路线 A直接解包刷机包里的 boot.img改 ramdisk 里的属性和 fstab再刷回去。2.2 为什么优先动 boot.img在 Android 8.1 的启动流程里内核启动后第一个执行的用户态进程是 init它会读取 ramdisk 里的default.prop把里面的属性写入系统属性服务adbd 也是在 init 解析属性后才启动的。也就是说adbd 的 root 能力和调试开关在启动极早期就已经定死了。而/system/build.prop要在 system 分区挂载之后才会被读取对于 user 版本来说system 分区默认只读、还有 dm-verity 校验你想改它必须先有权限可权限恰恰是它控制的这就是典型的死锁。明白了这个启动顺序就知道强制修改系统的第一刀必须砍在 boot.img 上而不是 system 镜像上。2.3 开工前必须确认的前置条件动手前有几件事必须提前确认否则很容易白忙活Bootloader 是否已解锁执行fastboot oem unlock或fastboot flashing unlock各厂商命令不同没有解锁的设备多数连fastboot flash boot都会被拒。确认分区方案执行fastboot getvar slot-count返回 2 是 A/B 设备返回 1 是传统 A-only。A/B 设备刷 boot 时要考虑两个 slot。备份原始 boot.img 和 vbmeta.img这是救命的我一般会直接adb pull或从官方固件包里抽出来放到电脑上单独存好。准备 Linux 环境或者 WSL解包 boot.img 需要cpio、lz4、mkbootimg这类工具Windows 下折腾起来非常痛苦。另外要说明一点8.1 还没有 dynamic partitionssuper 分区所以不用担心逻辑分区那套东西system、vendor、boot 都还是独立物理分区这反而让操作简单了不少。3. 核心实操解包并修改 boot.img在 8.1 上强制打开 adb remount铺垫了这么多现在进入正题。下面这套流程是我在一台 Android 8.1高通平台上实际跑通的核心思路就三步解包 boot.img改 default.prop 和 fstab重打包刷入。3.1 先拿到 boot.img如果手机已经有 root 权限直接通过 dd 把 boot 分区导出来adb shell dd if/dev/block/bootdevice/by-name/boot of/sdcard/boot.img bs2048 adb pull /sdcard/boot.img注意分区节点名各平台不一样先执行adb shell ls -l /dev/block/bootdevice/by-name/确认一下 boot 对应的实际节点。如果设备没有 root那就从官方刷机包里解出 boot.img或者用fastboot boot临时启动一个已 root 的 boot 后重新 dump。拿到后先验证一下文件file boot.img正常会输出类似Android bootimg, kernel (0x80008000), ramdisk (0x81000000)的信息也有的压缩格式会显示Android bootimg, ramdisk (0x82000000), ...只要能识别出来就没问题。3.2 解包 ramdisk我推荐用 magiskboot它比传统的unpack_bootimg省心得多能自动处理各种 boot header 版本和 ramdisk 压缩格式magiskboot unpack boot.img执行完会生成kernel、ramdisk.cpio或ramdisk.cpio.lz4、dtb以及记录了原 header 信息的文件。如果看到的是.lz4后缀先解压lz4 -d ramdisk.cpio.lz4 ramdisk.cpio然后解包 cpiomkdir root cd root cpio -idmv ../ramdisk.cpio解包后先别急着改执行ls看看目录结构重点关注default.prop和fstab.*比如 fstab.sdm845、fstab.goldfish在 8.1 上二者基本都在 ramdisk 根目录。3.3 修改 default.prop给 adbd 开门禁用文本编辑器打开default.prop重点看这几行ro.debuggable0 ro.secure1 ro.adb.secure1强制修改时把前三项都改掉ro.debuggable1 ro.secure0 ro.adb.secure0ro.debuggable1表示这是一个可调试的系统adbd 允许接收 root 请求ro.secure0让 adbd 直接以 root 身份运行这是 eng 版本的行为ro.adb.secure0则关闭了 adb 的 RSA 指纹授权省去每次连接都要点“允许调试”的麻烦。对于开发调试机来说这三个改动很实用。还有一个属性persist.sys.root_access有些厂商会用它做二次限制默认可能是 0 或 1最好也检查一下如果是 0改成 3允许 adb root 和应用 root。这里有个容易踩的坑8.1 的 init 在启动后期有可能会从/system/etc/prop.default或者/vendor/default.prop重新加载同名属性如果这些文件里写了ro.debuggable0会覆盖你刚才在 boot ramdisk 里改的值。所以改完 boot 后不要急着刷先看看这些路径是否还有覆盖项如果固件有要么一并改掉要么至少在改完的属性值后面加上平台自己的“兜底”逻辑。3.4 修改 fstab拆除 dm-verity 这个“保险柜”属性改完接下来处理分区校验标志。找到 ramdisk 下的fstab.*文件搜索verify关键字grep -n verify fstab.*典型的 system 分区行长这样/dev/block/bootdevice/by-name/system /system ext4 ro,barrier1 wait,verify/dev/block/bootdevice/by-name/vbmetaverify是 fs_mgr 的标志表示挂载后要启用 dm-verity 校验后面那个/dev/block/bootdevice/by-name/vbmeta是在 8.1 的 AVB 方案下用来指定 vbmeta 分区位置的通常写作verify或avbvbmeta。要强制让 remount 可用就得把verify和avb相关关键字全部去掉。改造后的行大致如下/dev/block/bootdevice/by-name/system /system ext4 ro,barrier1 waitvendor、product 分区如果也有verify/avb标志同样去掉。注意别一整行删掉只需移除验证标志保留挂载点、文件系统类型和 wait 这些基础参数。有朋友会问能不能不动 fstab只靠adb disable-verity可以但那是运行时方案依赖 adb 有足够权限而且每次重启后标记还在只是如果未来某次恢复出厂设置或者标记被清掉又会回到只读状态。直接从 fstab 源头去掉校验一劳永逸这也是“强制修改系统”和普通调试操作的本质区别。3.5 重打包并刷入ramdisk 改完后重新打包回 cpiofind . | cpio -o -H newc | gzip ../ramdisk.cpio.gz注意压缩格式要和原始 boot.img 保持一致如果原始 ramdisk 是 lz4那就要用lz4 -9 ../ramdisk.cpio生成.lz4文件。压缩格式不一致会导致内核无法正确解包 ramdisk直接开机卡第一屏。接下来我推荐直接交给 magiskboot 处理重打包它会自动尊重原 boot.img 的 header 版本、内核加载地址、页大小这些信息magiskboot repack boot.img执行完后会生成new-boot.img刷这个就行。如果你更习惯 mkbootimg也可以手动指定参数但一定要从原始 boot.img 里读出 cmdline 和 base 地址不然很容易刷出开不了机的包。刷入前如果你想保守一点先用fastboot boot临时启动一次不写 flash出了问题还能重启回原系统fastboot boot new-boot.img确认启动没问题后再正式刷入fastboot flash boot new-boot.imgA/B 设备建议两个 slot 都刷避免切 slot 后改动失效fastboot flash boot_a new-boot.img fastboot flash boot_b new-boot.img如果你的设备 vbmeta 分区之前是锁定校验状态的最好同时刷新 vbmeta 并关闭验证fastboot flash vbmeta vbmeta.img --disable-verity --disable-verification这步不是必须的因为 fstab 已经去掉了 verify但有些设备的 bootloader 在早期阶段就强制校验 vbmeta做到这一步更保险。刷完后重启先验证 adbd 状态adb shell getprop ro.debuggable adb shell getprop ro.secure如果分别输出1和0门禁已经打开。接着执行adb root adb disable-verity adb reboot重启后直接adb remount到这一步正常情况下 remount 就能成功了。4. 属性全部改对后 remount 依然失败的排查实况按理说 boot.img 改了、fstab 的 verify 也去掉了remount 应该一路绿灯。但现实永远比理想复杂。我在实际操作中至少遇到过四种“表面成功但实际失败”的情况这里完整复盘一下。4.1 adb root 报 “closed”属性又变回 0改完 boot 刷入后执行adb shell getprop ro.debuggable返回的还是 0adb root一直报closed。这种情况十有八九是属性在启动后期被覆盖了。排查链路先看内核 cmdline 有没有传入androidboot.debuggable0如果有它的优先级高于 ramdisk 里的 default.prop需要在刷机时通过修改 boot 的 cmdline 或者在内核 bootargs 里覆盖。再查/system/build.prop、/vendor/default.prop是否有同名属性。最后看 init.rc 或 vendor 平台的 init 脚本里有没有setprop ro.debuggable 0这种硬编码。解决办法是把能查到的覆盖项全部清掉或者在修改完这些文件后重新打包刷入。如果固件没有 vendor 分区重点看 system 分区里的 build.prop。4.2 remount 提示 Permission denieddmesg 全是 avc denied这是 SELinux enforcing 的典型表现。属性对了、fstab 也改了但 adbd 的 remount 操作被 SELinux 策略拦截因为adbddomain 默认没有对 block 设备节点的写权限。排查链路adb shell dmesg | grep avc如果看到类似type1400 audit(...): avc: denied { write } for pid... commadbd namesystem devmmcblk0p42 scontextu:r:adbd:s0 tcontextu:object_r:block_device:s0 tclassblk_file基本实锤。临时解法是先关闭 SELinuxadb shell setenforce 0再执行adb remount。如果想永久解决需要改 sepolicy 里 adbd 的策略给 adbd 增加对 block_device 的写权限这就必须走系统编译路线了。对于临时调试来说setenforce 0是最快的。4.3 remount 输出成功但往 /system 写文件还是 Read-only file system这种情况非常迷惑。adb remount没有报错cat /proc/mounts | grep system也显示 rw但touch /system/test就是提示只读。我的排查经历先看/proc/mounts里 system 行的挂载标志是不是真的包含 rwadb shell cat /proc/mounts | grep system如果显示ro,seclabel,relatime说明内核实际还是以只读方式挂上了多半是 fstab 没改全或者还有一个隐藏的 overlay 挂载。有些 8.1 设备把 system 分区通过 bind mount 或者 overlayfs 方式呈现底层还是只读的。另一个常见原因是 remount 成功后由于某个进程还在使用旧文件句柄系统做了延迟的写保护。我习惯的操作是adb shell stop adb shell mount -o rw,remount /system adb shell start先停掉框架再手动 remount写入测试文件后立刻重启验证避免框架缓存干扰判断。4.4 重启后又打回原形改过的文件全部消失如果你已经成功写入了文件但重启后改动全没了这不是错觉——因为adb remount本身是运行时操作它只是把当前系统重新挂载为可写并没有修改分区镜像内容。写入的数据写在分区上重启后如果 dm-verity 恢复、或者系统在关机时做了恢复都可能覆盖掉。更关键的是很多 8.1 设备在 remount 状态下写入的数据实际上写进了 A/B 分区的另一个 slot 或者被缓存在内存中重启后没有持久化。所以依赖 remount 做“永久修改”是不可靠的正确的固化方式是对 system 镜像本身做修改然后重新刷入镜像。我常用的固化流程是在 remount 可写状态下用dd把 system 分区整块导出来adb shell dd if/dev/block/bootdevice/by-name/system of/sdcard/system.img bs4096 adb pull /sdcard/system.img在 PC 上用mkdir system_root sudo mount -o loop system.img system_root挂载直接改里面的文件。改完卸载再fastboot flash system system.img刷回。这样改完重启后改动才真正保留。4.5 常见 remount 报错速查表报错信息可能原因处理方式adbd cannot run as root in production buildsro.debuggable0修改 boot ramdisk 里的 default.propadb: unable to connect for root: closed属性被 build.prop 或 init 脚本覆盖检查覆盖项统一修改remount not permittedro.secure1 且 adbd 无 root 权限设置 ro.secure0重启failed to remount partition: Permission deniedSELinux enforcing 或设备节点无权限setenforce 0检查 avc 日志Read-only file systemfstab 里的 verify 没清干净或内核强制只读确认 fstab 和 /proc/mounts 都已是 rwDevice or resource busy框架进程占用挂载点adb shell stop 后 remount再 start5. 固化修改、降低变砖风险的几个关键动作强制修改系统不是改完就算完能不能稳定复现、重启后还在不在、出问题怎么回头这些才是决定这套方案能不能落地的东西。5.1 用 fastboot boot 先试后刷我强烈建议每次打包好 new-boot.img 后不要直接fastboot flash而是先用fastboot boot new-boot.img临时跑一次。这个命令不会写入 boot 分区只是从内存引导一次非常适合用来验证“这个 boot.img 到底能不能开机、adb root 通不通、remount 能不能成功”。如果验证不通过重启设备就回到原来的系统不会造成变砖。我第一次操作时就是这么干的三步里面有一步参数写错了导致系统直接 bootloader 反复重启但因为有 tryboot 机制拔线重启就恢复了。后来我把这套流程固化成了习惯凡是涉及 boot 分区的改动一律先临时启动验证再刷入。5.2 关于 OTA 和验证机制的取舍强制修改 boot.img 并关闭 dm-verity 后设备整体的验证链就断了。具体表现是OTA 增量升级会校验失败因为升级脚本会检查当前系统的验证状态Play Integrity原 SafetyNet也会判定设备状态异常CTS 相关测试同样会挂。这些不是 bug是关闭验证后的必然结果。如果只是开发调试机问题不大但如果这台设备后续还要跑正式测试就得想清楚“临时调试”和“长期状态”之间的取舍。我的处理方式是保留原始 boot.img 和 vbmeta.img 的备份调试结束后随时刷回恢复验证链。另外提醒一句已经解锁 bootloader 的设备如果刷过非官方镜像很多厂商的fastboot oem lock会拒绝重新上锁或者上锁后无法开机。所以在修改前务必备份完整固件尤其是老设备全量刷机包的获取成本可比改一条属性高多了。5.3 我踩过几次坑之后的建议多说几句实在话。如果你手头设备只有 user 版本但又不想走解包 boot 这条路还有一个更省事的替代方案Android 8.1 上很多 Magisk 的 boot 修补方案也能达成类似效果它会在 ramdisk 里注入自己的初始化逻辑顺带帮你把ro.debuggable和验证状态处理掉然后你可以通过 Magisk 的终端或者模块来挂载 system 为可写。不过 Magisk 方案对 8.1 的兼容性不如新版本那么顺滑而且它主要还是解决 root 需求真到了要折腾 fstab 的层面倒不如直接手动改 boot至少每一步都知道自己做了什么。另外还有一个很基础的认知要强调能编 userdebug 版本就别硬改 user 版本。userdebug 系统天生就有ro.debuggable1你只需要adb root、adb disable-verity、adb remount三步就完事了完全不需要解包 boot。改 boot 是 user 版无奈之下的强制手段修改系统和修改系统镜像文件之间还是有本质区别的前者适合临时验证后者才适合产出交付物。最后分享一个我后来一直在用的工作流拿到一台 8.1 设备需要改系统时先确认能不能编译 userdebug能就编译不能就优先fastboot boot一个临时 boot 验证方案用adb push把文件推到/data/local/tmp配合 root 权限直接覆盖到系统目录这个办法在很多场景下比 remount 更轻量、更不容易把系统搞坏。只有到了必须修改挂载标志或者分区镜像的时候再动用完整解包重打包的流程。这样既控制风险又能快速完成验证效率比直接硬刚 remount 高得多。
返回列表