0%

Magisk源码分析(二)

对 Magisk 源码做一个简单的分析,本人很菜,分析不到位的地方请各位大神指出来。本文以 Magisk v25.1 为例

Patch boot.img

当使用 Magisk root 的时候,一般操作都是提取内核文件进行修补。直接上代码分析

关于修补内核的代码在 com.topjohnwu.magisk.core.tasks 类

1
protected fun patchFile(file: Uri) = extractFiles() && handleFile(file)

继续跟进,排除一些无关紧要的逻辑后,来到这里.

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
private fun patchBoot(): Boolean {
var isSigned = false
if (!srcBoot.isCharacter) {
try {
srcBoot.newInputStream().use {
if (SignBoot.verifySignature(it, null)) {
isSigned = true
console.add("- Boot image is signed with AVB 1.0")
}
}
} catch (e: IOException) {
console.add("! Unable to check signature")
Timber.e(e)
return false
}
}

val newBoot = installDir.getChildFile("new-boot.img")
if (!useRootDir) {
// Create output files before hand
newBoot.createNewFile()
File(installDir, "stock_boot.img").createNewFile()
}

val cmds = arrayOf(
"cd $installDir",
"KEEPFORCEENCRYPT=${Config.keepEnc} " +
"KEEPVERITY=${Config.keepVerity} " +
"PATCHVBMETAFLAG=${Config.patchVbmeta} " +
"RECOVERYMODE=${Config.recovery} " +
"sh boot_patch.sh $srcBoot")

if (!cmds.sh().isSuccess)
return false

val job = shell.newJob().add("./magiskboot cleanup", "cd /")

if (isSigned) {
console.add("- Signing boot image with verity keys")
val signed = File.createTempFile("signed", ".img", context.cacheDir)
try {
val src = newBoot.newInputStream().buffered()
val out = signed.outputStream().buffered()
withStreams(src, out) { _, _ ->
SignBoot.doSignature(null, null, src, out, "/boot")
}
} catch (e: IOException) {
console.add("! Unable to sign image")
Timber.e(e)
return false
}
job.add("cat $signed > $newBoot", "rm -f $signed")
}
job.exec()
return true
}

具体逻辑在 boot_patch.sh 里
Magisk\scripts\boot_patch.sh

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
.....
##################
# Ramdisk Patches
##################

ui_print "- Patching ramdisk"

echo "KEEPVERITY=$KEEPVERITY" > config
echo "KEEPFORCEENCRYPT=$KEEPFORCEENCRYPT" >> config
echo "PATCHVBMETAFLAG=$PATCHVBMETAFLAG" >> config
echo "RECOVERYMODE=$RECOVERYMODE" >> config
[ ! -z $SHA1 ] && echo "SHA1=$SHA1" >> config

# Compress to save precious ramdisk space
SKIP32="#"
SKIP64="#"
if [ -f magisk32 ]; then
./magiskboot compress=xz magisk32 magisk32.xz
unset SKIP32
fi
if [ -f magisk64 ]; then
./magiskboot compress=xz magisk64 magisk64.xz
unset SKIP64
fi

./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" \
"patch" \
"backup ramdisk.cpio.orig" \
"mkdir 000 .backup" \
"add 000 .backup/.magisk config"

rm -f ramdisk.cpio.orig config magisk*.xz
.....

add 0750 $INIT magiskinit 可以看出来,magisk 对内核的修补其实是用 magisk 替换了系统的 init 文件,添加 magisk64.xz 或 magisk32.xz 文件.
并且把原 init 文件放置到.backup 目录下 (这部分代码我没找到,但是 magiskinit 在代码里可以看出来).

magiskinit 注入 init.rc

Magisk\native\jni\init\init.cpp
主要逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main(int argc, char *argv[]) {
.......
if (argc > 1 && argv[1] == "selinux_setup"sv) {
init = new SecondStageInit(argv);
} else {
// This will also mount /sys and /proc
load_kernel_info(&config);//收集系统信息,在日志中有输出

if (config.skip_initramfs)//mi12 里是0
init = new LegacySARInit(argv, &config);//不执行,暂不分析
else if (config.force_normal_boot)
init = new FirstStageInit(argv, &config);//FirstStageInit
else if (access("/sbin/recovery", F_OK) == 0 || access("/system/bin/recovery", F_OK) == 0)
init = new RecoveryInit(argv, &config);//RecoveryInit,不执行,暂不分析
else if (check_two_stage())//判断是否是FirstStageInit
init = new FirstStageInit(argv, &config);
else
init = new RootFSInit(argv, &config);//不执行,暂不分析
}

// Run the main routine
init->start();
exit(1);
}

结合日志分析

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
[    0.317377] magiskinit: Device config:
[ 0.317386] magiskinit: skip_initramfs=[0]
[ 0.317390] magiskinit: force_normal_boot=[1]
[ 0.317394] magiskinit: rootwait=[1]
[ 0.317397] magiskinit: slot=[_b]
[ 0.317401] magiskinit: dt_dir=[/proc/device-tree/firmware/android]
[ 0.317404] magiskinit: fstab_suffix=[default]
[ 0.317407] magiskinit: hardware=[qcom]
[ 0.317409] magiskinit: hardware.platform=[]
[ 0.317412] magiskinit: emulator=[0]
[ 0.317415] magiskinit: FirstStageInit
[ 0.317783] magiskinit: Replace [/system/bin/init] -> [/data/magiskinit]
[ 0.324376] magiskinit: Unmount [/sys]
[ 0.324441] magiskinit: Unmount [/proc]
[ 0.792166] magiskinit: SecondStageInit
[ 0.794063] magiskinit: patch_ro_root
[ 0.794109] magiskinit: Setup Magisk tmp at /dev/0hsz
[ 0.798367] magiskinit: Setup userdata: [sda38] (259, 22)
[ 0.799558] magiskinit: mount /dev/0hsz/.magisk/block/data->/dev/0hsz/.magisk/mirror/data failed with 22: Invalid argument
[ 0.873824] magiskinit: Setup metadata: [sda17] (259, 1)
[ 0.883925] magiskinit: Inject magisk services: [ryq6EKuI] [VFzLTmmD]
[ 0.890089] magiskinit: Replace [d30138f2310a9fb9c54a3e0c21f58591] -> [L5mAeRz9]
[ 0.897612] magiskinit: Replace [d30138f2310a9fb9c54a3e0c21f58591] -> [L5mAeRz9]
[ 0.904678] magiskinit: Hijack [/sys/fs/selinux/load]
[ 0.904694] magiskinit: Hijack [/sys/fs/selinux/enforce]
[ 0.905782] magiskinit: Load custom sepolicy patch: [/dev/0hsz/.magisk/mirror/metadata/magisk/zygisk_shamiko/sepolicy.rule]
[ 0.906361] magiskinit: Mount [.magisk/rootdir/system/etc/init/hw/init.rc] -> [/system/etc/init/hw/init.rc]
[ 0.906400] magiskinit: Unmount [/dev/0hsz/.magisk/mirror/system_root]
[ 0.906409] magiskinit: Unmount [/dev/0hsz/.magisk/mirror/metadata]
[ 1.018016] magiskinit: Load policy from: .magisk/selinux/load

从日志可以分析,我的 magiskinit 执行了 FirstStageInit 和 SecondStageInit, 那就以这俩函数开始分析
先说明下,magiskinit 是以代理系统 init 的方式进行的,所以有 FirstStageInit 和 SecondStageInit 类

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();//执行原系统init
}
};

class SecondStageInit : public SARBase {
private:
bool prepare();
public:
SecondStageInit(char *argv[]) : SARBase(argv) {
setup_klog();
LOGD("%s\n", __FUNCTION__);
};

void start() override {
if (prepare())
patch_rw_root();//不执行,暂不分析
else
patch_ro_root();
exec_init();//执行原系统init
}
};

FirstStageInit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
......
#define INIT_PATH "/system/bin/init"
#define REDIR_PATH "/data/magiskinit"

void FirstStageInit::prepare() {
xmkdirs("/data", 0755);
xmount("tmpfs", "/data", "tmpfs", 0, "mode=755");
cp_afc("/init" /* magiskinit */, REDIR_PATH);//把/init(magiskinit) copy到/data/magiskinit

unlink("/init");
const char *orig_init = backup_init();
if (access(orig_init, F_OK) == 0) {
xrename(orig_init, "/init");//还原系统init到/init路径
} else {
......
}
{
auto init = mmap_data("/init", true);
// Redirect original init to magiskinit
init.patch({ make_pair(INIT_PATH, REDIR_PATH) });//patch掉原init中所有/system/bin/init为/data/magiskinit,因为原init会反复调用execv来执行/system/bin/init.
}
......
}

SecondStageInit

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
#define ROOTMIR     MIRRDIR "/system_root"
#define NEW_INITRC "/system/etc/init/hw/init.rc"

void SARBase::patch_ro_root() {
string tmp_dir;

LOGD("patch_ro_root\n");

if (access("/sbin", F_OK) == 0) { //小米12没有这个路径
tmp_dir = "/sbin";
} else {
char buf[8];
gen_rand_str(buf, sizeof(buf));
tmp_dir = "/dev/"s + buf;
xmkdir(tmp_dir.data(), 0);//创建magisk目录,类似/dev/xxx/ 这里xxx就是上面的随机数
}

setup_tmp(tmp_dir.data()); //初始化magisk目录
chdir(tmp_dir.data());
......
// Patch init.rc
if (access(NEW_INITRC, F_OK) == 0) {
// Android 11's new init.rc
xmkdirs(dirname(ROOTOVL NEW_INITRC), 0755);
patch_init_rc(NEW_INITRC, ROOTOVL NEW_INITRC, tmp_dir.data());
} else {
patch_init_rc("/init.rc", ROOTOVL "/init.rc", tmp_dir.data());//修改init.rc,目的是注入magisk服务
}

// Extract magisk
extract_files(false);//解压出magisk文件
......
// Mount rootdir
magic_mount(ROOTOVL);//结合日志分析,这里实际上就是mount了修改好的init.rc到原系统init.rc
......
chdir("/");
}

static void extract_files(bool sbin) {//解压magisk文件
const char *m32 = sbin ? "/sbin/magisk32.xz" : "magisk32.xz";
const char *m64 = sbin ? "/sbin/magisk64.xz" : "magisk64.xz";

auto magisk = mmap_data(m32);
unlink(m32);
int fd = xopen("magisk32", O_WRONLY | O_CREAT, 0755);
unxz(fd, magisk.buf, magisk.sz);
close(fd);
patch_socket_name("magisk32");
if (access(m64, F_OK) == 0) {
magisk = mmap_data(m64);
unlink(m64);
fd = xopen("magisk64", O_WRONLY | O_CREAT, 0755);
unxz(fd, magisk.buf, magisk.sz);
close(fd);
patch_socket_name("magisk64");//修改socket name
xsymlink("./magisk64", "magisk");
} else {
xsymlink("./magisk32", "magisk");
}

dump_manager("stub.apk", 0);
}

这一部分的主要 patch init.rc 注入 magisk 服务,用 dev/xxx/.magisk/rootdir/system/etc/init/hw/init.rc 替换原系统的 init.rc
修改后的 init.rc.
cat dev/xxx/.magisk/rootdir/system/etc/init/hw/init.rc

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
......
on post-fs-data
start logd
rm /dev/.magisk_unblock
start 3dgv8Xp9rdRtMv
wait /dev/.magisk_unblock 40
rm /dev/.magisk_unblock

service 3dgv8Xp9rdRtMv /dev/BYdnLYi/magisk --post-fs-data
user root
seclabel u:r:magisk:s0
oneshot

service OPIx4M5Px /dev/BYdnLYi/magisk --service
class late_start
user root
seclabel u:r:magisk:s0
oneshot

on property:sys.boot_completed=1
exec /dev/BYdnLYi/magisk --boot-complete

on property:init.svc.zygote=restarting
exec /dev/BYdnLYi/magisk --zygote-restart

on property:init.svc.zygote=stopped
exec /dev/BYdnLYi/magisk --zygote-restart

新增了 magisk 服务.

magiskinit 代理 init 进程

之前说过,在 magisk 里 patch 了原 init 的 /system/bin/init/data/magiskinit
所以原系统 init 的 execv("/system/bin/init") 被改成 execv("/data/magiskinit")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int byte_data::patch(bool log, str_pairs list) {
if (buf == nullptr)
return 0;
int count = 0;
for (uint8_t *p = buf, *eof = buf + sz; p < eof; ++p) {
for (auto [from, to] : list) {
if (memcmp(p, from.data(), from.length() + 1) == 0) {
if (log) LOGD("Replace [%s] -> [%s]\n", from.data(), to.data());
memset(p, 0, from.length());
memcpy(p, to.data(), to.length());
++count;
p += from.length();
}
}
}
return count;
}

可以看到,用 memcmp 比较,然后替换.

magisk 代理 init 的过程如下:

1
2
3
4
5
6
......
const char *orig_init = backup_init();//获取备份的系统init
if (access(orig_init, F_OK) == 0) {
xrename(orig_init, "/init");//把备份的init放到/init
}
......
1
2
3
4
5
6
7
8
9
void BaseInit::exec_init() {
// Unmount in reverse order
for (auto &p : reversed(mount_list)) {
if (xumount2(p.data(), MNT_DETACH) == 0)
LOGD("Unmount [%s]\n", p.data());
}
execv("/init", argv);//调用原系统init
exit(1);
}

好了,就先到这里吧.