diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java index 47eebdcd..9f99619f 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java @@ -7,6 +7,7 @@ import android.text.TextUtils; import com.elderdrivers.riru.xposed.Main; import com.elderdrivers.riru.xposed.config.ConfigManager; +import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; @@ -226,4 +227,20 @@ public class DexMakerUtils { TypeId boxTypeId; code.returnValue(resultLocals.get(returnType)); } + + public static String getSha1Hex(String text) { + final MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + byte[] result = digest.digest(text.getBytes("UTF-8")); + StringBuilder sb = new StringBuilder(); + for (byte b : result) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (Exception e) { + DexLog.e("error hashing target method: " + text, e); + } + return ""; + } } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java index f9aca719..22039ee3 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java @@ -1,7 +1,6 @@ package com.elderdrivers.riru.xposed.dexmaker; import com.elderdrivers.riru.xposed.Main; -import com.elderdrivers.riru.xposed.util.FileUtils; import java.io.File; import java.lang.reflect.Constructor; @@ -72,8 +71,6 @@ public final class DynamicBridge { if (!dexPathInited.compareAndSet(false, true)) { return; } - // delete previous compiled dex to prevent potential crashing - // TODO find a way to reuse them in consideration of performance try { // we always choose to use device encrypted storage data on android N and later // in case some app is installing hooks before phone is unlocked @@ -83,10 +80,6 @@ public final class DynamicBridge { dexOptDir = new File(dexDir, "oat"); dexDir.mkdirs(); DexLog.d(Main.appProcessName + " deleting dir: " + dexOptDir.getAbsolutePath()); - try { - FileUtils.delete(dexOptDir); - } catch (Throwable throwable) { - } } catch (Throwable throwable) { DexLog.e("error when init dex path", throwable); } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java index 7b2bcb60..39507c75 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java @@ -42,7 +42,10 @@ public class HookerDexMaker { public static final String METHOD_NAME_SETUP = "setup"; public static final TypeId objArrayTypeId = TypeId.get(Object[].class); private static final String CLASS_DESC_PREFIX = "L"; - private static final String CLASS_NAME_PREFIX = "EdHooker"; + /** + * Note: this identifier is used in native codes to pass class access verification. + */ + private static final String CLASS_NAME_PREFIX = "EdHooker_"; private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo"; private static final String FIELD_NAME_METHOD = "method"; private static final String PARAMS_FIELD_NAME_METHOD = "method"; @@ -183,26 +186,30 @@ public class HookerDexMaker { @TargetApi(Build.VERSION_CODES.O) private void doMake() throws Exception { + final boolean useInMemoryCl = TextUtils.isEmpty(mDexDirPath); mDexMaker = new DexMaker(); - // Generate a Hooker class. - String className = CLASS_NAME_PREFIX + sClassNameSuffix.getAndIncrement(); - String classDesc = CLASS_DESC_PREFIX + className + ";"; - mHookerTypeId = TypeId.get(classDesc); - mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT); - generateFields(); - generateSetupMethod(); - generateBackupMethod(); - generateHookMethod(); - generateCallBackupMethod(); - ClassLoader loader; - if (TextUtils.isEmpty(mDexDirPath)) { - // in memory dex classloader + // Generate a Hooker class. + String className = CLASS_NAME_PREFIX; + if (!useInMemoryCl) { + // if not using InMemoryDexClassLoader, className is also used as dex file name + // so it should be different from each other + String suffix = DexMakerUtils.getSha1Hex(mMember.toString()); + if (TextUtils.isEmpty(suffix)) { // just in case + suffix = String.valueOf(sClassNameSuffix.getAndIncrement()); + } + className = className + suffix; + if (!new File(mDexDirPath, className).exists()) { + // if file exists, reuse it and skip generating + doGenerate(className); + } + // load dex file from disk + loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath), className); + } else { + // do everything in memory + doGenerate(className); byte[] dexBytes = mDexMaker.generate(); loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader); - } else { - // Create the dex file and load it. - loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath)); } mHookClass = loader.loadClass(className); @@ -216,6 +223,17 @@ public class HookerDexMaker { HookMain.backupAndHook(mMember, mHookMethod, mBackupMethod); } + private void doGenerate(String className) { + String classDesc = CLASS_DESC_PREFIX + className + ";"; + mHookerTypeId = TypeId.get(classDesc); + mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT); + generateFields(); + generateSetupMethod(); + generateBackupMethod(); + generateHookMethod(); + generateCallBackupMethod(); + } + public Method getHookMethod() { return mHookMethod; } diff --git a/Core/build.gradle b/Core/build.gradle index bdb1faaf..77d36d30 100644 --- a/Core/build.gradle +++ b/Core/build.gradle @@ -1,5 +1,7 @@ +import org.gradle.internal.os.OperatingSystem; + apply plugin: 'com.android.library' -version "v0.3.1.2_beta" +version "v0.3.1.5_beta-SNAPSHOT" extensions["module_name"] = "EdXposed" android { compileSdkVersion 28 @@ -28,30 +30,24 @@ afterEvaluate { def zipTask = task("zip${nameCapped}", type: Exec, dependsOn: ":Bridge:makeAndCopy${nameCapped}") { workingDir '..' - commandLine 'sh', 'build.sh',\ - project.name,\ - "${project.version}-${nameLowered}",\ - "${project.extensions['module_name']}" + commandLine 'sh', 'build.sh', \ + project.name, \ + "${project.version}-${nameLowered}", \ + "${project.extensions['module_name']}" } -// def renameTask = task("build${nameCapped}", type: Copy) { -// from "release/magisk-${project.name}-${project.version}.zip" -// into "release" -// rename("${project.name}", "${project.extensions['module_name']}") -// rename("${project.version}", "${project.version}-${nameLowered}") -// } - def pushTask = task("push${nameCapped}", type: Exec) { workingDir 'release' - commandLine 'cmd', '/c', - "adb push magisk-${project.extensions['module_name']}" + - "-${project.version}-${nameLowered}.zip /sdcard/" + def commands = ["adb", "push", "magisk-${project.extensions['module_name']}" + + "-${project.version}-${nameLowered}.zip", "/sdcard/"] + if (OperatingSystem.current().isWindows()) { + commandLine 'cmd', '/c', commands.join(" ") + } else { + commandLine commands + } } -// renameTask.dependsOn(zipTask) pushTask.dependsOn(zipTask) } } -dependencies { -} diff --git a/Core/jni/main/Android.mk b/Core/jni/main/Android.mk index c7f12670..08325358 100644 --- a/Core/jni/main/Android.mk +++ b/Core/jni/main/Android.mk @@ -13,6 +13,7 @@ LOCAL_LDFLAGS := -Wl LOCAL_SRC_FILES:= \ main.cpp \ native_hook/native_hook.cpp \ + native_hook/riru_hook.cpp \ include/misc.cpp \ include/riru.c \ yahfa/HookMain.c \ diff --git a/Core/jni/main/native_hook/native_hook.cpp b/Core/jni/main/native_hook/native_hook.cpp index cb8fb739..efa989a7 100644 --- a/Core/jni/main/native_hook/native_hook.cpp +++ b/Core/jni/main/native_hook/native_hook.cpp @@ -7,6 +7,7 @@ #include "include/logging.h" #include "native_hook.h" +#include "riru_hook.h" static bool inlineHooksInstalled = false; @@ -51,8 +52,9 @@ static bool onIsInSamePackageCalled(void *thiz, void *that) { std::string storage1, storage2; const char *thisDesc = (*getDesc)(thiz, &storage1); const char *thatDesc = (*getDesc)(that, &storage2); - if (strstr(thisDesc, "EdHooker") != nullptr - || strstr(thatDesc, "EdHooker") != nullptr + // Note: these identifiers should be consistent with those in Java layer + if (strstr(thisDesc, "EdHooker_") != nullptr + || strstr(thatDesc, "EdHooker_") != nullptr || strstr(thisDesc, "com/elderdrivers/riru/") != nullptr || strstr(thatDesc, "com/elderdrivers/riru/") != nullptr) { return true; @@ -272,6 +274,7 @@ void install_inline_hooks() { LOGE("api level not supported: %d, skip", api_level); return; } + install_riru_hooks(); LOGI("using api level %d", api_level); void *whaleHandle = dlopen(kLibWhalePath, RTLD_LAZY | RTLD_GLOBAL); if (!whaleHandle) { diff --git a/Core/jni/main/native_hook/riru_hook.cpp b/Core/jni/main/native_hook/riru_hook.cpp new file mode 100644 index 00000000..4a497c53 --- /dev/null +++ b/Core/jni/main/native_hook/riru_hook.cpp @@ -0,0 +1,82 @@ +// +// Created by solo on 2019/3/16. +// + +#include +#include +#include +#include +#include +#include +#include +#include "riru_hook.h" + +#define PROP_KEY_COMPILER_FILTER "dalvik.vm.dex2oat-filter" +#define PROP_KEY_COMPILER_FLAGS "dalvik.vm.dex2oat-flags" +#define PROP_VALUE_COMPILER_FILTER "quicken" +#define PROP_VALUE_COMPILER_FLAGS "--inline-max-code-units=0" + +#define XHOOK_REGISTER(NAME) \ + if (xhook_register(".*", #NAME, (void*) new_##NAME, (void **) &old_##NAME) == 0) { \ + if (riru_get_version() >= 8) { \ + void *f = riru_get_func(#NAME); \ + if (f != nullptr) { \ + memcpy(&old_##NAME, &f, sizeof(void *)); \ + } \ + riru_set_func(#NAME, (void *) new_##NAME); \ + } \ + } else { \ + LOGE("failed to register riru hook " #NAME "."); \ + } + +#define NEW_FUNC_DEF(ret, func, ...) \ + static ret (*old_##func)(__VA_ARGS__); \ + static ret new_##func(__VA_ARGS__) + +NEW_FUNC_DEF(int, __system_property_get, const char *key, char *value) { + int res = old___system_property_get(key, value); + if (key) { + if (strcmp(PROP_KEY_COMPILER_FILTER, key) == 0) { + strcpy(value, PROP_VALUE_COMPILER_FILTER); + LOGI("system_property_get: %s -> %s", key, value); + } else if (strcmp(PROP_KEY_COMPILER_FLAGS, key) == 0) { + strcpy(value, PROP_VALUE_COMPILER_FLAGS); + LOGI("system_property_get: %s -> %s", key, value); + } + } + return res; +} + +NEW_FUNC_DEF(std::string, + _ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_, + const std::string &key, const std::string &default_value) { + std::string res = old__ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_( + key, default_value); + if (strcmp(PROP_KEY_COMPILER_FILTER, key.c_str()) == 0) { + res = PROP_VALUE_COMPILER_FILTER; + LOGI("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str()); + } else if (strcmp(PROP_KEY_COMPILER_FLAGS, key.c_str()) == 0) { + res = PROP_VALUE_COMPILER_FLAGS; + LOGI("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str()); + } + return res; +} + +void install_riru_hooks() { + + LOGI("install riru hook"); + + XHOOK_REGISTER(__system_property_get); + + if (GetAndroidApiLevel() >= ANDROID_P) { + XHOOK_REGISTER( + _ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_); + } + + if (xhook_refresh(0) == 0) { + xhook_clear(); + LOGI("riru hooks installed"); + } else { + LOGE("failed to install riru hooks"); + } +} \ No newline at end of file diff --git a/Core/jni/main/native_hook/riru_hook.h b/Core/jni/main/native_hook/riru_hook.h new file mode 100644 index 00000000..29385cd4 --- /dev/null +++ b/Core/jni/main/native_hook/riru_hook.h @@ -0,0 +1,10 @@ +// +// Created by solo on 2019/3/16. +// + +#ifndef EDXPOSED_RIRU_HOOK_H +#define EDXPOSED_RIRU_HOOK_H + +void install_riru_hooks(); + +#endif //EDXPOSED_RIRU_HOOK_H diff --git a/Core/template_override/common/post-fs-data.sh b/Core/template_override/common/post-fs-data.sh index 513beb64..bf60a151 100644 --- a/Core/template_override/common/post-fs-data.sh +++ b/Core/template_override/common/post-fs-data.sh @@ -22,4 +22,4 @@ supolicy --live "allow zygote apk_data_file * *" . ${MODDIR}/util_functions.sh -start_verbose_log_catcher +start_log_catchers diff --git a/Core/template_override/common/util_functions.sh b/Core/template_override/common/util_functions.sh index 7c479901..d5bc3f8e 100644 --- a/Core/template_override/common/util_functions.sh +++ b/Core/template_override/common/util_functions.sh @@ -1,6 +1,6 @@ #!/system/bin/sh -EDXP_VERSION="0.3.1.2_beta (3120)" +EDXP_VERSION="0.3.1.5_beta-SNAPSHOT (3150)" ANDROID_SDK=`getprop ro.build.version.sdk` BUILD_DESC=`getprop ro.build.description` PRODUCT=`getprop ro.build.product` @@ -23,32 +23,40 @@ setup_log_path () { else PATH_PREFIX=${PATH_PREFIX_LEGACY} fi - BASE_PATH=${PATH_PREFIX}${EDXP_INSTALLER} - if [[ -d ${BASE_PATH} ]] + DEFAULT_BASE_PATH=${PATH_PREFIX}${EDXP_INSTALLER} + BASE_PATH=${DEFAULT_BASE_PATH} + if [[ ! -d ${BASE_PATH} ]] then - LOG_PATH=${BASE_PATH}/log - else BASE_PATH=${PATH_PREFIX}${EDXP_MANAGER} - if [[ -d ${BASE_PATH} ]] + if [[ ! -d ${BASE_PATH} ]] then - LOG_PATH=${BASE_PATH}/log - else BASE_PATH=${PATH_PREFIX}${XP_INSTALLER} - if [[ -d ${BASE_PATH} ]] + if [[ ! -d ${BASE_PATH} ]] then - LOG_PATH=${BASE_PATH}/log - else - LOG_PATH=${BASE_PATH}/log + BASE_PATH=${DEFAULT_BASE_PATH} fi fi fi + LOG_PATH=${BASE_PATH}/log + CONF_PATH=${BASE_PATH}/conf + DISABLE_VERBOSE_LOG_FILE=${CONF_PATH}/disable_verbose_log + LOG_VERBOSE=true + if [[ -f ${DISABLE_VERBOSE_LOG_FILE} ]]; then LOG_VERBOSE=false; fi } start_log_cather () { LOG_FILE_NAME=$1 + LOG_TAG_FILTERS=$2 + CLEAN_OLD=$3 + START_NEW=$4 LOG_FILE=${LOG_PATH}/${LOG_FILE_NAME} mkdir -p ${LOG_PATH} - rm -rf ${LOG_FILE} + if [[ ${CLEAN_OLD} = true ]]; then + rm -rf ${LOG_FILE} + fi + if [[ ${START_NEW} = false ]]; then + return + fi touch ${LOG_FILE} chmod 777 ${LOG_FILE} echo "--------- beginning of head">>${LOG_FILE} @@ -67,11 +75,20 @@ start_log_cather () { echo "Manufacture: ${MANUFACTURE}">>${LOG_FILE} echo "Brand: ${BRAND}">>${LOG_FILE} echo "Product: ${PRODUCT}">>${LOG_FILE} - logcat -f ${LOG_FILE} *:S logcatcher-xposed-mlgmxyysd:S EdXposed-Fwk:V EdXposed-dexmaker:V XSharedPreferences:V EdXposed-Bridge:V EdXposed-YAHFA:V EdXposed-Core-Native:V xhook:V Riru:V RiruManager:V EdXposed-Manager:V XposedInstaller:V & + logcat -f ${LOG_FILE} *:S ${LOG_TAG_FILTERS} & } start_verbose_log_catcher () { - start_log_cather error.log + start_log_cather all.log "EdXposed-Fwk:V EdXposed-dexmaker:V XSharedPreferences:V EdXposed-Bridge:V EdXposed-YAHFA:V EdXposed-Core-Native:V EdXposed-Manager:V XposedInstaller:V" true ${LOG_VERBOSE} +} + +start_bridge_log_catcher () { + start_log_cather error.log "XSharedPreferences:V EdXposed-Bridge:V" true true +} + +start_log_catchers () { + start_bridge_log_catcher + start_verbose_log_catcher } setup_log_path diff --git a/Core/template_override/config.sh b/Core/template_override/config.sh index a40195e6..d644a3d6 100644 --- a/Core/template_override/config.sh +++ b/Core/template_override/config.sh @@ -41,7 +41,7 @@ LATESTARTSERVICE=false print_modname() { ui_print "************************************" - ui_print " Riru - Ed Xposed v0.3.1.2 " + ui_print " Riru - Ed Xposed v0.3.1.5 " ui_print "************************************" } diff --git a/Core/template_override/module.prop b/Core/template_override/module.prop index 01b6b215..0d135f09 100644 --- a/Core/template_override/module.prop +++ b/Core/template_override/module.prop @@ -1,7 +1,7 @@ id=riru_edxposed name=Riru - Ed Xposed -version=v0.3.1.2_beta -versionCode=3120 +version=v0.3.1.5_beta-SNAPSHOT +versionCode=3150 author=solohsu & MlgmXyysd description=Magisk version of Xposed. Require Riru - Core installed. minMagisk=17000 diff --git a/Core/template_override/riru_module.prop b/Core/template_override/riru_module.prop index 7e166e69..9636ce68 100644 --- a/Core/template_override/riru_module.prop +++ b/Core/template_override/riru_module.prop @@ -1,5 +1,5 @@ name=Ed Xposed -version=v0.3.1.2_beta -versionCode=3120 +version=v0.3.1.5_beta-SNAPSHOT +versionCode=3150 author=solohsu & MlgmXyysd description=Magisk version of Xposed. Require Riru - Core installed. diff --git a/Core/template_override/system/framework/edconfig.dex b/Core/template_override/system/framework/edconfig.dex index 55c27bee..12d12e21 100644 --- a/Core/template_override/system/framework/edconfig.dex +++ b/Core/template_override/system/framework/edconfig.dex @@ -1,4 +1,4 @@ -version=90.0-0.3.1.2-beta +version=90.0-0.3.1.5-beta-SNAPSHOT arch=arm64 minsdk=23 maxsdk=28 diff --git a/Core/template_override/system/framework/eddexmaker.dex b/Core/template_override/system/framework/eddexmaker.dex index 1410d305..6d194a63 100644 Binary files a/Core/template_override/system/framework/eddexmaker.dex and b/Core/template_override/system/framework/eddexmaker.dex differ diff --git a/README.md b/README.md index 6f3ff0b5..7a90408c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # EdXposed -A Riru module trying to provide a ART hooking framework (mainly for Android Pie) which delivers consistent APIs with the OG Xposed, leveraging YAHFA hooking framework. +A Riru module trying to provide a ART hooking framework (initially for Android Pie) which delivers consistent APIs with the OG Xposed, leveraging YAHFA hooking framework. ## Credits @@ -11,6 +11,13 @@ A Riru module trying to provide a ART hooking framework (mainly for Android Pie) - [dexmaker](https://github.com/linkedin/dexmaker) and [dalvikdx](https://github.com/JakeWharton/dalvik-dx): dynamiclly generate YAHFA hooker classes - [Whale](https://github.com/asLody/whale): used for inline hooking +## Supported versions + +- Android Oreo (8.0, 8.1) +- Android Pie (9.0) + +For devices with Android 7.x and lower, original Xposed is strongly recommended. + ## Known issues - resources hooking is not supported yet diff --git a/dexmaker/src/main/java/external/com/android/dx/DexMaker.java b/dexmaker/src/main/java/external/com/android/dx/DexMaker.java index 411418f8..3f151489 100644 --- a/dexmaker/src/main/java/external/com/android/dx/DexMaker.java +++ b/dexmaker/src/main/java/external/com/android/dx/DexMaker.java @@ -471,6 +471,10 @@ public final class DexMaker { } } + public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException { + return generateAndLoad(parent, dexCache, null); + } + /** * Generates a dex file and loads its types into the current process. * @@ -496,8 +500,9 @@ public final class DexMaker { * @param dexCache the destination directory where generated and optimized * dex files will be written. If null, this class will try to guess the * application's private data dir. + * @param fileName the name of dex file */ - public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException { + public ClassLoader generateAndLoad(ClassLoader parent, File dexCache, String fileName) throws IOException { if (dexCache == null) { String property = System.getProperty("dexmaker.dexcache"); if (property != null) { @@ -511,7 +516,9 @@ public final class DexMaker { } } - File result = new File(dexCache, generateFileName()); + if (fileName == null || fileName.isEmpty()) + fileName = generateFileName(); + File result = new File(dexCache, fileName); // Check that the file exists. If it does, return a DexClassLoader and skip all // the dex bytecode generation. if (result.exists()) {