Android10以后,Android系统限制了System分区的修改,结果就是,即使你i是自己编译的Android系统,即使是有做高的root权限,你依然无法挂载System分区并对其内容进行修改,尽管网上有各种帖子说可以使用mount -o rw,remount /
,但这并没有解决开发者的实际问题。这意味着传统的万能root出现局限性,为了解决这个问题Magisk的作者Top John Wu
在推文中确认了这一问题,并确认了导致该问题的原因是谷歌在Android10以后引入了EXT4 共享块
,而这个共享块和其他分区的区别在于根本没有可用空间的概念,所以也就没办法挂在为可读写。为了解决这个问题,Magisk团队发现可以重定向文件读取时的文件路径来实现修改的目的,这似乎和我们Hook有相似之处,而这个,被Top John Wu
称为Systemless(无System分区)而由此概念引申出来的Root方案,也叫做systemless root。。
Magisk文件结构
Magisk系统包含上层的控制App以及下层的可执二进制文件以及一些相关配置或者数据文件。我们从底层往上层看会更容易明白Magisk的功能构成和架构思维。
首先,Magisk 会挂载一个tmpfs目录来存放一些临时数据。在Android11以下,这个目录时sbin,从 Android 11 开始,/sbin文件夹可能不存在,那么 Magisk 会在/dev下随机创建一个文件夹并将其作为Magisk的Root文件夹。
放在sbin或者dev下的原因是:/sbin或/dev目录非su权限不可读,因此第三方APP无法检测。
我们可以通过在adb shell下使用magisk --path
打印当前Magisk使用的目录:
找到magisk目录
blueline:/ # magisk --path
/dev/bNpnxq
文件列表
接下来我们看下这个目录下都有什么文件,以及如何解读这些文件;
blueline:/dev/bNpnxq # ls -al
total 720
drwx------ 3 root root 200 2022-12-24 21:41 .
drwxr-xr-x 24 root root 6200 2022-12-25 01:40 ..
drwxr-xr-x 8 root root 180 2022-12-24 21:41 .magisk
lrwxrwxrwx 1 root root 10 1970-02-17 10:04 magisk -> ./magisk64
-rwxr-xr-x 1 root root 154452 1970-02-17 10:04 magisk32
-rwxr-xr-x 1 root root 247168 1970-02-17 10:04 magisk64
-rwxr-xr-x 1 u0_a206 u0_a206 328240 2022-12-24 21:41 magiskpolicy
lrwxrwxrwx 1 root root 8 1970-02-17 10:04 resetprop -> ./magisk
lrwxrwxrwx 1 root root 8 1970-02-17 10:04 su -> ./magisk
lrwxrwxrwx 1 root root 14 1970-02-17 10:04 supolicy -> ./magiskpolicy
可以看到除了magiskpolicy以外,其他都属于root用户组。
下面的注释说得很清楚了,直接看代码就好了。
# 想要获取 Magisk 正在使用的当前Base文件夹,请使用命令 `magisk --path`。
# 像 magisk、magiskinit 这样的二进制文件和所有指向小程序的符号链接都直接存储在这个路径中。
# 这意味着当这是 /sbin 时,这些二进制文件将直接位于 PATH 中。
MAGISKBASE=$(magisk --path)
# Magisk 内部文件
MAGISKTMP=$MAGISKBASE/.magisk
# Magisk 的 BusyBox 目录。
# 在此文件夹中存储 busybox 二进制文件和指向其所有小程序的符号链接。
# 此目录的任何用法已弃用,请直接调用 /data/adb/magisk/busybox 并使用 BusyBox 的 ASH 独立模式。
# 将来会删除此路径的创建。
$MAGISKTMP/busybox
# /data/adb/modules 将mount在这里。 原始文件夹未被使用(由于 nosuid 挂载标志)。
$MAGISKTMP/modules
# 当前的 Magisk 安装配置
$MAGISKTMP/config
# 分区镜像
# 应用在访问系统文件的时候会优先访问该目录下的文件,以达到狸猫换太子的目的
# 例如 system,system_ext,vendor,data......
$MAGISKTMP/mirror
# Magisk 内部创建block设备来挂载镜像,和mirror对应,对应关系可通过ls -al列出
$MAGISKTMP/block
# Root 目录补丁文件
# 位于 system-as-root 设备上,/ 不可写。
# 所有预初始化补丁文件都放在这里并mount
$MAGISKTMP/rootdir
下面是
/dev/bNpnxq/.magisk/mirror
列出的文件列表,对应block的设备节点
blueline:/dev/bNpnxq/.magisk/block # ls -al
total 0
d--------- 2 root root 160 2022-12-24 21:41 .
drwxr-xr-x 8 root root 180 2022-12-24 21:41 ..
brw------- 1 root root 253, 8 2022-12-24 21:41 data
brw------- 1 root root 259, 4 1970-02-17 10:04 metadata
brw------- 1 root root 253, 7 2022-12-24 21:41 product
brw------- 1 root root 253, 5 2022-12-24 21:41 system_ext
brw------- 1 root root 253, 4 2022-12-24 21:41 system_root
brw------- 1 root root 253, 6 2022-12-24 21:41 vendor
blueline:/dev/bNpnxq/.magisk/block # cd ../mirror/
blueline:
####################################
/dev/bNpnxq/.magisk/mirror # ls -al
total 24
d--------- 7 root root 220 2022-12-24 21:41 .
drwxr-xr-x 8 root root 180 2022-12-24 21:41 ..
drwxrwx--x 50 system system 4096 2022-12-24 21:41 data
lrwxrwxrwx 1 root root 9 2022-12-24 21:41 metadata -> /metadata
lrwxrwxrwx 1 root root 19 2022-12-24 21:41 persist -> /mnt/vendor/persist
drwxr-xr-x 14 root root 4096 2009-01-01 08:00 product
lrwxrwxrwx 1 root root 17 1970-02-17 10:04 sepolicy.rules -> ./metadata/magisk
lrwxrwxrwx 1 root root 20 2022-12-24 21:41 system -> ./system_root/system
drwxr-xr-x 9 root root 4096 2009-01-01 08:00 system_ext
drwxr-xr-x 26 root root 4096 2009-01-01 08:00 system_root
drwxr-xr-x 20 root shell 4096 2009-01-01 08:00 vendor
配置和模块(/data/adb目录)
不要被目录名迷惑,虽然是adb实际上是Magisk的一个应用目录,这个目录里面保存了Magisk的一些数据和配置文件,安装的模块也是被保存在这里,之所以使用这个目录是有它的优势的:
- 该目录对于任何出厂设备来说都存在,第三方APP无法据此检测Magisk。
- 该目录权限默认为700,所有者为root,因此第三方APP无法进入和读写。
- 该目录的安全上下文secontext是u:object_r:adb_data_file:s0,很少有进程有该权限。
- 该目录在Device Encrypted (DE) storage,因此当FBE (File-Based Encryption) 设备在Direct Boot mode时或者解锁锁屏后即可使用。
这个目录下包含以下文件(配合Magisk启动流程来看更容易理解):
SECURE_DIR=/data/adb
# 存储一些 post-fs-data 后需要执行的脚本的文件夹
$SECURE_DIR/post-fs-data.d
# 存放通用 late_start 服务脚本的文件夹
$SECURE_DIR/service.d
# Magisk 模块目录
$SECURE_DIR/modules
# 待升级的 Magisk 模块
# 因为模块文件在挂载时修改是不安全的
# 通过 Magisk 应用程序安装的模块将存储在这里,并在下次重启时合并到 $SECURE_DIR/modules
$SECURE_DIR/modules_update
# 数据库存储应用设置和root授权日志
MAGISKDB=$SECURE_DIR/magisk.db
# 所有与 magisk 相关的二进制文件,包括 busybox、scripts和 magisk 二进制文件。 用于支持模块安装、addon.d、Magisk应用程序等。
DATABIN=$SECURE_DIR/magisk
Magisk启动过程
Magisk启动分为以下几步:Pre-Init,post-fs-data,late_start,Resetprop,这几个启动流程我们可以对应上Android系统启动,下面我详细说明一下:
Pre-Init 阶段
使用 magiskinit替换init并执行:
- 先挂载所需的分区。 在传统的 system-as-root 设备上,切换root到/system;在2SI 设备上(使用systemless), 将 init 文件重定向到 magiskinit 并执行(原理是我们在patch boot的时候会重定向原来的init步骤 ),以此来挂载所需的分区。
- 在 init.rc注入magisk服务
- 对于使用 monolithic (整体的)安全策略的设备,从 /sepolicy读取安全策略;对于其它设备,用FIFO劫持selinuxfs nodes,设置 LD_PRELOAD 来hook security_load_policy ,并且协助劫持2SI 设备,并且启动daemon直到init尝试读取sepolicy。
- Patch sepolicy 规则。如果使用 “劫持” 方法, 则将修补的sepolicy载入kernel, 然后解除init 劫持并退出daemon。
- 执行原始的 init 来执行后续的启动过程
post-fs-data阶段
post-fs-data
过程在 /data 解密并挂载后触发。首先会启动守护进程 magiskd
,执行post-fs-data
脚本,这个时候模块文件就挂载完毕了。
late_start阶段
在启动过程的后期, late_start 类会被触发,开启Magisk “service” 模式。服务脚本会在这个模式下执行。
到这里Magisk就完全启动了,上面就是App了,他只是负责一些配置的修改和状态显示,以及调用前面启动的服务和程序来执行,大概就是这样的过程。
修改属性(Resetprop)
正常来说,只有init能修改系统属性,非root进程只能读取无法修改。在有root时可以通过由init提供的property_service发送请求(比如adb可以使用setprop命令)来实现修改,但是通过这种方式不能修改和删除只读属性(以ro.开头的属性,例如ro.build.product),除非修改系统源码。
resetprop 通过提取和patch AOSP中和系统属性相关的源代码,从而允许直接修改属性区域(prop_area),不再需要通过property_service来修改系统属性(实际上这个功能和Github上开源的mprop的原理类似)。不过也正是因为绕过了property_service,所以需要有下面的注意事项:
-
触发事件的修复:由于绕过了property_service,所以当属性改变时,在*.rc脚本中注册的on property:foo=bar 动作事件不会被触发(在init语言中这个叫做属性触发器)。但Mgisk考虑到了这点,所以默认情况下 resetprop设置属性时会和setprop一样,它会触发事件(通过先删除属性再通过property_service来设置属性来实现)。如果不想触发动作事件,可以使用-n参数禁用。
-
实现重启也保留修改:持久属性(以persist.开头的属性,例如persist.sys.usb.config)在 prop_area 和 /data/property都有存储。默认情况下,删除持久属性时不会把它从持久化存储中移除,也就是说下次重启时持久属性会被恢复; getprop在不会从持久化存储中读取持久属性。但是对于resetprop ,可以使用-p参数,这样删除时会同时将该属性从 prop_area 和 /data/property中移除,读取时也会同时从prop_area 和 持久化存储中读取。
SELinux 策略
Magisk通过patch原来的sepolicy来确保Magisk的操作可以安全地执行。新的magisk域权限很高, magiskd和所有root shell都会在该域中运行。magisk_file是一个新的文件类型,该文件类型允许每一个域访问(不受限制的文件上下文),下面这个magisk文件用的就是magisk_file
上下文。
blueline:/data/adb # ls -Zl
total 52
drwxr-xr-x 3 root root u:object_r:magisk_file:s0 3488 2022-12-24 21:41 magisk
-rw------- 1 root root u:object_r:adb_data_file:s0 40960 2022-12-27 02:22 magisk.db
drwxr-xr-x 2 root root u:object_r:system_file:s0 3488 2022-12-24 21:41 modules
drwxr-xr-x 2 root root u:object_r:adb_data_file:s0 3488 2022-12-24 21:41 post-fs-data.d
drwxr-xr-x 2 root root u:object_r:adb_data_file:s0 3488 2022-12-24 21:41 service.d
在 Android 8.0之前,所有被su允许的客户端域都可以直接连接到 magiskd 并且与守护进程连接,从而获得远程的root shell访问。Magisk同时也需要释放一些ioctl 操作以便root shell可以正常工作。
但是,在Android 8.0及以后,为了避免Android沙箱中对规则的放松,Magisk实现了一个新的SELinux模型。 magisk二进制文件被标记为magisk_exec文件类型,进程在以su client的域运行时执行 magisk二进制文件(包括su命令)时会被中转为 magisk_client(通过使用一个type_transition规则)。
规则严格限制了magisk域的进程才被允许归属于magisk_exec文件类型。不再允许直接通过socket连接至magiskd;唯一的访问该守护进程的方式就是通过一个magisk_client 进程。这样做确保了沙箱的完整性,让Magisk专用规则从其它规则中分离。如下magisk64:
blueline:/dev/bNpnxq # ls -lZ
total 720
lrwxrwxrwx 1 root root u:object_r:system_file:s0 10 1970-02-17 10:04 magisk -> ./magisk64
-rwxr-xr-x 1 root root u:object_r:system_file:s0 154452 1970-02-17 10:04 magisk32
-rwxr-xr-x 1 root root u:object_r:magisk_exec:s0 247168 1970-02-17 10:04 magisk64
-rwxr-xr-x 1 u0_a206 u0_a206 u:object_r:app_data_file:s0:c206,c256,c512,c768 328240 2022-12-24 21:41 magiskpolicy
lrwxrwxrwx 1 root root u:object_r:system_file:s0 8 1970-02-17 10:04 resetprop -> ./magisk
lrwxrwxrwx 1 root root u:object_r:system_file:s0 8 1970-02-17 10:04 su -> ./magisk
lrwxrwxrwx 1 root root u:object_r:system_file:s0 14 1970-02-17 10:04 supolicy -> ./magiskpolicy
关于更加详细规则可以在 magiskpolicy/rules.cpp
中找到。