AI手机网,短视频直播 硬改改机 一键新机 群控软件 刷机定制

 找回密码
 立即注册
搜索
查看: 3123|回复: 9

Riru原理浅析和EdXposed入口分析

[复制链接]
发表于 2021-8-11 13:47:17 | 显示全部楼层 |阅读模式
因为最近在用EdXposed,对于magisk和riru很是好奇,之前也大致了解过edxp通过riru实现zygote注入进而完成ART Hook实现类Xposed,但是在准备看源码的时候发现不知道入口在哪,本来想找找有没有现成的大佬总结,发现貌似没有,于是自力更生,从magisk插件开发到riru插件到riru加载逻辑,一步步找到了edxp的代码入口。
Edxp如何编译成一个Magisk插件
根据面具的官方文档(https://topjohnwu.github.io/Magisk/guides.html)一个Magisk插件模块是/data/adb/modules下的一个文件夹,其中至少包含着以下几个文件:
  • module.prop 模块标识
  • post-fs-data.sh post-fs-data时所执行的脚本
  • service.sh 启动脚本的首选文件
EdXposed在gradle构建出产物时,会将整个项目打包成一个magisk module installer,该installer与magisk module的区别在于installer为一个zip压缩文件,同时包含META-INF文件夹,其中的update-binary.sh将作为安装前执行的脚本。

但是我们在EdXposed中并没有搜到相关的module.prop文件,只能看到module.prop.tpl模版文件,那么这两个文件是如何关联的呢。

在edxp-core的build.gradle中可以看到声明了一系列构建Task,从EdXposed的构建命令./gradlew clean :edxp-core:[zip|push]YahfaRelease开始追踪,

  1. def zipTask = task("zip${backendCapped}${variantCapped}", type: Zip) {
  2.                 dependsOn prepareMagiskFilesTask
  3.                 archiveName "${module_name}-${project.version}-${variantLowered}.zip"
  4.                 destinationDir file("$projectDir/release")
  5.                 from "$zipPathMagiskRelease"
  6.             }

  7.             task("push${backendCapped}${variantCapped}", type: Exec) {
  8.                 dependsOn zipTask
  9.                 workingDir "${projectDir}/release"
  10.                 def commands = ["adb", "push",
  11.                                 "${module_name}-${project.version}-${variantLowered}.zip",
  12.                                 "/sdcard/"]
  13.                 if (is_windows) {
  14.                     commandLine 'cmd', '/c', commands.join(" ")
  15.                 } else {
  16.                     commandLine commands
  17.                 }
  18.             }
  19.         }

  20.         // backward compatible
  21.         task("zip${variantCapped}") {
  22.             dependsOn "zipYahfa${variantCapped}"
  23.         }
  24.         task("push${variantCapped}") {
  25.             dependsOn "pushYahfa${variantCapped}"
  26.         }
复制代码
可以发现 pushTask只是将构建产物推送到设备sdcard下,真正打包任务在zipTask中,其依赖于prepareMagiskFilesTask,该task的作用是:
  • 复制template_override目录下的META-INF和so文件
  • 替换和重写edconfig.tpl和module.prop.tpl,变成真正的module.prop
    1. def prepareMagiskFilesTask = task("prepareMagiskFiles${backendCapped}${variantCapped}", type: Delete) {
    2.                 dependsOn prepareJarsTask, "assemble${variantCapped}"
    3.                 delete file(zipPathMagiskRelease)
    4.                 doFirst {
    5.                     copy {
    6.                         from "${projectDir}/tpl/edconfig.tpl"
    7.                         into templateFrameworkPath
    8.                         rename "edconfig.tpl", "edconfig.jar"
    9.                         expand(version: "$version", backend: "$backend")
    10.                     }
    11.                     copy {
    12.                         from "${projectDir}/tpl/module.prop.tpl"
    13.                         into templateRootPath
    14.                         rename "module.prop.tpl", "module.prop"
    15.                         expand(moduleId: "$magiskModuleId", backend: "$backendCapped",
    16.                                 versionName: "$version",
    17.                                 versionCode: "$versionCode", authorList: "$authorList")
    18.                         filter(FixCrLfFilter.class, eol: FixCrLfFilter.CrLf.newInstance("lf"))
    19.                     }
    20.                 }
    21.                 def libPathRelease = "${buildDir}/intermediates/cmake/${variantLowered}/obj"
    22.                 doLast {
    23.                     copy {
    24.                         from "${projectDir}/template_override"
    25.                         into zipPathMagiskRelease
    26.                     }
    27.                     copy {
    28.                         from "$libPathRelease/armeabi-v7a"
    29.                         into "$zipPathMagiskRelease/system/lib"
    30.                     }
    31.                     copy {
    32.                         from "$libPathRelease/arm64-v8a"
    33.                         into "$zipPathMagiskRelease/system/lib64"
    34.                     }
    35.                     copy {
    36.                         from "$libPathRelease/x86"
    37.                         into "$zipPathMagiskRelease/system_x86/lib"
    38.                     }
    39.                     copy {
    40.                         from "$libPathRelease/x86_64"
    41.                         into "$zipPathMagiskRelease/system_x86/lib64"
    42.                     }
    43.                 }
    44.             }
    复制代码
    但是EdXposed作为一个Magisk插件,插件加载时所会执行的update-binary、post-fs-data.sh、customize.sh等脚本中并没有相关执行EdXp的命令,那么EdXposed是如何在Zygote进程加载时所执行呢。

    EdXposed实际上还是一个riru插件,即基于riru注入zygote进程时机和riru相关的API声明来实现zygote进程执行时的加载。
    Riru模块(插件)编译构建过程
    Riru模块本质上是一个Magisk插件,额外的地方在于新增了一个目录/data/adb/riru/modules(旧版本在/data/misc/riru/modules)下多了一个插件目录,该目录的作用在于在riru加载时通过该目录名称加载对应的插件so,具体逻辑下面具体分析。

    首先看一个Riru模块如何编译构建的,入口依然是build.gradle,即从构建命令zipMagiskMoudle入手:
  • 可以看到主要做了以下操作:

    • 复制$rootDir/template/magisk_module
    • 复制$rootDir/template/magisk_module/riru.sh,替换其中占位符
    • 生成module.prop
    • 在riru目录下生成module.prop.new
    • 复制native动态链接库
    • 生成sha1sum签名
    • zip压缩打包
    一句话总结:将项目下template/magisk_module下的如动态库、module.prop、post-fs-data.sh等magisk插件相关文件打包成magisk module install所需的zip格式。剩下的就是安装到手机中由Magisk进行加载。
    1. android.libraryVariants.all { variant ->
    2.     def task = variant.assembleProvider.get()
    3.     task.doLast {
    4.         // clear
    5.         delete { delete magiskDir }

    6.         // copy from template
    7.         copy {
    8.             from "$rootDir/template/magisk_module"
    9.             into magiskDir.path
    10.             exclude 'riru.sh'
    11.         }
    12.         // copy riru.sh
    13.         copy {
    14.             from "$rootDir/template/magisk_module"
    15.             into magiskDir.path
    16.             include 'riru.sh'
    17.             filter { line ->
    18.                 line.replaceAll('%%%RIRU_MODULE_ID%%%', moduleId)
    19.                         .replaceAll('%%%RIRU_MIN_API_VERSION%%%', moduleMinRiruApiVersion.toString())
    20.                         .replaceAll('%%%RIRU_MIN_VERSION_NAME%%%', moduleMinRiruVersionName)
    21.             }
    22.             filter(FixCrLfFilter.class,
    23.                     eol: FixCrLfFilter.CrLf.newInstance("lf"))
    24.         }
    25.         // copy .git files manually since gradle exclude it by default
    26.         Files.copy(file("$rootDir/template/magisk_module/.gitattributes").toPath(), file("${magiskDir.path}/.gitattributes").toPath())

    27.         // generate module.prop
    28.         def modulePropText = ""
    29.         magiskModuleProp.each { k, v -> modulePropText += "$k=$v\n" }
    30.         modulePropText = modulePropText.trim()
    31.         file("$magiskDir/module.prop").text = modulePropText

    32.         // generate module.prop for Riru
    33.         def riruModulePropText = ""
    34.         moduleProp.each { k, v -> riruModulePropText += "$k=$v\n" }
    35.         riruModulePropText = riruModulePropText.trim()
    36.         file(riruDir).mkdirs()

    37.         // module.prop.new will be renamed to module.prop in post-fs-data.sh
    38.         file("$riruDir/module.prop.new").text = riruModulePropText

    39.         // copy native files
    40.         def nativeOutDir = file("build/intermediates/cmake/$variant.name/obj")

    41.         file("$magiskDir/system").mkdirs()
    42.         file("$magiskDir/system_x86").mkdirs()
    43.         renameOrFail(file("$nativeOutDir/arm64-v8a"), file("$magiskDir/system/lib64"))
    44.         renameOrFail(file("$nativeOutDir/armeabi-v7a"), file("$magiskDir/system/lib"))
    45.         renameOrFail(file("$nativeOutDir/x86_64"), file("$magiskDir/system_x86/lib64"))
    46.         renameOrFail(file("$nativeOutDir/x86"), file("$magiskDir/system_x86/lib"))

    47.         // generate sha1sum
    48.         fileTree("$magiskDir").matching {
    49.             exclude "README.md", "META-INF"
    50.         }.visit { f ->
    51.             if (f.directory) return
    52.             file(f.file.path + ".sha256sum").text = calcSha256(f.file)
    53.         }
    54.     }
    55.     task.finalizedBy zipMagiskMoudle
    56. }

    57. task zipMagiskMoudle(type: Zip) {
    58.     from magiskDir
    59.     archiveName zipName
    60.     destinationDir outDir
    61. }
    复制代码
    模块加载过程
    一个典型的riru插件的加载顺序可以简单理解为:

    update_binary -> customize.sh -> post-fs-data.sh

    其中customize.sh是riru重写update_binary进行执行的,其他都是遵循magisk的插件执行顺序。

    接下来就通过这些shell脚本的执行来找到riru是如何定位插件、解析插件、执行插件和完成zygote注入的。

    首先看riru是如何被magisk执行的,因为riru也是一个magisk插件,所以直接先看post-fs-data.sh,可以看到:

  1. LIBRARIES_FILE='/system/etc/public.libraries.txt'
  2. mkdir -p "$MODDIR/system/etc"
  3. cp -f $LIBRARIES_FILE "$MODDIR/$LIBRARIES_FILE"
  4. grep -qxF 'libriru.so' "$MODDIR/$LIBRARIES_FILE" || echo 'libriru.so' >> "$MODDIR/$LIBRARIES_FILE"
复制代码
这个版本的riru直接通过/system/etc/public.libraries.txt自动完成so的dlopen加载。
  1. PS:

  2. riru目前存在了三种已知的app_process注入实现:
  3. 1. 最早通过替换libmemtrack.so
  4. 2. 后来通过/system/etc/public.libraries.txt
  5. 3. 目前通过native bridge即设置系统属性ro.dalvik.vm.native.bridge
复制代码
因此可以直接奔向riru的so,找到.initarray方法(\_attribute__((constructor))),可以看到两个关键方法调用,分别为
  1. <blockquote><div align="left">XHOOK_REGISTER(".*\libandroid_runtime.so$", jniRegisterNativeMethods);</div>
复制代码

  1. load_modules();
复制代码




具体.init_array代码可以看main.cpp中:
  1. extern "C" void constructor() __attribute__((constructor));

  2. // _init_array libriru.so被dlopen后最先执行的函数
  3. void constructor() {
  4. #ifdef DEBUG_APP
  5.     hide::hide_modules(nullptr, 0);
  6. #endif

  7.     if (getuid() != 0)
  8.         return;

  9.     char cmdline[ARG_MAX + 1];
  10.     get_self_cmdline(cmdline, 0);

  11.     if (strcmp(cmdline, "zygote") != 0
  12.         && strcmp(cmdline, "zygote32") != 0
  13.         && strcmp(cmdline, "zygote64") != 0
  14.         && strcmp(cmdline, "usap32") != 0
  15.         && strcmp(cmdline, "usap64") != 0) {
  16.         LOGW("not zygote (cmdline=%s)", cmdline);
  17.         return;
  18.     }

  19.     LOGI("Riru %s (%d) in %s", RIRU_VERSION_NAME, RIRU_VERSION_CODE, cmdline);

  20.     LOGI("config dir is %s", CONFIG_DIR);

  21.     if (access(CONFIG_DIR "/disable", F_OK) == 0) {
  22.         LOGI("%s exists, do nothing", CONFIG_DIR "/disable");
  23.         return;
  24.     }

  25.     read_prop();

  26.     // 通过GOT表hook libandroid_runtime.so中对jniRegisterNativeMethods方法的调用,因为libandroid_runtime.so中所有JNI方法都是通过该方法进行注册,然后再通过手动调用registeNatives来替换
  27.     // 因此通过hook该方法可以在com.android.internal.os.Zygote#nativeForkAndSpecialize和com.android.internal.os.Zygote#nativeForkSystemServer注册时进行替换
  28.     // Riru也因此完成了zygote进程的注入
  29.     XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods);

  30.     if (xhook_refresh(0) == 0) {
  31.         xhook_clear();
  32.         LOGI("hook installed");
  33.     } else {
  34.         LOGE("failed to refresh hook");
  35.     }

  36.     // 加载插件
  37.     load_modules();

  38.     status::writeToFile();
  39. }
复制代码
XHOOK_REGISTER
这里XHOOK_REGISTER是一个宏定义,
  1. XHOOK_REGISTER(".*\libandroid_runtime.so$", jniRegisterNativeMethods);
复制代码
实际上相当于
  1. if (xhook_register(".*\\libandroid_runtime.so$", jniRegisterNativeMethods, (void*)
复制代码
new_jniRegisterNativeMethods和old_jniRegisterNativeMethods则是通过NEW_FUNC_DEF宏定义来进行声明:
  1. #define NEW_FUNC_DEF(ret, func, ...) \
  2.     static ret (*old_##func)(__VA_ARGS__); \
  3.     static ret new_##func(__VA_ARGS__)

  4. NEW_FUNC_DEF(int, jniRegisterNativeMethods, JNIEnv *env, const char *className,
  5.              const JNINativeMethod *methods, int numMethods) {
  6.     api::putNativeMethod(className, methods, numMethods);

  7.     LOGD("jniRegisterNativeMethods %s", className);

  8.     JNINativeMethod *newMethods = nullptr;
  9.     if (strcmp("com/android/internal/os/Zygote", className) == 0) {
  10.         // com/android/internal/os/Zygote注册时回调onRegisterZygote方法获取新的jniMethods列表进行替换
  11.         newMethods = onRegisterZygote(env, className, methods, numMethods);
  12.     } else if (strcmp("android/os/SystemProperties", className) == 0) {
  13.         // hook android.os.SystemProperties#native_set to prevent a critical problem on Android 9
  14.         // see comment of SystemProperties_set in jni_native_method.cpp for detail
  15.         // 回调onRegisterSystemProperties方法
  16.         newMethods = onRegisterSystemProperties(env, className, methods, numMethods);
  17.     }

  18.     int res = old_jniRegisterNativeMethods(env, className, newMethods ? newMethods : methods,
  19.                                            numMethods);
  20.     /*if (!newMethods) {
  21.         NativeMethod::jniRegisterNativeMethodsPost(env, className, methods, numMethods);
  22.     }*/
  23.     delete newMethods;
  24.     return res;
  25. }
复制代码
可以看到在com/android/internal/os/Zygote注册JNI方法时会回调onRegisterZygote方法获取新的jniMethods列表进行替换,在onRegisterZygote方法中主要替换了三个方法的fnPtr指针并兼容不同的安卓版本:
  • nativeForkAndSpecialize
  • nativeSpecializeAppProcess
  • nativeForkSystemServer
这三个方法是应用进程或者系统服务进程被fork 出来的时候会调用的方法,这里以nativeForkAndSpecialize举例分析nativeForkAndSpecialize的AOP逻辑和模块中声明的forkAndSpecializePre/Post等系列方法如何被调用及调用时机。
  1. jint nativeForkAndSpecialize_r(
  2.         JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
  3.         jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name,
  4.         jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote,
  5.         jstring instructionSet, jstring appDataDir, jboolean isTopApp, jobjectArray pkgDataInfoList,
  6.         jobjectArray whitelistedDataInfoList, jboolean bindMountAppDataDirs, jboolean bindMountAppStorageDirs) {

  7.     // 通过nativeForkAndSpecialize_pre和nativeForkAndSpecialize_post完成了nativeForkAndSpecialize方法的AOP
  8.     nativeForkAndSpecialize_pre(env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
  9.                                 se_info, se_name, fdsToClose, fdsToIgnore, is_child_zygote,
  10.                                 instructionSet, appDataDir, isTopApp, pkgDataInfoList, whitelistedDataInfoList,
  11.                                 bindMountAppDataDirs, bindMountAppStorageDirs);

  12.     jint res = ((nativeForkAndSpecialize_r_t *) JNI::Zygote::nativeForkAndSpecialize->fnPtr)(
  13.             env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, se_name,
  14.             fdsToClose, fdsToIgnore, is_child_zygote, instructionSet, appDataDir, isTopApp, pkgDataInfoList,
  15.             whitelistedDataInfoList, bindMountAppDataDirs, bindMountAppStorageDirs);

  16.     nativeForkAndSpecialize_post(env, clazz, uid, res);
  17.     return res;
  18. }

  19. static void nativeForkAndSpecialize_pre(
  20.         JNIEnv *env, jclass clazz, jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
  21.         jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &se_name,
  22.         jintArray &fdsToClose, jintArray &fdsToIgnore, jboolean &is_child_zygote,
  23.         jstring &instructionSet, jstring &appDataDir, jboolean &isTopApp, jobjectArray &pkgDataInfoList,
  24.         jobjectArray &whitelistedDataInfoList, jboolean &bindMountAppDataDirs, jboolean &bindMountAppStorageDirs) {

  25.     // 遍历执行每个模块的forkAndSpecializePre方法,这里可以知道,只需要在模块中声明forkAndSpecializePre方法,即可在com.android.internal.os.Zygote#forkAndSpecialize方法执行前被调用
  26.     for (auto module : *get_modules()) {
  27.         if (!module->hasForkAndSpecializePre())
  28.             continue;

  29.         if (module->hasShouldSkipUid() && module->shouldSkipUid(uid))
  30.             continue;

  31.         if (!module->hasShouldSkipUid() && shouldSkipUid(uid))
  32.             continue;

  33.         module->forkAndSpecializePre(
  34.                 env, clazz, &uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external,
  35.                 &se_info, &se_name, &fdsToClose, &fdsToIgnore, &is_child_zygote,
  36.                 &instructionSet, &appDataDir, &isTopApp, &pkgDataInfoList, &whitelistedDataInfoList,
  37.                 &bindMountAppDataDirs, &bindMountAppStorageDirs);
  38.     }
  39. }
复制代码
load_modules
load_modules解析如下:
  1. void load_modules() {
  2.     DIR *dir;
  3.     struct dirent *entry;
  4.     char path[PATH_MAX];
  5.     void *handle;
  6.     const int riruApiVersion = RIRU_API_VERSION;

  7.     if (!(dir = _opendir(MODULES_DIR))) return;

  8.     // 遍历/data/adb/riru/modules目录下的文件夹
  9.     while ((entry = _readdir(dir))) {
  10.         if (entry->d_type != DT_DIR) continue;

  11.         // 获取文件夹名称
  12.         auto name = entry->d_name;
  13.         if (name[0] == '.') continue;

  14.         // 根据文件夹名称拼接so路径:/system/lib/libriru_%s.so,这一步操作也是在riru插件的customize.sh中完成
  15.         snprintf(path, PATH_MAX, MODULE_PATH_FMT, name);

  16.         if (access(path, F_OK) != 0) {
  17.             PLOGE("access %s", path);
  18.             continue;
  19.         }

  20.         handle = dlopen(path, 0);
  21.         if (!handle) {
  22.             LOGE("dlopen %s failed: %s", path, dlerror());
  23.             continue;
  24.         }

  25.         // 找到so的init方法,这里也是为什么riru的官方文档中指出必须要有一个导出的init函数
  26.         // 如果没有init方法,则将不会被认为是一个合法的riru module,后续get_modules也无法获取到
  27.         auto init = (RiruInit_t *) dlsym(handle, "init");
  28.         if (!init) {
  29.             LOGW("%s does not export init", path);
  30.             cleanup(handle, path);
  31.             continue;
  32.         }

  33.         // 1. pass riru api version, return module's api version
  34.         auto apiVersion = (int *) init((void *) &riruApiVersion);
  35.         if (apiVersion == nullptr) {
  36.             LOGE("%s returns null on step 1", path);
  37.             cleanup(handle, path);
  38.             continue;
  39.         }

  40.         if (*apiVersion < RIRU_MIN_API_VERSION || *apiVersion > RIRU_API_VERSION) {
  41.             LOGW("unsupported API %s: %d", name, *apiVersion);
  42.             cleanup(handle, path);
  43.             continue;
  44.         }

  45.         // 2. create and pass Riru struct by module's api version
  46.         auto module = new RiruModule(strdup(name));
  47.         module->handle = handle;
  48.         module->apiVersion = *apiVersion;

  49.         if (*apiVersion == 9) {
  50.             auto info = init_module_v9(module->token, init);
  51.             if (info == nullptr) {
  52.                 LOGE("%s returns null on step 2", path);
  53.                 cleanup(handle, path);
  54.                 continue;
  55.             }
  56.             module->info(info);
  57.         }

  58.         // 3. let the module to do some cleanup jobs
  59.         init(nullptr);

  60.         // 缓存插件信息到一个vector中,后续通过get_modules()方法才能找到对应的插件信息
  61.         get_modules()->push_back(module);

  62.         LOGI("module loaded: %s (api %d)", module->name, module->apiVersion);
  63.     }

  64.     closedir(dir);

  65.     status::getStatus()->hideEnabled = access(ENABLE_HIDE_FILE, F_OK) == 0;
  66.     if (status::getStatus()->hideEnabled) {
  67.         LOGI("hide is enabled");
  68.         auto modules = get_modules();
  69.         auto names = (const char **) malloc(sizeof(char *) * modules->size());
  70.         int names_count = 0;
  71.         for (auto module : *get_modules()) {
  72.             if (strcmp(module->name, MODULE_NAME_CORE) == 0) continue;
  73.             if (!module->supportHide) {
  74.                 LOGI("module %s does not support hide", module->name);
  75.                 continue;
  76.             }
  77.             names[names_count] = module->name;
  78.             names_count += 1;
  79.         }
  80.         hide::hide_modules(names, names_count);
  81.     } else {
  82.         PLOGE("access " ENABLE_HIDE_FILE);
  83.         LOGI("hide is not enabled");
  84.     }

  85.     for (auto module : *get_modules()) {
  86.         if (module->hasOnModuleLoaded()) {
  87.             LOGV("%s: onModuleLoaded", module->name);
  88.             // 回调module的_onModuleLoaded方法
  89.             module->onModuleLoaded();
  90.         }
  91.     }
  92. }
复制代码


EdXposed如何依赖riru
EdXposed本身也是一个riru插件,但是如何证明呢。

我们可以知道如果作为一个riru插件,必然需要
  • 在/data/adb/riru/modules或者/data/misc/riru/modules下存在一个插件文件夹。
  • 存在与插件文件夹相同名称的/system/lib/libriru_%s.so
  • so中可能有init或者forkAndSpecializePre等zygote进程相关的方法
在edxp-core中,依然通过build.gradle为入口分析zip打包的逻辑,可以看到也是一个标准的magisk module installer,其中customize.sh中有一行关键代码创建了riru插件的相关目录:

  1. # 创建riru的module目录,这里可以看出edxp同时是一个riru module,riru通过检测目录加载对应的libriru_xxx.so
  2. [[ -d "${RIRU_TARGET}" ]] || mkdir -p "${RIRU_TARGET}" || abort "! Can't mkdir -p ${RIRU_TARGET}"

  3. # RIRU_TARGET变量的赋值过程
  4. RIRU_PATH="/data/misc/riru"
  5. # 为libriru_edxp.so设置随机后缀, libriru_edxp.so -> libriru_{randomNum}.so
  6. # libriru_edxp.so由edxp-core:src/main/cpp/main相关c文件编译得出
  7. # getRandomNameExist方法作用为获取一个/proc/sys/kernel/random/uuid不存在的四位随机数
  8. RIRU_EDXP="$(getRandomNameExist 4 "libriru_" ".so" "
  9. /system/lib
  10. /system/lib64
  11. ")"
  12. RIRU_MODULES="${RIRU_PATH}/modules"
  13. RIRU_TARGET="${RIRU_MODULES}/${RIRU_EDXP}"
复制代码
通过这段脚本可以看出edxp插件在magisk加载时创建了一个riru插件目录,并且把libriru_edxp.so重命名成一个随机后缀的so文件由riru进行加载。

接着分析libriru_edxp.so,通过CMakeLists.txt可以知道是由edxp-core中编译得到,查看edxp-core/src/main/cpp/main/src/main.cpp中可以看到确实声明了riru中的一些方法模版钩子:
  • nativeForkAndSpecializePre
  • nativeForkAndSpecializePost
  • nativeForkSystemServerPre
  • nativeForkSystemServerPost
  • specializeAppProcessPre
  • specializeAppProcessPost
  • onModuleLoaded
  • shouldSkipUid
等方法,一切都可以想明白了。接下来就是edxp如何进行ART Hook。
PS
  • riru-docs :https://github.com/AlienwareHe/riru-docs
  • 目前的riru v22中可以看到一个riru module必须包含一个init导出函数,但是在edxp里并没有发现有init函数,在v19的riru中是没有init函数限制,而是校验module.prop。
  • riru v22中提到切换到native bridge方式注入之后,会导致riru和模块的加载时机变晚,对于Xposed框架可能会有变化和影响。



发表于 2021-8-11 13:47:24 | 显示全部楼层
现在手机速度也快了很多
发表于 2021-8-11 13:47:31 | 显示全部楼层
盖了帽了,厉害
发表于 2021-8-11 13:47:38 | 显示全部楼层
好用,速度比之前快多了,用了感觉很不错
发表于 2021-8-11 13:47:57 | 显示全部楼层
这网站可以哦
发表于 2021-8-11 13:48:07 | 显示全部楼层
这是真的吗
发表于 2021-8-11 13:48:05 | 显示全部楼层
过来看看!第一次来这个论坛!来学学技术
发表于 2021-8-11 13:48:20 | 显示全部楼层
感谢客服指导
发表于 2021-8-11 13:48:29 | 显示全部楼层
感谢远程协助
发表于 2023-1-17 03:22:39 | 显示全部楼层
好的帖子,需要下很大的精力
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

技术交流售后群

QQ|小黑屋|手机版|站点找错-建议|AI手机网 |Sitemap



GMT+8, 2024-4-27 17:09 , Processed in 0.180580 second(s), 27 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表