探讨新的riru加载方式

警告
本文最后更新于 2023-08-18,文中内容可能已过时。

最近在搜索riru相关的项目时偶尔发现了HuskyDG riru项目中的一个实验性想法

https://github.com/tcc0lin/self_pic/blob/main/riru-newload.png?raw=true

也就是更换加载riru的方式,众所周知,riru.so load是通过赋值ro.dalvik.vm.native.bridge为libriruloader.so来完成的,而HuskyDG则提出了一个新的试验性加载方式—通过修改libandroid_runtime.so替换ro.zygote属性来完成加载

而ro.zygote属性是指的什么呢?它所表达的含义是指定zygote的执行程序时什么,通常情况下它的取值有四种,zygote32、zygote64、zygote32_64、zygote64_32,分别对应着四种.rc文件

  • init.zygote32.rc:zygote 进程对应的执行程序是 app_process(纯 32bit 模式)
  • init.zygote64.rc:zygote 进程对应的执行程序是 app_process64(纯 64bit 模式)
  • init.zygote32_64.rc:启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process32(主模式)、app_process64
  • init.zygote64_32.rc:启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process64(主模式)、app_process32

而之所以要定义这么多种模式时因为在Android5.0之后开始支持64位程序,为了保证兼容性而推出的

看起来ro.zygote与load并没有什么直接联系,那么HuskyDG的这种新的load方式是怎么实现的呢?

核心代码在commit: 90ec934上,关键代码在两处

 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
# template/magisk_module/service.sh

mkdir -p "$(magisk --path)/riru"

patch_lib(){
    /data/adb/magisk/magiskboot hexpatch "$1" \
    726f2e64616c76696b2e766d2e6e61746976652e62726964676500 \
    726f2e7a79676f7465000000000000000000000000000000000000
}

if [ -f /system/lib/libandroid_runtime.so ]; then
    cp -af /system/lib/libandroid_runtime.so "$(magisk --path)/riru/libandroid_runtime.so.32"
    magisk --clone-attr /system/lib/libandroid_runtime.so "$(magisk --path)/riru/libandroid_runtime.so.32"
    patch_lib "$(magisk --path)/riru/libandroid_runtime.so.32"
    mount --bind "$(magisk --path)/riru/libandroid_runtime.so.32" /system/lib/libandroid_runtime.so
fi

if [ -f /system/lib64/libandroid_runtime.so ]; then
    cp -af /system/lib64/libandroid_runtime.so "$(magisk --path)/riru/libandroid_runtime.so.64"
    magisk --clone-attr /system/lib64/libandroid_runtime.so "$(magisk --path)/riru/libandroid_runtime.so.64"
    patch_lib "$(magisk --path)/riru/libandroid_runtime.so.64"
    mount --bind "$(magisk --path)/riru/libandroid_runtime.so.64" /system/lib64/libandroid_runtime.so
fi

// restart zygote
stop; start;

这里是service.sh新增的代码,从代码中可以看到操作步骤是提取libandroid_runtime.so->patch libandroid_runtime.so->mount bind将修改同步,主要看patch的过程,将726f2e64616c76696b2e766d2e6e61746976652e62726964676500的hex值修改成726f2e7a79676f7465000000000000000000000000000000000000,也就是将ro.dalvik.vm.native.bridge修改成ro.zygote

为了保证libandroid_runtime.so的总体长度不变,这里额外补充了0来补位

而这么做的意义是什么呢?从libandroid_runtime.so的源码来看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// core/jni/AndroidRuntime.cpp

// Native bridge library. "0" means that native bridge is disabled.
//
// Note: bridging is only enabled for the zygote. Other runs of
//       app_process may not have the permissions to mount etc.
property_get("ro.dalvik.vm.native.bridge", propBuf, "");
if (propBuf[0] == '\0') {
    ALOGW("ro.dalvik.vm.native.bridge is not expected to be empty");
} else if (zygote && strcmp(propBuf, "0") != 0) {
    snprintf(nativeBridgeLibrary, sizeof("-XX:NativeBridge=") + PROPERTY_VALUE_MAX,
                "-XX:NativeBridge=%s", propBuf);
    addOption(nativeBridgeLibrary);
}

将原先读取ro.dalvik.vm.native.bridge属性的地方改成了读取ro.zygote属性,避免了对ro.dalvik.vm.native.bridge的赋值,而ro.zygote属性在原生设备上已经赋值,当ro.zygote有值时,就会去加载/system/lib/$(getprop ro.zygote)的so文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# template/magisk_module/post-fs-data.sh

cd "$MODDIR" || exit
flock "module.prop"
mount --bind "$TMPPROP" "$MODDIR/module.prop"
# 新增
ln -s ./libriruloader.so "$MODDIR/system/lib/$(getprop ro.zygote)"
ln -s ./libriruloader.so "$MODDIR/system/lib64/$(getprop ro.zygote)"
#
unshare -m sh -c "/system/bin/app_process -Djava.class.path=rirud.apk /system/bin --nice-name=rirud riru.Daemon $(magisk -V) $(magisk --path) $(getprop ro.dalvik.vm.native.bridge)&"
umount "$MODDIR/module.prop"

而在post-fs-data阶段,又操作了软链,让/system/lib/$(getprop ro.zygote)实际指向的是libriruloader.so,从而完成libriruloader.so的加载

基本的实现流程就是这样,可以看出HuskyDG的这种方式还是很巧妙的,另外在实现这种方式的同时,HuskyDG也针对性的修改了原有代码和增加了maps隐藏的逻辑

1
2
3
4
5
6
// rirud/src/main/java/riru/DaemonUtils.java

public static void resetNativeBridgeProp(String value) {
    //resetProperty("ro.dalvik.vm.native.bridge", value);
    return;
}

去除原有对于ro.dalvik.vm.native.bridge属性的修改,移除template/magisk_module/system.prop

 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
// riru/src/main/cpp/jni_hooks.cpp

static std::vector<lsplt::MapInfo> find_maps(const char *name) {
    auto maps = lsplt::MapInfo::Scan();
    for (auto iter = maps.begin(); iter != maps.end();) {
        if (iter->path != name) {
            iter = maps.erase(iter);
        } else {
            ++iter;
        }
    }
    return maps;
}

void remap_all(const char *name) {
    // 过滤出带有libandroid_runtime.so的segment
    auto maps = find_maps(name);
    for (auto &info : maps) {
        void *addr = reinterpret_cast<void *>(info.start);
        // 获取size
        size_t size = info.end - info.start;
        // 重新mmap申请内存地址
        void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
        // 设置读权限
        if ((info.perms & PROT_READ) == 0) {
            mprotect(addr, size, PROT_READ);
        }
        // 复制到刚申请的内存上
        memcpy(copy, addr, size);
        // 匹配大小
        mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
        // 重新授权
        mprotect(addr, size, info.perms);
    }
}

void fakemap_file(const char *name) {
    auto maps = find_maps(name);
    remap_all(name);
    for (auto &info : maps) {
        //void *addr = reinterpret_cast<void *>(info.start);
        size_t size = info.end - info.start;
        int fd = open(name, O_RDONLY);
        if (fd >= 0) {
            mmap(nullptr, size, info.perms, MAP_PRIVATE, fd, info.offset);
            close(fd);
        } else {
            LOGE("open %s failed\n", name);
        }
    }
}

void jni::RestoreHooks(JNIEnv *env) {
    if (useTableOverride) {
        setTableOverride(nullptr);
@@ -227,6 +272,9 @@ void jni::RestoreHooks(JNIEnv *env) {
    RestoreJNIMethod(zygote, nativeForkSystemServer)

    LOGD("hooks restored");
    // 新增
    fakemap_file("/system/lib/libandroid_runtime.so");
    fakemap_file("/system/lib64/libandroid_runtime.so");

}

fakemap_file的作用相当于是去除maps中特征段的文件关联,和riru hide so_list的方式类似

相比于老方式来说,新方案没有更改系统属性,而是借助于Magisk修改libandroid_runtime.so。来在HuskyDG的电报群内和一些开发者讨论过,都一致认为这种新方式所暴露出来的风险是大于老方案的,确实如HuskyDG所说,这种新方式只是一种探索吧

相关内容