Riru原理理解

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

什么是riru?正如它Github上面所提到的那样,它提供的能力是允许模块能够将自己的代码注入到各个App进程或者是system_server进程中,而这一切的实现就是基于它对于zygote进程的注入。

那具体是怎么对于zygote进行注入的呢?文档中也提到,在早期的版本中,riru通过替换libmemtrack.so这个系统库的方式,原因是一方面zygote会加载该系统库,另一方面则是因为这个库足够小,仅仅只有十个函数,也就意味着替换掉它所造成的影响面是很小的,但是后续来看,由于使用到这个so的进程比较多,除了zygote还有SurfaceFlinger(用于显示系统)、mediaserver(用于媒体处理)等等,因此会造成一些意想不到的错误。

因此在v22,riru使用了一个新的注入方式,设置native bridge,也就是指定ro.dalvik.vm.native.bridge的值为riri的so来实现注入,这个思路是来源于canyie的一篇文章通过系统的native bridge实现注入zygote,它的原理大概是

  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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ALOGD(">>>>>> START %s uid %d <<<<<<\n",
            className != NULL ? className : "(unknown)", getuid());

    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    // 启动虚拟机
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }
    onVmCreated(env);
}

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote)
{
    JavaVMInitArgs initArgs;
    // ...

    // 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.
    // 读取ro.dalvik.vm.native.bridge属性
    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);
    }
    // ...
    initArgs.version = JNI_VERSION_1_4;
    initArgs.options = mOptions.editArray();
    initArgs.nOptions = mOptions.size();
    initArgs.ignoreUnrecognized = JNI_FALSE;

    /*
     * Initialize the VM.
     *
     * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
     * If this call succeeds, the VM is ready, and we can start issuing
     * JNI calls.
     */
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }

    return 0;
}

bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
  // ...
  // Look for a native bridge.
  //
  // The intended flow here is, in the case of a running system:
  //
  // Runtime::Init() (zygote):
  //   LoadNativeBridge -> dlopen from cmd line parameter.
  //  |
  //  V
  // Runtime::Start() (zygote):
  //   No-op wrt native bridge.
  //  |
  //  | start app
  //  V
  // DidForkFromZygote(action)
  //   action = kUnload -> dlclose native bridge.
  //   action = kInitialize -> initialize library
  //
  //
  // The intended flow here is, in the case of a simple dalvikvm call:
  //
  // Runtime::Init():
  //   LoadNativeBridge -> dlopen from cmd line parameter.
  //  |
  //  V
  // Runtime::Start():
  //   DidForkFromZygote(kInitialize) -> try to initialize any native bridge given.
  //   No-op wrt native bridge.
  {
    std::string native_bridge_file_name = runtime_options.ReleaseOrDefault(Opt::NativeBridge);
    // 加载native bridge
    is_native_bridge_loaded_ = LoadNativeBridge(native_bridge_file_name);
  }
  // ...
}

bool LoadNativeBridge(const char* nb_library_filename,
                      const NativeBridgeRuntimeCallbacks* runtime_cbs) {
  // We expect only one place that calls LoadNativeBridge: Runtime::Init. At that point we are not
  // multi-threaded, so we do not need locking here.

  if (nb_library_filename == nullptr || *nb_library_filename == 0) {
    CloseNativeBridge(false);
    return false;
  } else {
    if (!NativeBridgeNameAcceptable(nb_library_filename)) {
      CloseNativeBridge(true);
    } else {
      // Try to open the library.
      // 调用dlopen打开指定so
      void* handle = dlopen(nb_library_filename, RTLD_LAZY);
      if (handle != nullptr) {
        callbacks = reinterpret_cast<NativeBridgeCallbacks*>(dlsym(handle,
                                                                   kNativeBridgeInterfaceSymbol));
        if (callbacks != nullptr) {
          if (isCompatibleWith(NAMESPACE_VERSION)) {
            // Store the handle for later.
            native_bridge_handle = handle;
          } else {
            callbacks = nullptr;
            dlclose(handle);
            ALOGW("Unsupported native bridge interface.");
          }
        } else {
          dlclose(handle);
        }
      }

      // Two failure conditions: could not find library (dlopen failed), or could not find native
      // bridge interface (dlsym failed). Both are an error and close the native bridge.
      if (callbacks == nullptr) {
        CloseNativeBridge(true);
      } else {
        runtime_callbacks = runtime_cbs;
        state = NativeBridgeState::kOpened;
      }
    }
    return state == NativeBridgeState::kOpened;
  }
}

从上面的源码中就可以了解到整个的实现过程了,简而言之就是

1
startVM->LoadNativeBridge->dlopen->ro.dalvik.vm.native.bridge对应的so

以上就是关于riru的大致原理,下面再深入源码中看看有哪些实现细节

先从riru的项目结构入手,riru是个Magisk模块,自然是以Magisk模块模板为基础,首先来看看Magisk模块模板都具备哪些

参考开发者文档,Magisk模块都会放在/data/adb/modules目录下,结构如

 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
/data/adb/modules
├── .
├── .
|
├── $MODID 模块名称                 <--- The folder is named with the ID of the module
   
         *** Module Identity ***
   
   ├── module.prop   模块的基础信息      <--- This file stores the metadata of the module
   
         *** Main Contents ***
   
   ├── system   挂载到system目录           <--- This folder will be mounted if skip_mount does not exist
      ├── ...
      ├── ...
      └── ...
   
   ├── zygisk              <--- This folder contains the module's Zygisk native libraries
      ├── arm64-v8a.so
      ├── armeabi-v7a.so
      ├── x86.so
      ├── x86_64.so
      └── unloaded        <--- If exists, the native libraries are incompatible
   
         *** Status Flags ***
   
   ├── skip_mount          <--- If exists, Magisk will NOT mount your system folder
   ├── disable             <--- If exists, the module will be disabled
   ├── remove              <--- If exists, the module will be removed next reboot
   
         *** Optional Files ***
   
   ├── post-fs-data.sh  定制脚本   <--- This script will be executed in post-fs-data
   ├── service.sh          <--- This script will be executed in late_start service
|   ├── uninstall.sh        <--- This script will be executed when Magisk removes your module
   ├── system.prop  系统属性修改        <--- Properties in this file will be loaded as system properties by resetprop
   ├── sepolicy.rule   sepolicy规则修改    <--- Additional custom sepolicy rules
   
         *** Auto Generated, DO NOT MANUALLY CREATE OR MODIFY ***
   
   ├── vendor              <--- A symlink to $MODID/system/vendor
   ├── product             <--- A symlink to $MODID/system/product
   ├── system_ext          <--- A symlink to $MODID/system/system_ext
   
         *** Any additional files / folders are allowed ***
   
   ├── ...
   └── ...
|
├── another_module
   ├── .
   └── .
├── .
├── .

再看看模块的安装过程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
module.zip

├── META-INF
   └── com
       └── google
           └── android
               ├── update-binary      <--- The module_installer.sh you downloaded
               └── updater-script     <--- Should only contain the string "#MAGISK"

├── customize.sh                       <--- (Optional, more details later)
                                           This script will be sourced by update-binary
├── ...
├── ...  /* The rest of module's files */

customize.sh和update-binary是互相配合来完成安装过程中定制的部分

具体看看riru修改了

  • module.prop
    1
    2
    3
    4
    5
    6
    7
    8
    
    id=${id}
    name=${name}
    version=${version}
    versionCode=${versionCode}
    author=${author}
    description=${description}
    riruApi=${riruApi}
    riruMinApi=${riruMinApi}
    
    设置全局变量
  • post-fs-data
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    #!/system/bin/sh
    MODDIR=${0%/*}
    TMPPROP="$(magisk --path)/riru.prop"
    MIRRORPROP="$(magisk --path)/.magisk/modules/riru-core/module.prop"
    sh -Cc "cat '$MODDIR/module.prop' > '$TMPPROP'"
    if [ $? -ne 0 ]; then
    exit
    fi
    mount --bind "$TMPPROP" "$MIRRORPROP"
    if [ "$ZYGISK_ENABLE" = "1" ]; then
        sed -Ei 's/^description=(\[.*][[:space:]]*)?/description=[ ⛔ Riru is not loaded because of Zygisk. ] /g' "$MIRRORPROP"
        exit
    fi
    sed -Ei 's/^description=(\[.*][[:space:]]*)?/description=[ ⛔ app_process fails to run. ] /g' "$MIRRORPROP"
    cd "$MODDIR" || exit
    flock "module.prop"
    mount --bind "$TMPPROP" "$MODDIR/module.prop"
    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"
    
    主要在于使用unshare资源隔离的方式启动rirud的守护进程,和magiskd类似,都是在post-fs-data阶段启动的
  • system.prop
    1
    
    ro.dalvik.vm.native.bridge=libriruloader.so
    
    最关键的修改,修改了ro.dalvik.vm.native.bridge属性指向libriruloader.so
  • customize
     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
    
    // template/magisk_module/customize.sh
    
    // 文件解压
    if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
        ui_print "- Extracting x86 libraries"
        extract "$ZIPFILE" 'lib/x86/libriru.so' "$MODPATH/lib" true
        extract "$ZIPFILE" 'lib/x86/libriruhide.so' "$MODPATH/lib" true
        extract "$ZIPFILE" 'lib/x86/libriruloader.so' "$MODPATH/system/lib" true
    
        if [ "$IS64BIT" = true ]; then
            ui_print "- Extracting x64 libraries"
            extract "$ZIPFILE" 'lib/x86_64/libriru.so' "$MODPATH/lib64" true
            extract "$ZIPFILE" 'lib/x86_64/libriruhide.so' "$MODPATH/lib64" true
            extract "$ZIPFILE" 'lib/x86_64/libriruloader.so' "$MODPATH/system/lib64" true
        fi
    else
        ui_print "- Extracting arm libraries"
        extract "$ZIPFILE" 'lib/armeabi-v7a/libriru.so' "$MODPATH/lib" true
        extract "$ZIPFILE" 'lib/armeabi-v7a/libriruhide.so' "$MODPATH/lib" true
        extract "$ZIPFILE" 'lib/armeabi-v7a/libriruloader.so' "$MODPATH/system/lib" true
    
        if [ "$IS64BIT" = true ]; then
            ui_print "- Extracting arm64 libraries"
            extract "$ZIPFILE" 'lib/arm64-v8a/libriru.so' "$MODPATH/lib64" true
            extract "$ZIPFILE" 'lib/arm64-v8a/libriruhide.so' "$MODPATH/lib64" true
            extract "$ZIPFILE" 'lib/arm64-v8a/libriruloader.so' "$MODPATH/system/lib64" true
        fi
    fi
    
    // 权限设置
    ui_print "- Setting permissions"
    set_perm_recursive "$MODPATH" 0 0 0755 0644
    
    ui_print "- Extracting rirud"
    extract "$ZIPFILE" "rirud.apk" "$MODPATH"
    set_perm "$MODPATH/rirud.apk" 0 0 0600
    
    // 执行installer来检测selinux的规则是否符合预期
    ui_print "- Checking if your ROM has incorrect SELinux rules"
    /system/bin/app_process -Djava.class.path="$MODPATH/rirud.apk" /system/bin --nice-name=riru_installer riru.Installer --check-selinux
    

到这里关于riru的安装部分就结束了,下面回到riru项目来看看它的启动过程

上面讲到system.prop时已经可以看到riru指定了

1
ro.dalvik.vm.native.bridge=libriruloader.so

而从CMake文件(riru\src\main\cpp\CMakeLists.txt)中可以发现libriruloader.so来源于loader.cpp

1
2
add_library(riruloader SHARED loader/loader.cpp ${CMAKE_CURRENT_BINARY_DIR}/config.cpp)
target_link_libraries(riruloader log utils cxx::cxx)

跟进loader.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
 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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// riru/src/main/cpp/loader/loader.cpp

#ifdef HAS_NATIVE_BRIDGE

#include "native_bridge_callbacks.h"

//NOLINTNEXTLINE
extern "C" [[gnu::visibility("default")]] uint8_t NativeBridgeItf[
        sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>) * 2]{0};

static void *original_bridge = nullptr;

__used __attribute__((destructor)) void Destructor() {
    if (original_bridge) dlclose(original_bridge);
}

#endif

__used __attribute__((constructor)) void Constructor() {
    if (getuid() != 0) {
        return;
    }
    // 获取当前进程名
    std::string_view cmdline = getprogname();
    // 由于只注入zygoye,因此过滤其他进程
    if (cmdline != "zygote" &&
        cmdline != "zygote32" &&
        cmdline != "zygote64" &&
        cmdline != "usap32" &&
        cmdline != "usap64") {
        LOGW("not zygote (cmdline=%s)", cmdline.data());
        return;
    }

    LOGI("Riru %s (%d) in %s", riru::versionName, riru::versionCode, cmdline.data());
    LOGI("Android %s (api %d, preview_api %d)", android_prop::GetRelease(),
         android_prop::GetApiLevel(),
         android_prop::GetPreviewApiLevel());
    // 初始化rirud
    constexpr auto retries = 5U;
    RirudSocket rirud{retries};

    if (!rirud.valid()) {
        LOGE("rirud connect fails");
        return;
    }
    // 获取riru模块的地址,riru模块名称是riru-core
    std::string magisk_path = rirud.ReadMagiskTmpfsPath();
    if (magisk_path.empty()) {
        LOGE("failed to obtain magisk path");
        return;
    }

    // 获取到riru.so的路径
    BuffString<PATH_MAX> riru_path;
    riru_path += magisk_path;
    riru_path += "/.magisk/modules/riru-core/lib";
#ifdef __LP64__
    riru_path += "64";
#endif
    riru_path += "/libriru.so";

    // 读取so并调用init方法
    auto *handle = DlopenExt(riru_path, 0);
    if (handle) {
        auto init = reinterpret_cast<void (*)(void *, const char *, const RirudSocket &)>(dlsym(
                handle, "init"));
        if (init) {
            init(handle, magisk_path.data(), rirud);
        } else {
            LOGE("dlsym init %s", dlerror());
        }
    } else {
        LOGE("dlopen riru.so %s", dlerror());
    }

#ifdef HAS_NATIVE_BRIDGE
    // 针对x86设备
    auto native_bridge = rirud.ReadNativeBridge();
    if (native_bridge.empty()) {
        LOGW("Failed to read original native bridge from socket");
        return;
    }

    LOGI("original native bridge: %s", native_bridge.data());

    if (native_bridge == "0") {
        return;
    }
    
    original_bridge = dlopen(native_bridge.data(), RTLD_NOW);
    if (original_bridge == nullptr) {
        LOGE("dlopen failed: %s", dlerror());
        return;
    }

    auto *original_native_bridge_itf = dlsym(original_bridge, "NativeBridgeItf");
    if (original_native_bridge_itf == nullptr) {
        LOGE("dlsym failed: %s", dlerror());
        return;
    }

    int sdk = 0;
    std::array<char, PROP_VALUE_MAX + 1> value;
    if (__system_property_get("ro.build.version.sdk", value.data()) > 0) {
        sdk = atoi(value.data());
    }

    auto callbacks_size = 0;
    if (sdk >= __ANDROID_API_R__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>);
    } else if (sdk == __ANDROID_API_Q__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_Q__>);
    } else if (sdk == __ANDROID_API_P__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_P__>);
    } else if (sdk == __ANDROID_API_O_MR1__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_O_MR1__>);
    } else if (sdk == __ANDROID_API_O__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_O__>);
    } else if (sdk == __ANDROID_API_N_MR1__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_N_MR1__>);
    } else if (sdk == __ANDROID_API_N__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_N__>);
    } else if (sdk == __ANDROID_API_M__) {
        callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_M__>);
    }
    // 覆盖原始的NativeBridgeItf
    memcpy(NativeBridgeItf, original_native_bridge_itf, callbacks_size);
#endif
}

从代码中可以看出,正常对于arm设备来说libriruloader.so相当于loader,负责读取解压在module目录中的libriru.so,而对于x86设备来说,也就是代码中的ifdef HAS_NATIVE_BRIDGE,它的定义可以在这里看到

1
2
3
4
5
// riru/src/main/cpp/CMakeLists.txt

if ("${ANDROID_ABI}" STREQUAL "x86" OR "${ANDROID_ABI}" STREQUAL "x86_64")
    add_definitions(-DHAS_NATIVE_BRIDGE)
endif ()

需要和rirud通信,用原始的NativeBridgeItf替换修改后的NativeBridgeItf

riruloader引导启动了riru.so,riru.so来自entry.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// riru/src/main/cpp/CMakeLists.txt

add_library(riru SHARED
        entry.cpp
        jni_hooks.cpp
        hide_utils.cpp
        module.cpp
        magisk.cpp
        ${CMAKE_CURRENT_BINARY_DIR}/config.cpp)
target_include_directories(riru PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(riru log utils xhook::xhook cxx::cxx proc-maps-parser::proc-maps-parser)

根据loader中的流程,在dlopen riru.so会调用的是init方法,也就是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// riru/src/main/cpp/entry.cpp

extern "C" [[gnu::visibility("default")]] [[maybe_unused]] void
// NOLINTNEXTLINE
init(void *handle, const char* magisk_path, const RirudSocket& rirud) {
    self_handle = handle;

    magisk::SetPath(magisk_path);
    hide::PrepareMapsHideLibrary();
    jni::InstallHooks();
    modules::Load(rirud);
}

init的过程分为了三个部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// riru/src/main/cpp/hide_utils.cpp

void PrepareMapsHideLibrary() {
    // 获取so的绝对路径,也就是/data/adb/modules/riru-core
    auto hide_lib_path = magisk::GetPathForSelfLib("libriruhide.so");

    // load riruhide.so and run the hide
    LOGD("dlopen libriruhide");
    // dlopen
    riru_hide_handle = DlopenExt(hide_lib_path.c_str(), 0);
    if (!riru_hide_handle) {
        LOGE("dlopen %s failed: %s", hide_lib_path.c_str(), dlerror());
        return;
    }
    // dlsym riru_hide
    riru_hide_func = reinterpret_cast<riru_hide_t *>(dlsym(riru_hide_handle, "riru_hide"));
    if (!riru_hide_func) {
        LOGE("dlsym failed: %s", dlerror());
        dlclose(riru_hide_handle);
        return;
    }
}

调用libriruhide.so的riru_hide方法,libriruhide.so对应的是

1
add_library(riruhide SHARED hide/hide.cpp)

这一步只是准备好riru_hide的函数,并未真正执行,执行需要在各个注入进程内部来执行

提前来看看具体代码

  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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
// riru/src/main/cpp/hide/hide.cpp

int riru_hide(const std::set<std::string_view> &names) {
    // 对应的是https://github.com/h33p/vmread/blob/master/pmparser.c,格式化当前进程的maps变成对应的结构体
    procmaps_iterator *maps = pmparser_parse(-1);
    if (maps == nullptr) {
        LOGE("cannot parse the memory map");
        return false;
    }

    char buf[PATH_MAX];
    hide_struct *data = nullptr;
    size_t data_count = 0;
    procmaps_struct *maps_tmp;
    // 遍历maps结构体
    while ((maps_tmp = pmparser_next(maps)) != nullptr) {
        bool matched = false;
#ifdef DEBUG_APP
        matched = strstr(maps_tmp->pathname, "libriru.so");
#endif
        // 检测maps是否包含name
        matched = names.count(maps_tmp->pathname);

        if (!matched) continue;
        // 如果匹配到了
        auto start = (uintptr_t) maps_tmp->addr_start;
        auto end = (uintptr_t) maps_tmp->addr_end;
        // 如果可读
        if (maps_tmp->is_r) {
            // 创建data来存储maps_tmp
            if (data) {
                data = (hide_struct *) realloc(data, sizeof(hide_struct) * (data_count + 1));
            } else {
                data = (hide_struct *) malloc(sizeof(hide_struct));
            }
            // 保存到hide_struct结构体中
            data[data_count].original = maps_tmp;
            data_count += 1;
        }
        LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s", start, end, maps_tmp->perm, maps_tmp->offset,
             maps_tmp->pathname);
    }

    for (int i = 0; i < data_count; ++i) {
        // 进行hide
        do_hide(&data[i]);
    }

    if (data) free(data);
    pmparser_free(maps);
    return 0;
}

static int do_hide(hide_struct *data) {
    auto procstruct = data->original;
    auto start = (uintptr_t) procstruct->addr_start;
    auto end = (uintptr_t) procstruct->addr_end;
    auto length = end - start;
    int prot = get_prot(procstruct);

    // backup
    // 通过mmap建立内存映射,由于fd为-1,表示建立的是匿名映射,backup_address为新地址
    data->backup_address = (uintptr_t) FAILURE_RETURN(
            mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0),
            MAP_FAILED);
    LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s is backup to %" PRIxPTR, start, end, procstruct->perm,
         procstruct->offset,
         procstruct->pathname, data->backup_address);

    if (!procstruct->is_r) {
        // 老地址如果不可读,通过mprotect授予可读权限
        LOGD("mprotect +r");
        FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_READ), -1);
    }
    // 老地址数据复制到新地址上
    LOGD("memcpy -> backup");
    memcpy((void *) data->backup_address, (void *) start, length);

    // munmap original
    LOGD("munmap original");
    // 删除老地址的映射
    FAILURE_RETURN(munmap((void *) start, length), -1);

    // restore
    LOGD("mmap original");
    // 老地址重建映射
    FAILURE_RETURN(mmap((void *) start, length, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0),
                   MAP_FAILED);
    LOGD("mprotect +w");
    // 授予老地址写权限
    FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_WRITE), -1);
    LOGD("memcpy -> original");
    // 新地址数据返还给老地址
    memcpy((void *) start, (void *) data->backup_address, length);
    if (!procstruct->is_w) {
        LOGD("mprotect -w");
        // 使用老权限,也就是没有可写权限的权限
        FAILURE_RETURN(mprotect((void *) start, length, prot), -1);
    }
    return 0;
}

do_hide的逻辑简单来看就是替换原有的map记录,中间会涉及到数据的转存,因此会有memcpy的操作,去除文件的关键点就在于munmap去除了fd引用从而保证该map没有关联的文件,调用点在

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// riru/src/main/cpp/hide_utils.cpp

void HideFromMaps() {
    // 获取libriru.so的绝对路径
    auto self_path = magisk::GetPathForSelfLib("libriru.so");
    std::set<std::string_view> names{self_path};
    // 得到真实路径
    for (const auto &module : modules::Get()) {
        if (module.supportHide) {
            if (!module.isLoaded()) {
                LOGD("%s is unloaded", module.id.data());
            } else {
                names.emplace(module.path);
            }
        } else {
            LOGD("module %s does not support hide", module.id.data());
        }
    }
    if (!names.empty()) hide::HidePathsFromMaps(names);
}
 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
// riru/src/main/cpp/jni_hooks.cpp

void jni::InstallHooks() {
    // 引入xhook
    XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods)
    // 确认hook状态
    if (xhook_refresh(0) == 0) {
        xhook_clear();
        LOGI("hook installed");
    } else {
        LOGE("failed to refresh hook");
    }

    useTableOverride = old_jniRegisterNativeMethods == nullptr;
}

#define XHOOK_REGISTER(PATH_REGEX, NAME) \
    if (xhook_register(PATH_REGEX, #NAME, (void*) new_##NAME, (void **) &old_##NAME) != 0) \
        LOGE("failed to register hook " #NAME "."); \

#define NEW_FUNC_DEF(ret, func, ...) \
    using func##_t = ret(__VA_ARGS__); \
    static func##_t *old_##func; \
    static ret new_##func(__VA_ARGS__)

NEW_FUNC_DEF(int, jniRegisterNativeMethods, JNIEnv *env, const char *className,
             const JNINativeMethod *methods, int numMethods) {
    LOGD("jniRegisterNativeMethods %s", className);

    auto newMethods = handleRegisterNative(className, methods, numMethods);
    int res = old_jniRegisterNativeMethods(env, className, newMethods ? newMethods.get() : methods,
                                           numMethods);
    /*if (!newMethods) {
        NativeMethod::jniRegisterNativeMethodsPost(env, className, methods, numMethods);
    }*/
    return res;
}

替换了RegisterNative方法,新的RegisterNative采用handleRegisterNative处理

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

static std::unique_ptr<JNINativeMethod[]>
handleRegisterNative(const char *className, const JNINativeMethod *methods, int numMethods) {
    if (strcmp("com/android/internal/os/Zygote", className) == 0) {
        // 注入zygote
        return onRegisterZygote(className, methods, numMethods);
    } else {
        return nullptr;
    }
}

static std::unique_ptr<JNINativeMethod[]>
onRegisterZygote(const char *className, const JNINativeMethod *methods, int numMethods) {

    // 替换原始方法所在的内存
    auto newMethods = std::make_unique<JNINativeMethod[]>(numMethods);
    memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods);

    // 主要hooks三个函数
    // - nativeForkAndSpecialize
    // - nativeSpecializeAppProcess
    // - nativeForkSystemServer
    // 可以覆盖到所有app和system_server
    JNINativeMethod method;
    for (int i = 0; i < numMethods; ++i) {
        method = methods[i];

        if (strcmp(method.name, "nativeForkAndSpecialize") == 0) {
            jni::zygote::nativeForkAndSpecialize = new JNINativeMethod{method.name,
                                                                       method.signature,
                                                                       method.fnPtr};

            if (strcmp(nativeForkAndSpecialize_r_sig, method.signature) == 0)
                newMethods[i].fnPtr = (void *) nativeForkAndSpecialize_r;

            ......
            
            // 适配不同版本,替换实现指针
            auto replaced = newMethods[i].fnPtr != methods[i].fnPtr;
            if (replaced) {
                LOGI("replaced com.android.internal.os.Zygote#nativeForkAndSpecialize");
            }
        } else if (strcmp(method.name, "nativeSpecializeAppProcess") == 0) {
            jni::zygote::nativeSpecializeAppProcess = new JNINativeMethod{method.name,
                                                                          method.signature,
                                                                          method.fnPtr};

            if (strcmp(nativeSpecializeAppProcess_r_sig, method.signature) == 0)
                newMethods[i].fnPtr = (void *) nativeSpecializeAppProcess_r;
            
            ......

            auto replaced = newMethods[i].fnPtr != methods[i].fnPtr;
            if (replaced) {
                LOGI("replaced com.android.internal.os.Zygote#nativeSpecializeAppProcess");
            }
        } else if (strcmp(method.name, "nativeForkSystemServer") == 0) {
            jni::zygote::nativeForkSystemServer = new JNINativeMethod{method.name, method.signature,
                                                                      method.fnPtr};

            if (strcmp(nativeForkSystemServer_sig, method.signature) == 0)
                newMethods[i].fnPtr = (void *) nativeForkSystemServer;
            else if (strcmp(nativeForkSystemServer_samsung_q_sig, method.signature) == 0)
                newMethods[i].fnPtr = (void *) nativeForkSystemServer_samsung_q;
            else
                LOGW("found nativeForkSystemServer but signature %s mismatch", method.signature);

            auto replaced = newMethods[i].fnPtr != methods[i].fnPtr;
            if (replaced) {
                LOGI("replaced com.android.internal.os.Zygote#nativeForkSystemServer");
            }
        }
    }
    return newMethods;
}

具体逻辑的实现

  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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
// riru/src/main/cpp/jni_hooks.cpp

jint nativeForkSystemServer(
        JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids, jint runtimeFlags,
        jobjectArray rlimits, jlong permittedCapabilities, jlong effectiveCapabilities) {

    // pre
    nativeForkSystemServer_pre(
            env, clazz, uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities,
            effectiveCapabilities);

    // origin
    jint res = ((nativeForkSystemServer_t *) jni::zygote::nativeForkSystemServer->fnPtr)(
            env, clazz, uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities,
            effectiveCapabilities);

    // post
    nativeForkSystemServer_post(env, clazz, res);
    return res;
}

static void nativeForkSystemServer_pre(
        JNIEnv *env, jclass clazz, uid_t &uid, gid_t &gid, jintArray &gids, jint &debug_flags,
        jobjectArray &rlimits, jlong &permittedCapabilities, jlong &effectiveCapabilities) {

    // 执行各个模块的方法
    for (const auto &module : modules::Get()) {
        if (!module.hasForkSystemServerPre())
            continue;

        module.resetAllowUnload();

        module.forkSystemServerPre(
                env, clazz, &uid, &gid, &gids, &debug_flags, &rlimits, &permittedCapabilities,
                &effectiveCapabilities);
    }
}

static void nativeForkSystemServer_post(JNIEnv *env, jclass clazz, jint res) {
    
    if (res == 0) jni::RestoreHooks(env);

    if (res == 0 && android_prop::CheckZTE()) {
        auto *process = env->FindClass("android/os/Process");
        auto *set_argv0 = env->GetStaticMethodID(process, "setArgV0", "(Ljava/lang/String;)V");
        env->CallStaticVoidMethod(process, set_argv0, env->NewStringUTF("system_server"));
    }

    // 同理
    for (const auto &module : modules::Get()) {
        if (!module.hasForkSystemServerPost()) continue;

        if (res == 0) LOGD("%s: forkSystemServerPost", module.id.data());
        module.forkSystemServerPost(env, clazz, res);
    }
}

static void
nativeSpecializeAppProcess_post(JNIEnv *env, jclass clazz, jint uid, jboolean is_child_zygote) {

    jni::RestoreHooks(env);

    for (const auto &module : modules::Get()) {
        if (!module.hasSpecializeAppProcessPost())
            continue;

        if (module.apiVersion < 25) {
            if (module.hasShouldSkipUid() && module.shouldSkipUid(uid))
                continue;

            if (!module.hasShouldSkipUid() && shouldSkipUid(uid))
                continue;
        }

        LOGD("%s: specializeAppProcessPost", module.id.data());
        module.specializeAppProcessPost(env, clazz);
    }
    // 每个app初始化时需要额外对solist进行隐藏
    Entry::Unload(is_child_zygote);
}

void Entry::Unload(jboolean is_child_zygote) {
    self_unload_allowed = true;

    for (auto &module : modules::Get()) {
        if (module.allowUnload()) {
            LOGD("%s: unload", module.id.data());
            module.unload();
        } else {
            if (module.apiVersion >= 25)
                LOGD("%s: unload is not allow for this process", module.id.data());
            else {
                LOGD("%s: unload is not supported by module (API < 25), self unload is also disabled",
                     module.id.data());
                self_unload_allowed = false;
            }
        }
    }

    hide::HideFromSoList();

    // Child zygote (webview zyote or app zygote) has no "execmem" permission
    if (android_prop::GetApiLevel() < 29 && !is_child_zygote) {
        hide::HideFromMaps();
    }

    if (self_unload_allowed) {
        SelfUnload();
    }
}

除了对maps隐藏以外,还有HideFromSoList对系统so列表的隐藏

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

void HideFromSoList() {
    // 获取libriru.so的路径,这里的路径是magisk目录下的路径
    auto self_path = magisk::GetPathForSelfLib("libriru.so");
    std::set<std::string_view> names_to_remove{};
    if (Entry::IsSelfUnloadAllowed()) {
        LOGD("don't hide self since it will be unloaded");
    } else {
        names_to_remove.emplace(self_path);
    }
    for (const auto &module : modules::Get()) {
        if (module.supportHide) {
            if (!module.isLoaded()) {
                LOGD("%s is unloaded", module.id.data());
                continue;
            }
            if (module.apiVersion < 24) {
                LOGW("%s is too old to hide so", module.id.data());
            } else {
                names_to_remove.emplace(module.path);
            }
        } else {
            LOGD("module %s does not support hide", module.id.data());
        }
    }

    if (android_prop::GetApiLevel() >= 23 && !names_to_remove.empty()) {
        // 具体移除方法
        RemoveFromSoList(names_to_remove);
    }
}

void RemovePathsFromSolist(const std::set<std::string_view> &names) {
    if (!initialized) {
        LOGW("not initialized");
        return;
    }
    ProtectedDataGuard g;
    for (const auto &soinfo : linker_get_solist()) {
        const auto &real_path = soinfo->get_realpath();
        if (real_path && names.count(real_path)) {
            solist_remove_soinfo(soinfo);
        }
    }
}

先看看so列表是怎么获取到的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
std::list<soinfo *> linker_get_solist() {
    std::list<soinfo *> linker_solist{};
    for (auto *iter = solist; iter; iter = iter->next()) {
        linker_solist.push_back(iter);
    }
    return linker_solist;
}

// 通过获取linker地址进而获取solist的地址
const auto initialized = []() {
    SandHook::ElfImg linker("/linker");
    return ProtectedDataGuard::setup(linker) &&
            (solist = getStaticVariable<soinfo>(linker, "__dl__ZL6solist")) != nullptr &&
            (sonext = linker.getSymbAddress<soinfo**>("__dl__ZL6sonext")) != nullptr &&
            (somain = getStaticVariable<soinfo>(linker, "__dl__ZL6somain")) != nullptr &&
            soinfo::setup(linker);
}();

隐藏逻辑

 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
bool solist_remove_soinfo(soinfo *si) {
    soinfo *prev = nullptr, *trav;
    for (trav = solist; trav != nullptr; trav = trav->next()) {
        if (trav == si) {
            break;
        }
        prev = trav;
    }

    if (trav == nullptr) {
        // si was not in solist
        LOGE("name \"%s\"@%p is not in solist!", si->get_realpath(), si);
        return false;
    }
    // 相当于重构链表
    // prev will never be null, because the first entry in solist is
    // always the static libdl_info.
    prev->next(si->next());
    if (si == *sonext) {
        *sonext = prev;
    }

    LOGD("removed soinfo: %s", si->get_realpath());

    return true;
}
 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
// riru/src/main/cpp/module.cpp

void modules::Load(const RirudSocket &rirud) {
    uint32_t num_modules;
    // 模块获取
    auto &modules = modules::Get();
    if (!rirud.Write(RirudSocket::Action::READ_MODULES) ||
        !rirud.Write(is64bit) || !rirud.Read(num_modules)) {
        LOGE("Faild to load modules");
        return;
    }
    std::string magisk_module_path;
    std::string path;
    std::string id;
    uint32_t num_libs;
    // 遍历模块进行load
    while (num_modules-- > 0) {
        if (!rirud.Read(magisk_module_path) || !rirud.Read(num_libs)) {
            LOGE("Faild to read module's magisk path");
            return;
        }
        while (num_libs-- > 0) {
            if (!rirud.Read(id) || !rirud.Read(path)) {
                LOGE("Faild to read module's lib path");
                return;
            }
            LoadModule(id, path, magisk_module_path);
        }
    }

    // android10以上额外增加maps hide
    // On Android 10+, zygote has "execmem" permission, we can use "riru hide" here
    if (android_prop::GetApiLevel() >= __ANDROID_API_Q__) {
        hide::HideFromMaps();
    }

    // 另一种load方式
    for (const auto &module : modules::Get()) {
        if (module.hasOnModuleLoaded()) {
            LOGV("%s: onModuleLoaded", module.id.data());
            module.onModuleLoaded();
        }
    }

    WriteModules(rirud);
}

关于模块的load关键点在三个地方,首先是LoadModule

 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
static void LoadModule(std::string_view id, std::string_view path, std::string_view magisk_module_path) {
    // 判断路径是否可读
    if (access(path.data(), F_OK) != 0) {
        PLOGE("access %s", path.data());
        return;
    }
    // dlopen
    auto *handle = DlopenExt(path.data(), 0);
    if (!handle) {
        LOGE("dlopen %s failed: %s", path.data(), dlerror());
        return;
    }
    // 调用init函数
    auto init = reinterpret_cast<RiruInit_t *>(dlsym(handle, "init"));
    if (!init) {
        LOGW("%s does not export init", path.data());
        Cleanup(handle);
        return;
    }

    auto allow_unload = std::make_unique<int>();
    auto riru = std::make_unique<Riru>(Riru{
            .riruApiVersion = riru::apiVersion,
            .unused = nullptr,
            .magiskModulePath = magisk_module_path.data(),
            .allowUnload = allow_unload.get()
    });

    auto *module_info = init(riru.get());
    if (module_info == nullptr) {
        LOGE("%s requires higher Riru version (or its broken)", path.data());
        Cleanup(handle);
        return;
    }

    auto api_version = module_info->moduleApiVersion;
    if (api_version < riru::minApiVersion || api_version > riru::apiVersion) {
        LOGW("unsupported API %s: %d", id.data(), api_version);
        Cleanup(handle);
        return;
    }

    LOGI("module loaded: %s (api %d)", id.data(), api_version);

    modules::Get().emplace_back(id, path, magisk_module_path, api_version, module_info->moduleInfo,
                                handle,
                                std::move(allow_unload));
}

HideFromMaps是之前PrepareMapsHideLibrary的调用点

最后是onModuleLoaded,这个功能是为了防止模块开发者在使用线程时造成hide功能产生SIGSEGV

至此riru的实现细节大概分析完了,都说”riru提供注入功能,lsposed提供hook功能“,lsposed确实底层也依赖riru或者zygisk,下次再深入看看lsposed

相关内容