magiskinit 原理探究 测试设备:pixel 7 pro
magisk 版本: 27000
从流程上看
安装magisk apk,使用magisk修补init_boot.img
将修补后的init_boot.img 刷入手机
重启手机, 完成root
想要理解为什么,我认为有两个目标:
理解init_boot.img 在android 发挥的作用
被修改后的镜像,做了哪些特殊行为
magisk debug环境搭建 能对magisk init插入一些日志,验证分析。
官网安装debug版本的magisk 。
clone magisk 的源码,参考文档 ,搭建好编译环境
编译apk ,将编译的apk 重命名为zip 通过magisk刷入到手机,安装成功。
重启设备,查看magiskinit日志。
1 2 3 4 5 6 7 8 [ 0.433073] magiskinit: Device config: [ 0.433085] magiskinit: skip_initramfs=[0] [ 0.433092] magiskinit: force_normal_boot=[1] [ 0.433098] magiskinit: rootwait=[0] [ 0.433104] magiskinit: slot=[_a] [ 0.433111] magiskinit: dt_dir=[/proc/device-tree/firmware/android] [ 0.433117] magiskinit: fstab_suffix=[] [ 0.433122] magiskinit: hardware=[cheetah]
理解init_boot.img 的作用 “android对bootloader行为的说明” 说到 ,bootloader 加载init_boot.img, boot.img 。
将内核加载内存,并且在内存中执行
加载ramdisks和bootconfig 到内存创建initramfs
其中ramdisk 就在init_boot.img ,ramdisk中有init 进程 ,内核启动后,会执行initramfs 根目录的/init,进行第一阶段,完成目录挂载、系统环境的搭建后,切换根目录到/system , 执行/system/bin/init 开始第二阶段,初始化用户态环境 ,继续启动系统。init的这个过程就是2SI。
这里理解init_boot.img 提供initramfs 环境 、init程序。
修补init_boot.img 分析源码,修补镜像功能所在代码com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt > MagiskInstallImpl。
核心功能patchBoot :
1 2 3 4 5 6 7 8 9 val cmds = arrayOf( "cd $installDir " , "KEEPFORCEENCRYPT=${Config.keepEnc} " + "KEEPVERITY=${Config.keepVerity} " + "PATCHVBMETAFLAG=${Info.patchBootVbmeta} " + "RECOVERYMODE=${Config.recovery} " + "LEGACYSAR=${Info.legacySAR} " + "sh boot_patch.sh $srcBoot " ) val isSuccess = cmds.sh().isSuccess
准备一些环境变量,用boot_patch.sh 去patch init_boot.img,
1 2 3 4 5 6 7 8 9 10 11 12 ./magiskboot cpio ramdisk.cpio \ "add 0750 $INIT magiskinit" \ "mkdir 0750 overlay.d" \ "mkdir 0750 overlay.d/sbin" \ "$SKIP32 add 0644 overlay.d/sbin/magisk32.xz magisk32.xz" \ "$SKIP64 add 0644 overlay.d/sbin/magisk64.xz magisk64.xz" \ "add 0644 overlay.d/sbin/stub.xz stub.xz" \ "patch" \ "$SKIP_BACKUP backup ramdisk.cpio.orig" \ "mkdir 000 .backup" \ "add 000 .backup/.magisk config" \ || abort "! Unable to patch ramdisk"
将patch后的ramdisk 和原始文件对比,前者多了.overlay.d/sbin/magisk*.xz 和.overlay.d/sbin/stub.xz ; 并且init 文件不同;此时init 为magiskinit进程。
1 2 #解压命令 cpio -i -d < ramdisk.cpio
magiskinit 代码分析 已经搭建好了magisk debug环境,直接看看日志,magisk做了什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 [ 0.433073] magiskinit: Device config: [ 0.433085] magiskinit: skip_initramfs=[0] [ 0.433092] magiskinit: force_normal_boot=[1] [ 0.433098] magiskinit: rootwait=[0] [ 0.433104] magiskinit: slot=[_a] [ 0.433111] magiskinit: dt_dir=[/proc/device-tree/firmware/android] [ 0.433117] magiskinit: fstab_suffix=[] [ 0.433122] magiskinit: hardware=[cheetah] [ 0.433128] magiskinit: hardware.platform=[gs201] [ 0.433132] magiskinit: emulator=[0] [ 0.433140] magiskinit: FirstStageInit [ 0.433145] magiskinit: Setup data tmp [ 0.434591] magiskinit: unxz /.backup/init.xz -> /.backup/init ?? hah 不知到啥时候还存了个init.xz [ 0.614553] magiskinit: Patch @ 0000F39F [/system/bin/init] -> [/data/magiskinit] [ 0.614831] magiskinit: Unmount [/sys] [ 0.614916] magiskinit: Unmount [/proc] [ 1.523679] magiskinit: SecondStageInit [ 1.523791] magiskinit: Setup Magisk tmp at /debug_ramdisk [ 1.527170] magiskinit: Setup persist: [sda1] (8, 1) [ 1.530972] magiskinit: preinit: .magisk/mirror/preinit/magisk [ 1.534912] magiskinit: Patching init.rc in /system/etc/init/hw [ 1.535883] magiskinit: Inject magisk rc [ 1.535951] magiskinit: Patching init.zygote64.rc in /system/etc/init/hw [ 1.536340] magiskinit: Inject zygote restart [ 1.536385] magiskinit: Patching init.zygote64_32.rc in /system/etc/init/hw [ 1.536617] magiskinit: Inject zygote restart [ 1.536649] magiskinit: Patching init.zygote32.rc in /system/etc/init/hw [ 1.536867] magiskinit: Inject zygote restart [ 1.552893] magiskinit: Hijack [/sys/fs/selinux/load] [ 1.552912] magiskinit: Hijack [/sys/fs/selinux/enforce] [ 1.553672] magiskinit: Mount [.magisk/rootdir/system/etc/init/hw/init.zygote32.rc] -> [/system/etc/init/hw/init.zygote32.rc] [ 1.553707] magiskinit: Mount [.magisk/rootdir/system/etc/init/hw/init.zygote64_32.rc] -> [/system/etc/init/hw/init.zygote64_32.rc] [ 1.553721] magiskinit: Mount [.magisk/rootdir/system/etc/init/hw/init.zygote64.rc] -> [/system/etc/init/hw/init.zygote64.rc] [ 1.553741] magiskinit: Mount [.magisk/rootdir/system/etc/init/hw/init.rc] -> [/system/etc/init/hw/init.rc] [ 1.554586] magiskinit: Unmount [/data] [ 1.637197] magiskinit: Load policy from: .magisk/selinux/load
结合代码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class FirstStageInit : public BaseInit {private : void prepare () ; public : FirstStageInit (char *argv[], BootConfig *config) : BaseInit (argv, config) { LOGD ("%s\n" , __FUNCTION__); }; void start () override { prepare (); exec_init (); } }; void FirstStageInit::prepare () { prepare_data (); restore_ramdisk_init (); auto init = mmap_data ("/init" , true ); for (size_t off : init.patch (INIT_PATH, REDIR_PATH)) { LOGD ("Patch @ %08zX [" INIT_PATH "] -> [" REDIR_PATH "]\n" , off); } } void BaseInit::exec_init () { for (auto &p : reversed) { if (xumount2 (p.data (), MNT_DETACH) == 0 ) LOGD ("Unmount [%s]\n" , p.data ()); } execv ("/init" , argv); exit (1 ); }
prepare将当前/init 复制到/data/magiskinit ,还原/init为原init 。然后重定位/system/bin/init 为/data/magiskinit ,修改/init程序中 /system/bin/init 字符串为/data/magiskinit。
第二阶段代码执行如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class SecondStageInit : public MagiskInit {private : bool prepare () ; public : SecondStageInit (char *argv[]) : MagiskInit (argv) { LOGD ("%s\n" , __FUNCTION__); }; void start () override { bool is_rootfs = prepare (); if (is_rootfs) patch_rw_root (); else patch_ro_root (); exec_init (); } }; bool SecondStageInit::prepare () { umount2 ("/init" , MNT_DETACH); unlink ("/data/init" ); argv[0 ] = (char *) INIT_PATH; struct statfs sfs{}; statfs ("/" , &sfs); if (sfs.f_type == RAMFS_MAGIC || sfs.f_type == TMPFS_MAGIC) { unlink ("/init" ); xsymlink (INIT_PATH, "/init" ); return true ; } return false ; } void MagiskInit::patch_rw_root () { mount_list.emplace_back ("/data" ); parse_config_file (); mkdir ("/root" , 0777 ); clone_attr ("/sbin" , "/root" ); link_path ("/sbin" , "/root" ); load_overlay_rc ("/overlay.d" ); mv_path ("/overlay.d" , "/" ); rm_rf ("/data/overlay.d" ); rm_rf ("/.backup" ); patch_rc_scripts ("/" , "/sbin" , true ); bool treble; { auto init = mmap_data ("/init" ); treble = init.contains (SPLIT_PLAT_CIL); } xmkdir (PRE_TMPSRC, 0 ); xmount ("tmpfs" , PRE_TMPSRC, "tmpfs" , 0 , "mode=755" ); xmkdir (PRE_TMPDIR, 0 ); setup_tmp (PRE_TMPDIR); chdir (PRE_TMPDIR); extract_files (true ); if ((!treble && access ("/sepolicy" , F_OK) == 0 ) || !hijack_sepolicy ()) { patch_sepolicy ("/sepolicy" , "/sepolicy" ); } chdir ("/" ); cp_afc (REDIR_PATH, "/sbin/magisk" ); }
创建/debug_ramdisk 存放文件magisk 相关文件并且patch init.rc , patch sepolicy , 执行/system/bin/init 。
完整的控制流,/magiskinit -> /init 一阶段 -> /data/magiskinit 二阶段 -> /system/bin/init 二阶段。可以知道magiskinit 通过劫持init进程,实现root权限 去修改配置、启动root权限服务的功能。
patch init.rc patch代码入口 , 主要看看init.rc 的patch ,zygote 的patch 类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 static void patch_rc_scripts (const char *src_path, const char *tmp_path, bool writable) { auto src_dir = xopen_dir(src_path); if (!src_dir) return ; int src_fd = dirfd(src_dir.get()); auto dest_dir = writable ? [&] { return xopen_dir(src_path); }() : [&] { char buf[PATH_MAX] = {}; ssprintf(buf, sizeof (buf), ROOTOVL "%s" , src_path); xmkdirs(buf, 0755 ); return xopen_dir(buf); }(); if (!dest_dir) return ; int dest_fd = dirfd(dest_dir.get()); { auto src = xopen_file(xopenat(src_fd, INIT_RC, O_RDONLY | O_CLOEXEC, 0 ), "re" ); if (!src) return ; if (writable) unlinkat(src_fd, INIT_RC, 0 ); auto dest = xopen_file( xopenat(dest_fd, INIT_RC, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0 ), "we" ); if (!dest) return ; LOGD("Patching " INIT_RC " in %s\n" , src_path); file_readline(false , src.get(), [&dest](string_view line) -> bool { if (str_contains(line, "start vaultkeeper" )) { LOGD("Remove vaultkeeper\n" ); return true ; } if (line.starts_with("service flash_recovery" )) { LOGD("Remove flash_recovery\n" ); fprintf (dest.get(), "service flash_recovery /system/bin/true\n" ); return true ; } if (line.starts_with("on property:persist.sys.zygote.early=" )) { LOGD("Invalidate persist.sys.zygote.early\n" ); fprintf (dest.get(), "on property:persist.sys.zygote.early.xxxxx=true\n" ); return true ; } fprintf (dest.get(), "%s" , line.data()); return true ; }); fprintf (dest.get(), "\n" ); for (auto &script : rc_list) { replace_all(script, "${MAGISKTMP}" , tmp_path); fprintf (dest.get(), "\n%s\n" , script.data()); } rc_list.clear(); rust::inject_magisk_rc(fileno(dest.get()), tmp_path); fclone_attr(fileno(src.get()), fileno(dest.get())); } }
实现patch init 核心三部曲:
拷贝/system/etc/init/hw/init.rc 的内容到/debug_randisk/.magisk/rootdir/system/etc/init/hw/init.rc
注入启动magiskd 服务的代码到/debug_randisk/.magisk/rootdir/system/etc/init/hw/init.rc
bind mount 将/debug_randisk/roodit 到 /
但是呢,实际手机并没有 /debug_randisk/rootdir,因为magiskd 将该目录删除 ,可以通过注释该代码找到文件。
patch selinux