From d0b1d7c52b1c6544bbdcad40f4db424e85f2b1dd Mon Sep 17 00:00:00 2001 From: solohsu Date: Wed, 20 Mar 2019 10:29:51 +0800 Subject: [PATCH] Introduce Whale hook for Android --- edxp-core/build.gradle | 21 ++ edxp-core/jni/main/native_hook/native_hook.h | 4 +- .../system/etc/public.libraries-edxp.txt | 1 + .../lib/{libwhale.so => libwhale.edxp.so} | Bin .../lib64/{libwhale.so => libwhale.edxp.so} | Bin .../lib/{libwhale.so => libwhale.edxp.so} | Bin .../lib64/{libwhale.so => libwhale.edxp.so} | Bin .../sandhook/config/SandHookProvider.java | 9 +- edxp-whale/.gitignore | 1 + edxp-whale/build.gradle | 61 ++++++ edxp-whale/libs/framework-stub.jar | Bin 0 -> 15295 bytes edxp-whale/proguard-rules.pro | 33 ++++ edxp-whale/src/main/AndroidManifest.xml | 1 + .../java/com/elderdrivers/riru/edxp/Main.java | 146 ++++++++++++++ .../edxp/yahfa/config/WhaleEdxpConfig.java | 23 +++ .../edxp/yahfa/config/WhaleHookProvider.java | 37 ++++ .../riru/edxp/yahfa/core/HookMain.java | 186 ++++++++++++++++++ .../edxp/yahfa/core/HookMethodResolver.java | 155 +++++++++++++++ .../riru/edxp/yahfa/dexmaker/DexLog.java | 37 ++++ .../riru/edxp/yahfa/dexmaker/MethodInfo.java | 94 +++++++++ .../riru/edxp/yahfa/entry/Router.java | 122 ++++++++++++ .../entry/bootstrap/AppBootstrapHookInfo.java | 14 ++ .../entry/bootstrap/SysBootstrapHookInfo.java | 16 ++ .../entry/bootstrap/SysInnerHookInfo.java | 10 + .../entry/bootstrap/WorkAroundHookInfo.java | 10 + .../entry/hooker/HandleBindAppHooker.java | 89 +++++++++ .../hooker/LoadedApkConstructorHooker.java | 98 +++++++++ .../entry/hooker/OnePlusWorkAroundHooker.java | 41 ++++ .../hooker/StartBootstrapServicesHooker.java | 66 +++++++ .../yahfa/entry/hooker/SystemMainHooker.java | 43 ++++ .../entry/hooker/XposedBlackListHooker.java | 87 ++++++++ .../entry/hooker/XposedInstallerHooker.java | 64 ++++++ .../edxp/yahfa/proxy/BlackWhiteListProxy.java | 132 +++++++++++++ .../riru/edxp/yahfa/proxy/NormalProxy.java | 70 +++++++ .../edxp/yahfa/util/InlinedMethodCallers.java | 49 +++++ .../yahfa/util/PrebuiltMethodsDeopter.java | 41 ++++ .../main/java/com/lody/whale/VMHelper.java | 105 ++++++++++ .../java/com/lody/whale/WhaleRuntime.java | 74 +++++++ .../edxp/yahfa/config/YahfaHookProvider.java | 7 +- settings.gradle | 2 +- .../riru/edxp/config/EdXpConfigGlobal.java | 7 +- .../riru/edxp/hook/HookProvider.java | 5 +- .../de/robv/android/xposed/XposedBridge.java | 9 +- 43 files changed, 1956 insertions(+), 14 deletions(-) rename edxp-core/template_override/system/lib/{libwhale.so => libwhale.edxp.so} (100%) rename edxp-core/template_override/system/lib64/{libwhale.so => libwhale.edxp.so} (100%) rename edxp-core/template_override/system_x86/lib/{libwhale.so => libwhale.edxp.so} (100%) rename edxp-core/template_override/system_x86/lib64/{libwhale.so => libwhale.edxp.so} (100%) create mode 100644 edxp-whale/.gitignore create mode 100644 edxp-whale/build.gradle create mode 100644 edxp-whale/libs/framework-stub.jar create mode 100644 edxp-whale/proguard-rules.pro create mode 100644 edxp-whale/src/main/AndroidManifest.xml create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleEdxpConfig.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleHookProvider.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMain.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMethodResolver.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexLog.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/AppBootstrapHookInfo.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysBootstrapHookInfo.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysInnerHookInfo.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/WorkAroundHookInfo.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/HandleBindAppHooker.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/LoadedApkConstructorHooker.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/OnePlusWorkAroundHooker.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/StartBootstrapServicesHooker.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/SystemMainHooker.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedBlackListHooker.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedInstallerHooker.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/BlackWhiteListProxy.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/NormalProxy.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/InlinedMethodCallers.java create mode 100644 edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/PrebuiltMethodsDeopter.java create mode 100644 edxp-whale/src/main/java/com/lody/whale/VMHelper.java create mode 100644 edxp-whale/src/main/java/com/lody/whale/WhaleRuntime.java diff --git a/edxp-core/build.gradle b/edxp-core/build.gradle index 9e1a3998..01b3910c 100644 --- a/edxp-core/build.gradle +++ b/edxp-core/build.gradle @@ -69,6 +69,27 @@ afterEvaluate { } pushTask.dependsOn(zipTask) + + zipTask = task("zipWhale${nameCapped}", type: Exec, dependsOn: ":edxp-whale:makeAndCopy${nameCapped}") { + workingDir '..' + commandLine 'sh', 'build.sh', \ + project.name, \ + "Whale-${project.version}-${nameLowered}", \ + "${project.extensions['module_name']}" + } + + pushTask = task("pushWhale${nameCapped}", type: Exec) { + workingDir 'release' + def commands = ["adb", "push", "magisk-${project.extensions['module_name']}-Whale" + + "-${project.version}-${nameLowered}.zip", "/sdcard/"] + if (OperatingSystem.current().isWindows()) { + commandLine 'cmd', '/c', commands.join(" ") + } else { + commandLine commands + } + } + + pushTask.dependsOn(zipTask) } } diff --git a/edxp-core/jni/main/native_hook/native_hook.h b/edxp-core/jni/main/native_hook/native_hook.h index 0a79f2c2..eb6f7a9a 100644 --- a/edxp-core/jni/main/native_hook/native_hook.h +++ b/edxp-core/jni/main/native_hook/native_hook.h @@ -5,10 +5,10 @@ #if defined(__LP64__) static constexpr const char *kLibArtPath = "/system/lib64/libart.so"; -static constexpr const char *kLibWhalePath = "/system/lib64/libwhale.so"; +static constexpr const char *kLibWhalePath = "/system/lib64/libwhale.edxp.so"; #else static constexpr const char *kLibArtPath = "/system/lib/libart.so"; -static constexpr const char *kLibWhalePath = "/system/lib/libwhale.so"; +static constexpr const char *kLibWhalePath = "/system/lib/libwhale.edxp.so"; #endif #define XHOOK_REGISTER(NAME) \ diff --git a/edxp-core/template_override/system/etc/public.libraries-edxp.txt b/edxp-core/template_override/system/etc/public.libraries-edxp.txt index 3c8badba..03b2c76d 100644 --- a/edxp-core/template_override/system/etc/public.libraries-edxp.txt +++ b/edxp-core/template_override/system/etc/public.libraries-edxp.txt @@ -1 +1,2 @@ libsandhook.edxp.so +libwhale.edxp.so diff --git a/edxp-core/template_override/system/lib/libwhale.so b/edxp-core/template_override/system/lib/libwhale.edxp.so similarity index 100% rename from edxp-core/template_override/system/lib/libwhale.so rename to edxp-core/template_override/system/lib/libwhale.edxp.so diff --git a/edxp-core/template_override/system/lib64/libwhale.so b/edxp-core/template_override/system/lib64/libwhale.edxp.so similarity index 100% rename from edxp-core/template_override/system/lib64/libwhale.so rename to edxp-core/template_override/system/lib64/libwhale.edxp.so diff --git a/edxp-core/template_override/system_x86/lib/libwhale.so b/edxp-core/template_override/system_x86/lib/libwhale.edxp.so similarity index 100% rename from edxp-core/template_override/system_x86/lib/libwhale.so rename to edxp-core/template_override/system_x86/lib/libwhale.edxp.so diff --git a/edxp-core/template_override/system_x86/lib64/libwhale.so b/edxp-core/template_override/system_x86/lib64/libwhale.edxp.so similarity index 100% rename from edxp-core/template_override/system_x86/lib64/libwhale.so rename to edxp-core/template_override/system_x86/lib64/libwhale.edxp.so diff --git a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java index da08315b..967b2410 100644 --- a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java +++ b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java @@ -2,12 +2,10 @@ package com.elderdrivers.riru.edxp.sandhook.config; import com.elderdrivers.riru.edxp.hook.HookProvider; import com.elderdrivers.riru.edxp.sandhook.dexmaker.DexMakerUtils; -import com.elderdrivers.riru.edxp.sandhook.dexmaker.DynamicBridge; import com.elderdrivers.riru.edxp.sandhook.util.PrebuiltMethodsDeopter; import com.swift.sandhook.xposedcompat.XposedCompat; import com.swift.sandhook.xposedcompat.methodgen.SandHookXposedBridge; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import de.robv.android.xposed.XposedBridge; @@ -19,7 +17,7 @@ public class SandHookProvider implements HookProvider { } @Override - public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable { + public Object invokeOriginalMethod(Member method, long methodId, Object thisObject, Object[] args) throws Throwable { return SandHookXposedBridge.invokeOriginalMethod(method, thisObject, args); } @@ -32,4 +30,9 @@ public class SandHookProvider implements HookProvider { public void deoptMethods(String packageName, ClassLoader classLoader) { PrebuiltMethodsDeopter.deoptMethods(packageName, classLoader); } + + @Override + public long getMethodId(Member member) { + return 0; + } } diff --git a/edxp-whale/.gitignore b/edxp-whale/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/edxp-whale/.gitignore @@ -0,0 +1 @@ +/build diff --git a/edxp-whale/build.gradle b/edxp-whale/build.gradle new file mode 100644 index 00000000..7087eedb --- /dev/null +++ b/edxp-whale/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "com.elderdrivers.riru.edxp.whale" + minSdkVersion 26 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + compileOnly files("libs/framework-stub.jar") + implementation project(':edxp-common') + implementation project(':xposed-bridge') +} + + +preBuild.doLast { + def imlFile = file(project.name + ".iml") + println 'Change ' + project.name + '.iml order' + try { + def parsedXml = (new groovy.util.XmlParser()).parse(imlFile) + def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' } + parsedXml.component[1].remove(jdkNode) + def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform" + new groovy.util.Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK']) + groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile)) + } catch (FileNotFoundException e) { + // nop, iml not found + } +} + +afterEvaluate { + + tasks.withType(JavaCompile) { + options.compilerArgs.add("-Xbootclasspath/p:${projectDir.absolutePath}/libs/framework-stub.jar") + } + + android.applicationVariants.all { variant -> + def nameCapped = variant.name.capitalize() + def nameLowered = variant.name.toLowerCase() + + def makeAndCopyTask = task("makeAndCopy${nameCapped}", type: Jar, dependsOn: "assemble${nameCapped}") { + from "build/intermediates/dex/${nameLowered}/mergeDex${nameCapped}/out/" + destinationDir file("../edxp-core/template_override/system/framework/") + baseName "edxp" + } + } +} \ No newline at end of file diff --git a/edxp-whale/libs/framework-stub.jar b/edxp-whale/libs/framework-stub.jar new file mode 100644 index 0000000000000000000000000000000000000000..36cd86b3c4fd658ed66ff5195ae0cc54d6ebc485 GIT binary patch literal 15295 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 SP*C)sAAQg^FUtb{>;D0!&FVP- literal 0 HcmV?d00001 diff --git a/edxp-whale/proguard-rules.pro b/edxp-whale/proguard-rules.pro new file mode 100644 index 00000000..1d01a7a1 --- /dev/null +++ b/edxp-whale/proguard-rules.pro @@ -0,0 +1,33 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-dontobfuscate +-keep class de.robv.android.xposed.** {*;} +-keep class android.** { *; } + +-keep interface com.elderdrivers.riru.common.KeepAll +-keep interface com.elderdrivers.riru.common.KeepMembers + +-keep class * implements com.elderdrivers.riru.common.KeepAll { *; } +-keepclassmembers class * implements com.elderdrivers.riru.common.KeepMembers { *; } + +-keep class * com.lody.** {*;} \ No newline at end of file diff --git a/edxp-whale/src/main/AndroidManifest.xml b/edxp-whale/src/main/AndroidManifest.xml new file mode 100644 index 00000000..38b7acf3 --- /dev/null +++ b/edxp-whale/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java new file mode 100644 index 00000000..33d4939e --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java @@ -0,0 +1,146 @@ +package com.elderdrivers.riru.edxp; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.os.Process; + +import com.elderdrivers.riru.common.KeepAll; +import com.elderdrivers.riru.edxp.BuildConfig; +import com.elderdrivers.riru.edxp.config.InstallerChooser; +import com.elderdrivers.riru.edxp.yahfa.core.HookMethodResolver; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; +import com.elderdrivers.riru.edxp.yahfa.proxy.BlackWhiteListProxy; +import com.elderdrivers.riru.edxp.yahfa.proxy.NormalProxy; +import com.elderdrivers.riru.edxp.util.Utils; +import com.lody.whale.WhaleRuntime; + +import java.lang.reflect.Method; +import java.util.Arrays; + +@SuppressLint("DefaultLocale") +public class Main implements KeepAll { + + public static String appDataDir = ""; + public static String niceName = ""; + public static String appProcessName = ""; + private static String forkAndSpecializePramsStr = ""; + private static String forkSystemServerPramsStr = ""; + + static { + init(Build.VERSION.SDK_INT); + HookMethodResolver.init(); + Router.injectConfig(); + InstallerChooser.setInstallerPackageName(getInstallerPkgName()); + try { + WhaleRuntime.getMethodSlot(null); + } catch (Throwable throwable) {} + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // entry points + /////////////////////////////////////////////////////////////////////////////////////////////// + + public static void forkAndSpecializePre(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, + String niceName, int[] fdsToClose, int[] fdsToIgnore, + boolean startChildZygote, String instructionSet, + String appDataDir) { + if (BuildConfig.DEBUG) { + forkAndSpecializePramsStr = String.format( + "Zygote#forkAndSpecialize(%d, %d, %s, %d, %s, %d, %s, %s, %s, %s, %s, %s, %s)", + uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits), + mountExternal, seInfo, niceName, Arrays.toString(fdsToClose), + Arrays.toString(fdsToIgnore), startChildZygote, instructionSet, appDataDir); + } + if (isBlackWhiteListEnabled()) { + BlackWhiteListProxy.forkAndSpecializePre(uid, gid, gids, debugFlags, rlimits, + mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, + instructionSet, appDataDir); + } else { + NormalProxy.forkAndSpecializePre(uid, gid, gids, debugFlags, rlimits, mountExternal, + seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, instructionSet, + appDataDir); + } + } + + public static void forkAndSpecializePost(int pid, String appDataDir, String niceName) { + if (pid == 0) { + Utils.logD(forkAndSpecializePramsStr + " = " + Process.myPid()); + if (isBlackWhiteListEnabled()) { + BlackWhiteListProxy.forkAndSpecializePost(pid, appDataDir, niceName); + } else { + NormalProxy.forkAndSpecializePost(pid, appDataDir, niceName); + } + } else { + // in zygote process, res is child zygote pid + // don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66 + } + } + + public static void forkSystemServerPre(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, + long permittedCapabilities, long effectiveCapabilities) { + if (BuildConfig.DEBUG) { + forkSystemServerPramsStr = String.format("Zygote#forkSystemServer(%d, %d, %s, %d, %s, %d, %d)", + uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits), + permittedCapabilities, effectiveCapabilities); + } + if (isBlackWhiteListEnabled()) { + BlackWhiteListProxy.forkSystemServerPre(uid, gid, gids, debugFlags, rlimits, + permittedCapabilities, effectiveCapabilities); + } else { + NormalProxy.forkSystemServerPre(uid, gid, gids, debugFlags, rlimits, + permittedCapabilities, effectiveCapabilities); + } + } + + public static void forkSystemServerPost(int pid) { + if (pid == 0) { + Utils.logD(forkSystemServerPramsStr + " = " + Process.myPid()); + if (isBlackWhiteListEnabled()) { + BlackWhiteListProxy.forkSystemServerPost(pid); + } else { + NormalProxy.forkSystemServerPost(pid); + } + } else { + // in zygote process, res is child zygote pid + // don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66 + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // native methods + /////////////////////////////////////////////////////////////////////////////////////////////// + + public static native boolean backupAndHookNative(Object target, Method hook, Method backup); + + public static native void setMethodNonCompilable(Object member); + + public static native void ensureMethodCached(Method hook, Method backup); + + // JNI.ToReflectedMethod() could return either Method or Constructor + public static native Object findMethodNative(Class targetClass, String methodName, String methodSig); + + private static native void init(int SDK_version); + + public static native String getInstallerPkgName(); + + public static native boolean isBlackWhiteListEnabled(); + + public static native boolean isDynamicModulesEnabled(); + + public static native boolean isAppNeedHook(String appDataDir); + + // prevent from fatal error caused by holding not whitelisted file descriptors when forking zygote + // https://github.com/rovo89/Xposed/commit/b3ba245ad04cd485699fb1d2ebde7117e58214ff + public static native void closeFilesBeforeForkNative(); + + public static native void reopenFilesAfterForkNative(); + + public static native void deoptMethodNative(Object object); + + public static native long suspendAllThreads(); + + public static native void resumeAllThreads(long obj); + + public static native int waitForGcToComplete(long thread); +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleEdxpConfig.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleEdxpConfig.java new file mode 100644 index 00000000..60f364bd --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleEdxpConfig.java @@ -0,0 +1,23 @@ +package com.elderdrivers.riru.edxp.yahfa.config; + +import com.elderdrivers.riru.edxp.config.EdXpConfig; +import com.elderdrivers.riru.edxp.config.InstallerChooser; +import com.elderdrivers.riru.edxp.Main; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.XposedBlackListHooker; + +public class WhaleEdxpConfig implements EdXpConfig { + @Override + public String getInstallerBaseDir() { + return InstallerChooser.INSTALLER_DATA_BASE_DIR; + } + + @Override + public String getBlackListModulePackageName() { + return XposedBlackListHooker.BLACK_LIST_PACKAGE_NAME; + } + + @Override + public boolean isDynamicModulesMode() { + return Main.isDynamicModulesEnabled(); + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleHookProvider.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleHookProvider.java new file mode 100644 index 00000000..cb94160b --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/WhaleHookProvider.java @@ -0,0 +1,37 @@ +package com.elderdrivers.riru.edxp.yahfa.config; + +import com.elderdrivers.riru.edxp.hook.HookProvider; +import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter; +import com.lody.whale.WhaleRuntime; + +import java.lang.reflect.Member; + +import de.robv.android.xposed.XposedBridge; + +public class WhaleHookProvider implements HookProvider { + + @Override + public void hookMethod(Member method, XposedBridge.AdditionalHookInfo additionalInfo) { + WhaleRuntime.hookMethodNative(method.getDeclaringClass(), method, additionalInfo); + } + + @Override + public Object invokeOriginalMethod(Member method, long methodId, Object thisObject, Object[] args) throws Throwable { + return WhaleRuntime.invokeOriginalMethodNative(methodId, thisObject, args); + } + + @Override + public Member findMethodNative(Member hookMethod) { + return hookMethod; + } + + @Override + public void deoptMethods(String packageName, ClassLoader classLoader) { + PrebuiltMethodsDeopter.deoptMethods(packageName, classLoader); + } + + @Override + public long getMethodId(Member member) { + return WhaleRuntime.getMethodSlot(member); + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMain.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMain.java new file mode 100644 index 00000000..13da190a --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMain.java @@ -0,0 +1,186 @@ +package com.elderdrivers.riru.edxp.yahfa.core; + +import com.elderdrivers.riru.edxp.Main; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.OnePlusWorkAroundHooker; +import com.elderdrivers.riru.edxp.util.Utils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import de.robv.android.xposed.XposedHelpers; + +import static com.elderdrivers.riru.edxp.Main.backupAndHookNative; +import static com.elderdrivers.riru.edxp.Main.findMethodNative; + +public class HookMain { + + private static Set hookItemWhiteList = Collections.singleton(OnePlusWorkAroundHooker.class.getName()); + + public static void doHookDefault(ClassLoader patchClassLoader, ClassLoader originClassLoader, String hookInfoClassName) { + try { + Class hookInfoClass = Class.forName(hookInfoClassName, true, patchClassLoader); + String[] hookItemNames = (String[]) hookInfoClass.getField("hookItemNames").get(null); + for (String hookItemName : hookItemNames) { + doHookItemDefault(patchClassLoader, hookItemName, originClassLoader); + } + } catch (Throwable e) { + Utils.logE("error when hooking all in: " + hookInfoClassName, e); + } + } + + private static void doHookItemDefault(ClassLoader patchClassLoader, String hookItemName, ClassLoader originClassLoader) { + try { + Utils.logD("Start hooking with item " + hookItemName); + Class hookItem = Class.forName(hookItemName, true, patchClassLoader); + + String className = (String) hookItem.getField("className").get(null); + String methodName = (String) hookItem.getField("methodName").get(null); + String methodSig = (String) hookItem.getField("methodSig").get(null); + + if (className == null || className.equals("")) { + Utils.logW("No target class. Skipping..."); + return; + } + Class clazz = null; + try { + clazz = Class.forName(className, true, originClassLoader); + } catch (ClassNotFoundException cnfe) { + Utils.logE(className + " not found in " + originClassLoader); + return; + } + if (Modifier.isAbstract(clazz.getModifiers())) { + Utils.logW("Hook may fail for abstract class: " + className); + } + + Method hook = null; + Method backup = null; + for (Method method : hookItem.getDeclaredMethods()) { + if (method.getName().equals("hook") && Modifier.isStatic(method.getModifiers())) { + hook = method; + } else if (method.getName().equals("backup") && Modifier.isStatic(method.getModifiers())) { + backup = method; + } + } + if (hook == null) { + Utils.logE("Cannot find hook for " + methodName); + return; + } + findAndBackupAndHook(clazz, methodName, methodSig, hook, backup); + } catch (Throwable e) { + if (!hookItemWhiteList.contains(hookItemName)) { + Utils.logE("error when hooking " + hookItemName, e); + } + } + } + + public static void findAndHook(Class targetClass, String methodName, String methodSig, Method hook) { + hook(findMethod(targetClass, methodName, methodSig), hook); + } + + public static void findAndBackupAndHook(Class targetClass, String methodName, String methodSig, + Method hook, Method backup) { + backupAndHook(findMethod(targetClass, methodName, methodSig), hook, backup); + } + + public static void hook(Object target, Method hook) { + backupAndHook(target, hook, null); + } + + public static void backupAndHook(Object target, Method hook, Method backup) { + Utils.logD(String.format("target=%s, hook=%s, backup=%s", target, hook, backup)); + if (target == null) { + throw new IllegalArgumentException("null target method"); + } + if (hook == null) { + throw new IllegalArgumentException("null hook method"); + } + + if (!Modifier.isStatic(hook.getModifiers())) { + throw new IllegalArgumentException("Hook must be a static method: " + hook); + } + checkCompatibleMethods(target, hook, "Original", "Hook"); + if (backup != null) { + if (!Modifier.isStatic(backup.getModifiers())) { + throw new IllegalArgumentException("Backup must be a static method: " + backup); + } + // backup is just a placeholder and the constraint could be less strict + checkCompatibleMethods(target, backup, "Original", "Backup"); + } + if (backup != null) { + HookMethodResolver.resolveMethod(hook, backup); + } + // make sure GC completed before hook + Thread currentThread = Thread.currentThread(); + int lastGcType = Main.waitForGcToComplete( + XposedHelpers.getLongField(currentThread, "nativePeer")); + if (lastGcType < 0) { + Utils.logW("waitForGcToComplete failed, using fallback"); + Runtime.getRuntime().gc(); + } + if (!backupAndHookNative(target, hook, backup)) { + throw new RuntimeException("Failed to hook " + target + " with " + hook); + } + } + + public static Object findMethod(Class cls, String methodName, String methodSig) { + if (cls == null) { + throw new IllegalArgumentException("null class"); + } + if (methodName == null) { + throw new IllegalArgumentException("null method name"); + } + if (methodSig == null) { + throw new IllegalArgumentException("null method signature"); + } + return findMethodNative(cls, methodName, methodSig); + } + + private static void checkCompatibleMethods(Object original, Method replacement, String originalName, String replacementName) { + ArrayList> originalParams; + if (original instanceof Method) { + originalParams = new ArrayList<>(Arrays.asList(((Method) original).getParameterTypes())); + } else if (original instanceof Constructor) { + originalParams = new ArrayList<>(Arrays.asList(((Constructor) original).getParameterTypes())); + } else { + throw new IllegalArgumentException("Type of target method is wrong"); + } + + ArrayList> replacementParams = new ArrayList<>(Arrays.asList(replacement.getParameterTypes())); + + if (original instanceof Method + && !Modifier.isStatic(((Method) original).getModifiers())) { + originalParams.add(0, ((Method) original).getDeclaringClass()); + } else if (original instanceof Constructor) { + originalParams.add(0, ((Constructor) original).getDeclaringClass()); + } + + + if (!Modifier.isStatic(replacement.getModifiers())) { + replacementParams.add(0, replacement.getDeclaringClass()); + } + + if (original instanceof Method + && !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) { + throw new IllegalArgumentException("Incompatible return types. " + originalName + ": " + ((Method) original).getReturnType() + ", " + replacementName + ": " + replacement.getReturnType()); + } else if (original instanceof Constructor) { + if (replacement.getReturnType().equals(Void.class)) { + throw new IllegalArgumentException("Incompatible return types. " + "" + ": " + "V" + ", " + replacementName + ": " + replacement.getReturnType()); + } + } + + if (originalParams.size() != replacementParams.size()) { + throw new IllegalArgumentException("Number of arguments don't match. " + originalName + ": " + originalParams.size() + ", " + replacementName + ": " + replacementParams.size()); + } + + for (int i = 0; i < originalParams.size(); i++) { + if (!replacementParams.get(i).isAssignableFrom(originalParams.get(i))) { + throw new IllegalArgumentException("Incompatible argument #" + i + ": " + originalName + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i)); + } + } + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMethodResolver.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMethodResolver.java new file mode 100644 index 00000000..d8f616b5 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/HookMethodResolver.java @@ -0,0 +1,155 @@ +package com.elderdrivers.riru.edxp.yahfa.core; + +import android.os.Build; + +import com.elderdrivers.riru.edxp.Main; +import com.elderdrivers.riru.edxp.util.Utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * create by Swift Gan on 14/01/2019 + * To ensure method in resolved cache + */ + +public class HookMethodResolver { + + public static Class artMethodClass; + + public static Field resolvedMethodsField; + public static Field dexCacheField; + public static Field dexMethodIndexField; + public static Field artMethodField; + + public static boolean canResolvedInJava = false; + public static boolean isArtMethod = false; + + public static long resolvedMethodsAddress = 0; + public static int dexMethodIndex = 0; + + public static Method testMethod; + public static Object testArtMethod; + + public static void init() { + checkSupport(); + } + + private static void checkSupport() { + try { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + isArtMethod = false; + canResolvedInJava = false; + return; + } + + testMethod = HookMethodResolver.class.getDeclaredMethod("init"); + artMethodField = getField(Method.class, "artMethod"); + + testArtMethod = artMethodField.get(testMethod); + + if (hasJavaArtMethod() && testArtMethod.getClass() == artMethodClass) { + checkSupportForArtMethod(); + isArtMethod = true; + } else if (testArtMethod instanceof Long) { + checkSupportForArtMethodId(); + isArtMethod = false; + } else { + canResolvedInJava = false; + } + + } catch (Throwable throwable) { + Utils.logE("error when checkSupport", throwable); + } + } + + // may 5.0 + private static void checkSupportForArtMethod() throws Exception { + dexMethodIndexField = getField(artMethodClass, "dexMethodIndex"); + dexCacheField = getField(Class.class, "dexCache"); + Object dexCache = dexCacheField.get(testMethod.getDeclaringClass()); + resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods"); + if (resolvedMethodsField.get(dexCache) instanceof Object[]) { + canResolvedInJava = true; + } + } + + // may 6.0 + private static void checkSupportForArtMethodId() throws Exception { + dexMethodIndexField = getField(Method.class, "dexMethodIndex"); + dexMethodIndex = (int) dexMethodIndexField.get(testMethod); + dexCacheField = getField(Class.class, "dexCache"); + Object dexCache = dexCacheField.get(testMethod.getDeclaringClass()); + resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods"); + Object resolvedMethods = resolvedMethodsField.get(dexCache); + if (resolvedMethods instanceof Long) { + canResolvedInJava = false; + resolvedMethodsAddress = (long) resolvedMethods; + } else if (resolvedMethods instanceof long[]) { + canResolvedInJava = true; + } + } + + public static void resolveMethod(Method hook, Method backup) { + if (canResolvedInJava && artMethodField != null) { + // in java + try { + resolveInJava(hook, backup); + } catch (Exception e) { + // in native + resolveInNative(hook, backup); + } + } else { + // in native + resolveInNative(hook, backup); + } + } + + private static void resolveInJava(Method hook, Method backup) throws Exception { + Object dexCache = dexCacheField.get(hook.getDeclaringClass()); + if (isArtMethod) { + Object artMethod = artMethodField.get(backup); + int dexMethodIndex = (int) dexMethodIndexField.get(artMethod); + Object resolvedMethods = resolvedMethodsField.get(dexCache); + ((Object[])resolvedMethods)[dexMethodIndex] = artMethod; + } else { + int dexMethodIndex = (int) dexMethodIndexField.get(backup); + Object resolvedMethods = resolvedMethodsField.get(dexCache); + long artMethod = (long) artMethodField.get(backup); + ((long[])resolvedMethods)[dexMethodIndex] = artMethod; + } + } + + private static void resolveInNative(Method hook, Method backup) { + Main.ensureMethodCached(hook, backup); + } + + public static Field getField(Class topClass, String fieldName) throws NoSuchFieldException { + while (topClass != null && topClass != Object.class) { + try { + Field field = topClass.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (Exception e) { + } + topClass = topClass.getSuperclass(); + } + throw new NoSuchFieldException(fieldName); + } + + public static boolean hasJavaArtMethod() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return false; + } + if (artMethodClass != null) + return true; + try { + artMethodClass = Class.forName("java.lang.reflect.ArtMethod"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} \ No newline at end of file diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexLog.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexLog.java new file mode 100644 index 00000000..f7729737 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DexLog.java @@ -0,0 +1,37 @@ +package com.elderdrivers.riru.edxp.yahfa.dexmaker; + +import android.util.Log; + +import com.elderdrivers.riru.edxp.BuildConfig; + +public class DexLog { + + public static final String TAG = "EdXposed-dexmaker"; + + public static int v(String s) { + return Log.v(TAG, s); + } + + public static int i(String s) { + return Log.i(TAG, s); + } + + public static int d(String s) { + if (BuildConfig.DEBUG) { + return Log.d(TAG, s); + } + return 0; + } + + public static int w(String s) { + return Log.w(TAG, s); + } + + public static int e(String s) { + return Log.e(TAG, s); + } + + public static int e(String s, Throwable t) { + return Log.e(TAG, s, t); + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java new file mode 100644 index 00000000..27ea86e0 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/MethodInfo.java @@ -0,0 +1,94 @@ +package com.elderdrivers.riru.edxp.yahfa.dexmaker; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +public class MethodInfo { + + public String className; + public String classDesc; + public String methodName; + public String methodSig; + public Method method; + public Constructor constructor; + public boolean isConstructor; + public ClassLoader classLoader; + + public MethodInfo(Member member) { + if (member instanceof Method) { + method = (Method) member; + isConstructor = false; + classLoader = member.getDeclaringClass().getClassLoader(); + generateMethodInfo(); + } else if (member instanceof Constructor) { + constructor = (Constructor) member; + isConstructor = true; + classLoader = member.getDeclaringClass().getClassLoader(); + generateConstructorInfo(); + } else { + throw new IllegalArgumentException("member should be Method or Constructor"); + } + } + + private void generateConstructorInfo() { + methodName = ""; + className = constructor.getDeclaringClass().getName(); + generateCommonInfo(constructor.getParameterTypes(), void.class); + } + + private void generateMethodInfo() { + methodName = method.getName(); + className = method.getDeclaringClass().getName(); + generateCommonInfo(method.getParameterTypes(), method.getReturnType()); + } + + private void generateCommonInfo(Class[] parameterTypes, Class returnType) { + classDesc = "L" + className.replace(".", "/") + ";"; + StringBuilder builder = new StringBuilder(); + builder.append("("); + for (Class parameterType : parameterTypes) { + builder.append(getDescStr(parameterType)); + } + builder.append(")"); + builder.append(getDescStr(returnType)); + methodSig = builder.toString(); + } + + public Class getClassForSure() { + try { + // TODO does initialize make sense? + return Class.forName(className, true, classLoader); + } catch (Throwable throwable) { + DexLog.e("error when getClassForSure", throwable); + return null; + } + } + + public static String getDescStr(Class clazz) { + if (clazz.equals(boolean.class)) { + return "Z"; + } else if (clazz.equals(byte.class)) { + return "B"; + } else if (clazz.equals(char.class)) { + return "C"; + } else if (clazz.equals(double.class)) { + return "D"; + } else if (clazz.equals(float.class)) { + return "F"; + } else if (clazz.equals(int.class)) { + return "I"; + } else if (clazz.equals(long.class)) { + return "J"; + } else if (clazz.equals(short.class)) { + return "S"; + } else if (clazz.equals(void.class)) { + return "V"; + } else { + String prefix = clazz.isArray() ? "" : "L"; + String suffix = clazz.isArray() ? "" : ";"; + return prefix + clazz.getName().replace(".", "/") + suffix; + } + } + +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java new file mode 100644 index 00000000..0a3347b3 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java @@ -0,0 +1,122 @@ +package com.elderdrivers.riru.edxp.yahfa.entry; + +import android.app.AndroidAppHelper; +import android.text.TextUtils; + +import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal; +import com.elderdrivers.riru.edxp.util.Utils; +import com.elderdrivers.riru.edxp.yahfa.config.WhaleHookProvider; +import com.elderdrivers.riru.edxp.yahfa.config.WhaleEdxpConfig; +import com.elderdrivers.riru.edxp.yahfa.core.HookMain; +import com.elderdrivers.riru.edxp.yahfa.entry.bootstrap.AppBootstrapHookInfo; +import com.elderdrivers.riru.edxp.yahfa.entry.bootstrap.SysBootstrapHookInfo; +import com.elderdrivers.riru.edxp.yahfa.entry.bootstrap.SysInnerHookInfo; +import com.elderdrivers.riru.edxp.yahfa.entry.bootstrap.WorkAroundHookInfo; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.SystemMainHooker; + +import java.util.concurrent.atomic.AtomicBoolean; + +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedInit; + +public class Router { + + public volatile static boolean forkCompleted = false; + + private static volatile AtomicBoolean bootstrapHooked = new AtomicBoolean(false); + + + public static void prepare(boolean isSystem) { + // this flag is needed when loadModules + XposedInit.startsSystemServer = isSystem; +// InstallerChooser.setup(); + } + + public static void checkHookState(String appDataDir) { + // determine whether allow xposed or not +// XposedBridge.disableHooks = ConfigManager.shouldHook(parsePackageName(appDataDir)); + } + + private static String parsePackageName(String appDataDir) { + if (TextUtils.isEmpty(appDataDir)) { + return ""; + } + int lastIndex = appDataDir.lastIndexOf("/"); + if (lastIndex < 1) { + return ""; + } + return appDataDir.substring(lastIndex + 1); + } + + public static void installBootstrapHooks(boolean isSystem) { + // Initialize the Xposed framework + try { + if (!bootstrapHooked.compareAndSet(false, true)) { + return; + } + Router.startBootstrapHook(isSystem); + XposedInit.initForZygote(isSystem); + } catch (Throwable t) { + Utils.logE("error during Xposed initialization", t); + XposedBridge.disableHooks = true; + } + } + + public static void loadModulesSafely(boolean isInZygote) { + try { + // FIXME some coredomain app can't reading modules.list + XposedInit.loadModules(isInZygote); + } catch (Exception exception) { + Utils.logE("error loading module list", exception); + } + } + + public static void startBootstrapHook(boolean isSystem) { + Utils.logD("startBootstrapHook starts: isSystem = " + isSystem); + ClassLoader classLoader = XposedBridge.BOOTCLASSLOADER; + if (isSystem) { + HookMain.doHookDefault( + Router.class.getClassLoader(), + classLoader, + SysBootstrapHookInfo.class.getName()); + } else { + HookMain.doHookDefault( + Router.class.getClassLoader(), + classLoader, + AppBootstrapHookInfo.class.getName()); + } + } + + public static void startSystemServerHook() { + HookMain.doHookDefault( + Router.class.getClassLoader(), + SystemMainHooker.systemServerCL, + SysInnerHookInfo.class.getName()); + } + + public static void startWorkAroundHook() { + HookMain.doHookDefault( + Router.class.getClassLoader(), + XposedBridge.BOOTCLASSLOADER, + WorkAroundHookInfo.class.getName()); + } + + public static void onEnterChildProcess() { + forkCompleted = true; + } + + public static void logD(String prefix) { + Utils.logD(String.format("%s: pkg=%s, prc=%s", prefix, AndroidAppHelper.currentPackageName(), + AndroidAppHelper.currentProcessName())); + } + + public static void logE(String prefix, Throwable throwable) { + Utils.logE(String.format("%s: pkg=%s, prc=%s", prefix, AndroidAppHelper.currentPackageName(), + AndroidAppHelper.currentProcessName()), throwable); + } + + public static void injectConfig() { + EdXpConfigGlobal.sConfig = new WhaleEdxpConfig(); + EdXpConfigGlobal.sHookProvider = new WhaleHookProvider(); + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/AppBootstrapHookInfo.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/AppBootstrapHookInfo.java new file mode 100644 index 00000000..24aa7c29 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/AppBootstrapHookInfo.java @@ -0,0 +1,14 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.bootstrap; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.HandleBindAppHooker; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.LoadedApkConstructorHooker; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.OnePlusWorkAroundHooker; + +public class AppBootstrapHookInfo implements KeepMembers { + public static String[] hookItemNames = { + HandleBindAppHooker.class.getName(), + LoadedApkConstructorHooker.class.getName(), + OnePlusWorkAroundHooker.class.getName() + }; +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysBootstrapHookInfo.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysBootstrapHookInfo.java new file mode 100644 index 00000000..2cb7e02e --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysBootstrapHookInfo.java @@ -0,0 +1,16 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.bootstrap; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.HandleBindAppHooker; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.LoadedApkConstructorHooker; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.OnePlusWorkAroundHooker; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.SystemMainHooker; + +public class SysBootstrapHookInfo implements KeepMembers { + public static String[] hookItemNames = { + HandleBindAppHooker.class.getName(), + SystemMainHooker.class.getName(), + LoadedApkConstructorHooker.class.getName(), + OnePlusWorkAroundHooker.class.getName() + }; +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysInnerHookInfo.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysInnerHookInfo.java new file mode 100644 index 00000000..a185c0df --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/SysInnerHookInfo.java @@ -0,0 +1,10 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.bootstrap; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.StartBootstrapServicesHooker; + +public class SysInnerHookInfo implements KeepMembers { + public static String[] hookItemNames = { + StartBootstrapServicesHooker.class.getName() + }; +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/WorkAroundHookInfo.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/WorkAroundHookInfo.java new file mode 100644 index 00000000..f0c5f065 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/bootstrap/WorkAroundHookInfo.java @@ -0,0 +1,10 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.bootstrap; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.yahfa.entry.hooker.OnePlusWorkAroundHooker; + +public class WorkAroundHookInfo implements KeepMembers { + public static String[] hookItemNames = { + OnePlusWorkAroundHooker.class.getName() + }; +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/HandleBindAppHooker.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/HandleBindAppHooker.java new file mode 100644 index 00000000..12d7ef2c --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/HandleBindAppHooker.java @@ -0,0 +1,89 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.hooker; + +import android.app.ActivityThread; +import android.app.LoadedApk; +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.res.CompatibilityInfo; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.util.Utils; +import com.elderdrivers.riru.edxp.Main; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; + +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.XposedInit; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static com.elderdrivers.riru.edxp.config.InstallerChooser.INSTALLER_PACKAGE_NAME; +import static com.elderdrivers.riru.edxp.util.ClassLoaderUtils.replaceParentClassLoader; +import static com.elderdrivers.riru.edxp.yahfa.entry.hooker.XposedBlackListHooker.BLACK_LIST_PACKAGE_NAME; + +// normal process initialization (for new Activity, Service, BroadcastReceiver etc.) +public class HandleBindAppHooker implements KeepMembers { + + public static String className = "android.app.ActivityThread"; + public static String methodName = "handleBindApplication"; + public static String methodSig = "(Landroid/app/ActivityThread$AppBindData;)V"; + + public static void hook(Object thiz, Object bindData) { + if (XposedBlackListHooker.shouldDisableHooks("")) { + backup(thiz, bindData); + return; + } + try { + Router.logD("ActivityThread#handleBindApplication() starts"); + ActivityThread activityThread = (ActivityThread) thiz; + ApplicationInfo appInfo = (ApplicationInfo) XposedHelpers.getObjectField(bindData, "appInfo"); + // save app process name here for later use + Main.appProcessName = (String) XposedHelpers.getObjectField(bindData, "processName"); + String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName; + Utils.logD("processName=" + Main.appProcessName + + ", packageName=" + reportedPackageName + ", appDataDir=" + Main.appDataDir); + + if (XposedBlackListHooker.shouldDisableHooks(reportedPackageName)) { + return; + } + + ComponentName instrumentationName = (ComponentName) XposedHelpers.getObjectField(bindData, "instrumentationName"); + if (instrumentationName != null) { + Router.logD("Instrumentation detected, disabling framework for"); + XposedBridge.disableHooks = true; + return; + } + CompatibilityInfo compatInfo = (CompatibilityInfo) XposedHelpers.getObjectField(bindData, "compatInfo"); + if (appInfo.sourceDir == null) { + return; + } + + XposedHelpers.setObjectField(activityThread, "mBoundApplication", bindData); + XposedInit.loadedPackagesInProcess.add(reportedPackageName); + LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo); + + replaceParentClassLoader(loadedApk.getClassLoader()); + + XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks); + lpparam.packageName = reportedPackageName; + lpparam.processName = (String) XposedHelpers.getObjectField(bindData, "processName"); + lpparam.classLoader = loadedApk.getClassLoader(); + lpparam.appInfo = appInfo; + lpparam.isFirstApplication = true; + XC_LoadPackage.callAll(lpparam); + + if (reportedPackageName.equals(INSTALLER_PACKAGE_NAME)) { + XposedInstallerHooker.hookXposedInstaller(lpparam.classLoader); + } + if (reportedPackageName.equals(BLACK_LIST_PACKAGE_NAME)) { + XposedBlackListHooker.hook(lpparam.classLoader); + } + } catch (Throwable t) { + Router.logE("error when hooking bindApp", t); + } finally { + backup(thiz, bindData); + } + } + + public static void backup(Object thiz, Object bindData) { + } +} \ No newline at end of file diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/LoadedApkConstructorHooker.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/LoadedApkConstructorHooker.java new file mode 100644 index 00000000..ce2f7605 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/LoadedApkConstructorHooker.java @@ -0,0 +1,98 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.hooker; + +import android.app.ActivityThread; +import android.app.AndroidAppHelper; +import android.app.LoadedApk; +import android.content.pm.ApplicationInfo; +import android.content.res.CompatibilityInfo; +import android.util.Log; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; + +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.XposedInit; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static com.elderdrivers.riru.edxp.util.ClassLoaderUtils.replaceParentClassLoader; + +// when a package is loaded for an existing process, trigger the callbacks as well +// ed: remove resources related hooking +public class LoadedApkConstructorHooker implements KeepMembers { + public static String className = "android.app.LoadedApk"; + public static String methodName = ""; + public static String methodSig = "(Landroid/app/ActivityThread;" + + "Landroid/content/pm/ApplicationInfo;" + + "Landroid/content/res/CompatibilityInfo;" + + "Ljava/lang/ClassLoader;ZZZ)V"; + + public static void hook(Object thiz, ActivityThread activityThread, + ApplicationInfo aInfo, CompatibilityInfo compatInfo, + ClassLoader baseLoader, boolean securityViolation, + boolean includeCode, boolean registerPackage) { + + if (XposedBlackListHooker.shouldDisableHooks("")) { + backup(thiz, activityThread, aInfo, compatInfo, baseLoader, securityViolation, + includeCode, registerPackage); + return; + } + + Router.logD("LoadedApk# starts"); + backup(thiz, activityThread, aInfo, compatInfo, baseLoader, securityViolation, + includeCode, registerPackage); + + try { + LoadedApk loadedApk = (LoadedApk) thiz; + String packageName = loadedApk.getPackageName(); + Object mAppDir = XposedHelpers.getObjectField(thiz, "mAppDir"); + Router.logD("LoadedApk# ends: " + mAppDir); + + if (XposedBlackListHooker.shouldDisableHooks(packageName)) { + return; + } + + if (packageName.equals("android")) { + Router.logD("LoadedApk# is android, skip: " + mAppDir); + return; + } + + // mIncludeCode checking should go ahead of loadedPackagesInProcess added checking + if (!XposedHelpers.getBooleanField(loadedApk, "mIncludeCode")) { + Router.logD("LoadedApk# mIncludeCode == false: " + mAppDir); + return; + } + + if (!XposedInit.loadedPackagesInProcess.add(packageName)) { + Router.logD("LoadedApk# has been loaded before, skip: " + mAppDir); + return; + } + + // OnePlus magic... + if (Log.getStackTraceString(new Throwable()). + contains("android.app.ActivityThread$ApplicationThread.schedulePreload")) { + Router.logD("LoadedApk# maybe oneplus's custom opt, skip"); + return; + } + + replaceParentClassLoader(loadedApk.getClassLoader()); + + XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks); + lpparam.packageName = packageName; + lpparam.processName = AndroidAppHelper.currentProcessName(); + lpparam.classLoader = loadedApk.getClassLoader(); + lpparam.appInfo = loadedApk.getApplicationInfo(); + lpparam.isFirstApplication = false; + XC_LoadPackage.callAll(lpparam); + } catch (Throwable t) { + Router.logE("error when hooking LoadedApk.", t); + } + } + + public static void backup(Object thiz, ActivityThread activityThread, + ApplicationInfo aInfo, CompatibilityInfo compatInfo, + ClassLoader baseLoader, boolean securityViolation, + boolean includeCode, boolean registerPackage) { + + } +} \ No newline at end of file diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/OnePlusWorkAroundHooker.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/OnePlusWorkAroundHooker.java new file mode 100644 index 00000000..f3261f9d --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/OnePlusWorkAroundHooker.java @@ -0,0 +1,41 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.hooker; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.Main; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; + +import de.robv.android.xposed.XposedBridge; + +/** + * On OnePlus stock roms (Android Pie), {@link dalvik.system.BaseDexClassLoader#findClass(String)} + * will open /dev/binder to communicate with PackageManagerService to check whether + * current package name inCompatConfigList, which is an OnePlus OEM feature enabled only when + * system prop "persist.sys.oem.region" set to "CN".(detail of related source code: + * https://gist.github.com/solohsu/ecc07141759958fc096ba0781fac0a5f) + * If we invoke intZygoteCallbacks in + * {@link Main#forkAndSpecializePre}, where in zygote process, + * we would get a chance to invoke findclass, leaving fd of /dev/binder open in zygote process, + * which is not allowed because /dev/binder is not in predefined whitelist here: + * http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/jni/fd_utils.cpp#35 + * So we just hook BaseDexClassLoader#inCompatConfigList to return false to prevent + * open of /dev/binder and we haven't found side effects yet. + * Other roms might share the same problems but not reported too. + */ +public class OnePlusWorkAroundHooker implements KeepMembers { + + public static String className = "dalvik.system.BaseDexClassLoader"; + public static String methodName = "inCompatConfigList"; + public static String methodSig = "(ILjava/lang/String;)Z"; + + public static boolean hook(int type, String packageName) { + if (XposedBridge.disableHooks || Router.forkCompleted) { + return backup(type, packageName); + } + Router.logD("BaseDexClassLoader#inCompatConfigList() starts"); + return false; + } + + public static boolean backup(int type, String packageName) { + return false; + } +} \ No newline at end of file diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/StartBootstrapServicesHooker.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/StartBootstrapServicesHooker.java new file mode 100644 index 00000000..7bc1c542 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/StartBootstrapServicesHooker.java @@ -0,0 +1,66 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.hooker; + +import android.os.Build; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; + +import de.robv.android.xposed.XC_MethodReplacement; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.XposedInit; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static com.elderdrivers.riru.edxp.util.ClassLoaderUtils.replaceParentClassLoader; +import static com.elderdrivers.riru.edxp.util.Utils.logD; +import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; + +public class StartBootstrapServicesHooker implements KeepMembers { + public static String className = "com.android.server.SystemServer"; + public static String methodName = "startBootstrapServices"; + public static String methodSig = "()V"; + + public static void hook(Object systemServer) { + + if (XposedBridge.disableHooks) { + backup(systemServer); + return; + } + + logD("SystemServer#startBootstrapServices() starts"); + + try { + XposedInit.loadedPackagesInProcess.add("android"); + + replaceParentClassLoader(SystemMainHooker.systemServerCL); + + XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks); + lpparam.packageName = "android"; + lpparam.processName = "android"; // it's actually system_server, but other functions return this as well + lpparam.classLoader = SystemMainHooker.systemServerCL; + lpparam.appInfo = null; + lpparam.isFirstApplication = true; + XC_LoadPackage.callAll(lpparam); + + // Huawei + try { + findAndHookMethod("com.android.server.pm.HwPackageManagerService", SystemMainHooker.systemServerCL, "isOdexMode", XC_MethodReplacement.returnConstant(false)); + } catch (XposedHelpers.ClassNotFoundError | NoSuchMethodError ignored) { + } + + try { + String className = "com.android.server.pm." + (Build.VERSION.SDK_INT >= 23 ? "PackageDexOptimizer" : "PackageManagerService"); + findAndHookMethod(className, SystemMainHooker.systemServerCL, "dexEntryExists", String.class, XC_MethodReplacement.returnConstant(true)); + } catch (XposedHelpers.ClassNotFoundError | NoSuchMethodError ignored) { + } + } catch (Throwable t) { + Router.logE("error when hooking startBootstrapServices", t); + } finally { + backup(systemServer); + } + } + + public static void backup(Object systemServer) { + + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/SystemMainHooker.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/SystemMainHooker.java new file mode 100644 index 00000000..2478f2e6 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/SystemMainHooker.java @@ -0,0 +1,43 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.hooker; + +import android.app.ActivityThread; + +import com.elderdrivers.riru.common.KeepMembers; +import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; + +import de.robv.android.xposed.XposedBridge; + + +// system_server initialization +// ed: only support sdk >= 21 for now +public class SystemMainHooker implements KeepMembers { + + public static String className = "android.app.ActivityThread"; + public static String methodName = "systemMain"; + public static String methodSig = "()Landroid/app/ActivityThread;"; + + public static ClassLoader systemServerCL; + + public static ActivityThread hook() { + if (XposedBridge.disableHooks) { + return backup(); + } + Router.logD("ActivityThread#systemMain() starts"); + ActivityThread activityThread = backup(); + try { + // get system_server classLoader + systemServerCL = Thread.currentThread().getContextClassLoader(); + // deopt methods in SYSTEMSERVERCLASSPATH + PrebuiltMethodsDeopter.deoptSystemServerMethods(systemServerCL); + Router.startSystemServerHook(); + } catch (Throwable t) { + Router.logE("error when hooking systemMain", t); + } + return activityThread; + } + + public static ActivityThread backup() { + return null; + } +} \ No newline at end of file diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedBlackListHooker.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedBlackListHooker.java new file mode 100644 index 00000000..7261f63b --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedBlackListHooker.java @@ -0,0 +1,87 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.hooker; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Build; + +import com.elderdrivers.riru.edxp.util.Utils; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XSharedPreferences; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; + +import static com.elderdrivers.riru.edxp.config.InstallerChooser.INSTALLER_PACKAGE_NAME; +import static com.elderdrivers.riru.edxp.util.FileUtils.IS_USING_PROTECTED_STORAGE; + +public class XposedBlackListHooker { + + public static final String BLACK_LIST_PACKAGE_NAME = "com.flarejune.xposedblacklist"; + private static final String BLACK_LIST_PREF_NAME = "list"; + private static final String PREF_KEY_BLACK_LIST = "blackList"; + public static final String PREF_FILE_PATH = (IS_USING_PROTECTED_STORAGE ? "/data/user_de/0/" : "/data/data") + + BLACK_LIST_PACKAGE_NAME + "/shared_prefs/" + BLACK_LIST_PREF_NAME + ".xml"; + private static final XSharedPreferences PREFERENCES = new XSharedPreferences(new File(PREF_FILE_PATH)); + // always white list. empty string is to make sure blackList does not contain empty packageName + private static final List WHITE_LIST = Arrays.asList(INSTALLER_PACKAGE_NAME, BLACK_LIST_PACKAGE_NAME, ""); + + static { + try { + PREFERENCES.makeWorldReadable(); + } catch (Throwable throwable) { + Utils.logE("error making pref worldReadable", throwable); + } + } + + public static boolean shouldDisableHooks(String packageName) { + return XposedBridge.disableHooks || getBlackList().contains(packageName); + } + + public static Set getBlackList() { + try { + PREFERENCES.reload(); + Set result = PREFERENCES.getStringSet(PREF_KEY_BLACK_LIST, new HashSet()); + if (result != null) result.removeAll(WHITE_LIST); + return result; + } catch (Throwable throwable) { + Utils.logE("error when reading black list", throwable); + return new HashSet<>(); + } + } + + public static void hook(ClassLoader classLoader) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return; + } + try { + XposedHelpers.findAndHookMethod(ContextWrapper.class, "getSharedPreferences", String.class, int.class, new XC_MethodHook() { + @TargetApi(Build.VERSION_CODES.N) + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + try { + String prefName = (String) param.args[0]; + if (!prefName.equals(BLACK_LIST_PREF_NAME)) { + return; + } + Activity activity = (Activity) param.thisObject; + Context context = activity.createDeviceProtectedStorageContext(); + context.moveSharedPreferencesFrom(activity, prefName); + param.setResult(context.getSharedPreferences(prefName, (int) param.args[1])); + } catch (Throwable throwable) { + Utils.logE("error hooking Xposed BlackList", throwable); + } + } + }); + } catch (Throwable throwable) { + Utils.logE("error hooking Xposed BlackList", throwable); + } + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedInstallerHooker.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedInstallerHooker.java new file mode 100644 index 00000000..07931306 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/hooker/XposedInstallerHooker.java @@ -0,0 +1,64 @@ +package com.elderdrivers.riru.edxp.yahfa.entry.hooker; + +import com.elderdrivers.riru.edxp.util.Utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XC_MethodReplacement; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; + +import static com.elderdrivers.riru.edxp.config.InstallerChooser.LEGACY_INSTALLER_PACKAGE_NAME; + +public class XposedInstallerHooker { + + public static void hookXposedInstaller(ClassLoader classLoader) { + try { + final String xposedAppClass = LEGACY_INSTALLER_PACKAGE_NAME + ".XposedApp"; + final Class InstallZipUtil = XposedHelpers.findClass(LEGACY_INSTALLER_PACKAGE_NAME + + ".util.InstallZipUtil", classLoader); + XposedHelpers.findAndHookMethod(xposedAppClass, classLoader, "getActiveXposedVersion", + XC_MethodReplacement.returnConstant(XposedBridge.getXposedVersion())); + XposedHelpers.findAndHookMethod(xposedAppClass, classLoader, + "reloadXposedProp", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + Utils.logD("before reloadXposedProp..."); + final String propFieldName = "mXposedProp"; + final Object thisObject = param.thisObject; + if (XposedHelpers.getObjectField(thisObject, propFieldName) != null) { + param.setResult(null); + Utils.logD("reloadXposedProp already done, skip..."); + return; + } + File file = new File("/system/framework/edconfig.jar"); + FileInputStream is = null; + try { + is = new FileInputStream(file); + Object props = XposedHelpers.callStaticMethod(InstallZipUtil, + "parseXposedProp", is); + synchronized (thisObject) { + XposedHelpers.setObjectField(thisObject, propFieldName, props); + } + Utils.logD("reloadXposedProp done..."); + param.setResult(null); + } catch (IOException e) { + Utils.logE("Could not read " + file.getPath(), e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ignored) { + } + } + } + } + }); + } catch (Throwable t) { + Utils.logE("Could not hook Xposed Installer", t); + } + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/BlackWhiteListProxy.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/BlackWhiteListProxy.java new file mode 100644 index 00000000..ba9842d9 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/BlackWhiteListProxy.java @@ -0,0 +1,132 @@ +package com.elderdrivers.riru.edxp.yahfa.proxy; + +import android.text.TextUtils; + +import com.elderdrivers.riru.edxp.Main; +import com.elderdrivers.riru.edxp.config.ConfigManager; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; +import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter; +import com.elderdrivers.riru.edxp.util.ProcessUtils; +import com.elderdrivers.riru.edxp.util.Utils; + +import de.robv.android.xposed.XposedBridge; + +import static com.elderdrivers.riru.edxp.Main.isAppNeedHook; +import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix; + +/** + * 1. Non dynamic mode + * - system_server is whitelisted + * * for all child processes of main zygote + * What've been done in main zygote pre-forking system_server + * 1) non dynamic flag set (no need to reset) + * 2) boot image methods deopted (no need to redo) + * 3) startSystemServer flag set to true (need to reset) + * 4) workaround hooks installed (need to redo) + * 5) module list loaded and initZygote called (no need to redo) + * 6) close all fds (no need to redo because of 5)) + * * for all child processes of secondary zygote + * 1) do the same things pre-forking first child process + * - system_server is blacklisted: + * * for all child processes of both main zygote and secondary zygote + * 1) do the same things pre-forking first child process + * 2. Dynamic mode: + * to be continued + */ +public class BlackWhiteListProxy { + + public static void forkAndSpecializePre(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, + String niceName, int[] fdsToClose, int[] fdsToIgnore, + boolean startChildZygote, String instructionSet, + String appDataDir) { + final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); + if (isDynamicModulesMode) { + // should never happen + return; + } + // only enter here when isDynamicModulesMode is off + onForkPreForNonDynamicMode(false); + } + + public static void forkAndSpecializePost(int pid, String appDataDir, String niceName) { + onForkPostCommon(false, appDataDir, niceName); + } + + public static void forkSystemServerPre(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, long permittedCapabilities, + long effectiveCapabilities) { + final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); + if (isDynamicModulesMode) { + // should never happen + return; + } + // only enter here when isDynamicModulesMode is off + onForkPreForNonDynamicMode(true); + } + + public static void forkSystemServerPost(int pid) { + onForkPostCommon(true, getDataPathPrefix() + "android", "system_server"); + } + + /** + * Some details are different between main zygote and secondary zygote. + */ + private static void onForkPreForNonDynamicMode(boolean isSystemServer) { + ConfigManager.setDynamicModulesMode(false); + // set startsSystemServer flag used when loadModules + Router.prepare(isSystemServer); + // deoptBootMethods once for all child processes of zygote + PrebuiltMethodsDeopter.deoptBootMethods(); + // we never install bootstrap hooks here in black/white list mode except workaround hooks + // because installed hooks would be propagated to all child processes of zygote + Router.startWorkAroundHook(); + // loadModules once for all child processes of zygote + // TODO maybe just save initZygote callbacks and call them when whitelisted process forked? + Router.loadModulesSafely(true); + Main.closeFilesBeforeForkNative(); + } + + private static void onForkPostCommon(boolean isSystemServer, String appDataDir, String niceName) { + Main.appDataDir = appDataDir; + Main.niceName = niceName; + final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); + ConfigManager.setDynamicModulesMode(isDynamicModulesMode); + Router.onEnterChildProcess(); + if (!isDynamicModulesMode) { + Main.reopenFilesAfterForkNative(); + } + if (!checkNeedHook(appDataDir, niceName)) { + // if is blacklisted, just stop here + return; + } + Router.prepare(isSystemServer); + PrebuiltMethodsDeopter.deoptBootMethods(); + Router.installBootstrapHooks(isSystemServer); + if (isDynamicModulesMode) { + Router.loadModulesSafely(false); + } + } + + private static boolean checkNeedHook(String appDataDir, String niceName) { + boolean needHook; + if (TextUtils.isEmpty(appDataDir)) { + Utils.logE("niceName:" + niceName + ", procName:" + + ProcessUtils.getCurrentProcessName(Main.appProcessName) + ", appDataDir is null, blacklisted!"); + needHook = false; + } else { + // FIXME some process cannot read app_data_file because of MLS, e.g. bluetooth + needHook = isAppNeedHook(appDataDir); + } + if (!needHook) { + // clean up the scene + onBlackListed(); + } + return needHook; + } + + private static void onBlackListed() { + XposedBridge.clearLoadedPackages(); + XposedBridge.clearInitPackageResources(); + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/NormalProxy.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/NormalProxy.java new file mode 100644 index 00000000..9c1d3dba --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/proxy/NormalProxy.java @@ -0,0 +1,70 @@ +package com.elderdrivers.riru.edxp.yahfa.proxy; + +import com.elderdrivers.riru.edxp.Main; +import com.elderdrivers.riru.edxp.config.ConfigManager; +import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter; +import com.elderdrivers.riru.edxp.yahfa.entry.Router; + +import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix; + +public class NormalProxy { + + public static void forkAndSpecializePre(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, + String niceName, int[] fdsToClose, int[] fdsToIgnore, + boolean startChildZygote, String instructionSet, + String appDataDir) { + // mainly for secondary zygote + final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); + ConfigManager.setDynamicModulesMode(isDynamicModulesMode); + // call this to ensure the flag is set to false ASAP + Router.prepare(false); + PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote + // install bootstrap hooks for secondary zygote + Router.installBootstrapHooks(false); + // only load modules for secondary zygote + Router.loadModulesSafely(true); + Main.closeFilesBeforeForkNative(); + } + + public static void forkAndSpecializePost(int pid, String appDataDir, String niceName) { + // TODO consider processes without forkAndSpecializePost called + Main.appDataDir = appDataDir; + Main.niceName = niceName; + Router.prepare(false); + Main.reopenFilesAfterForkNative(); + Router.onEnterChildProcess(); + // load modules for each app process on its forked if dynamic modules mode is on + Router.loadModulesSafely(false); + } + + public static void forkSystemServerPre(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, + long permittedCapabilities, long effectiveCapabilities) { + final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); + ConfigManager.setDynamicModulesMode(isDynamicModulesMode); + // set startsSystemServer flag used when loadModules + Router.prepare(true); + PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for main zygote + // install bootstrap hooks for main zygote as early as possible + // in case we miss some processes not forked via forkAndSpecialize + // for instance com.android.phone + Router.installBootstrapHooks(true); + // loadModules have to be executed in zygote even isDynamicModules is false + // because if not global hooks installed in initZygote might not be + // propagated to processes not forked via forkAndSpecialize + Router.loadModulesSafely(true); + Main.closeFilesBeforeForkNative(); + } + + public static void forkSystemServerPost(int pid) { + // in system_server process + Main.appDataDir = getDataPathPrefix() + "android"; + Main.niceName = "system_server"; + Router.prepare(true); + Main.reopenFilesAfterForkNative(); + Router.onEnterChildProcess(); + // reload module list if dynamic mode is on + Router.loadModulesSafely(false); + } + +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/InlinedMethodCallers.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/InlinedMethodCallers.java new file mode 100644 index 00000000..15fffeb0 --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/InlinedMethodCallers.java @@ -0,0 +1,49 @@ +package com.elderdrivers.riru.edxp.yahfa.util; + +import java.util.HashMap; + +/** + * Providing a whitelist of methods which are the callers of the target methods we want to hook. + * Because the target methods are inlined into the callers, we deoptimize the callers to + * run in intercept mode to make target methods hookable. + *

+ * Only for methods which are included in pre-compiled framework codes. + * TODO recompile system apps and priv-apps since their original dex files are available + */ +public class InlinedMethodCallers { + + public static final String KEY_BOOT_IMAGE = "boot_image"; + public static final String KEY_SYSTEM_SERVER = "system_server"; + + /** + * Key should be {@link #KEY_BOOT_IMAGE}, {@link #KEY_SYSTEM_SERVER}, or a package name + * of system apps or priv-apps i.e. com.android.systemui + */ + private static final HashMap CALLERS = new HashMap<>(); + + /** + * format for each row: {className, methodName, methodSig} + */ + private static final String[][] BOOT_IMAGE = { + // callers of Application#attach(Context) + {"android.app.Instrumentation", "newApplication", "(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Context;)Landroid/app/Application;"} + }; + + private static final String[][] SYSTEM_SERVER = {}; + + private static final String[][] SYSTEM_UI = {}; + + static { + CALLERS.put(KEY_BOOT_IMAGE, BOOT_IMAGE); + CALLERS.put(KEY_SYSTEM_SERVER, SYSTEM_SERVER); + CALLERS.put("com.android.systemui", SYSTEM_UI); + } + + public static HashMap getAll() { + return CALLERS; + } + + public static String[][] get(String where) { + return CALLERS.get(where); + } +} diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/PrebuiltMethodsDeopter.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/PrebuiltMethodsDeopter.java new file mode 100644 index 00000000..ecd8813d --- /dev/null +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/yahfa/util/PrebuiltMethodsDeopter.java @@ -0,0 +1,41 @@ +package com.elderdrivers.riru.edxp.yahfa.util; + +import com.elderdrivers.riru.edxp.util.Utils; +import com.elderdrivers.riru.edxp.Main; + +import java.util.Arrays; + +import de.robv.android.xposed.XposedHelpers; + +import static com.elderdrivers.riru.edxp.yahfa.util.InlinedMethodCallers.KEY_BOOT_IMAGE; +import static com.elderdrivers.riru.edxp.yahfa.util.InlinedMethodCallers.KEY_SYSTEM_SERVER; + +public class PrebuiltMethodsDeopter { + + public static void deoptMethods(String where, ClassLoader cl) { + String[][] callers = InlinedMethodCallers.get(where); + if (callers == null) { + return; + } + for (String[] caller : callers) { + try { + Object method = Main.findMethodNative( + XposedHelpers.findClass(caller[0], cl), caller[1], caller[2]); + if (method != null) { + Main.deoptMethodNative(method); + } + } catch (Throwable throwable) { + Utils.logE("error when deopting method: " + Arrays.toString(caller), throwable); + } + } + } + + public static void deoptBootMethods() { + // todo check if has been done before + deoptMethods(KEY_BOOT_IMAGE, null); + } + + public static void deoptSystemServerMethods(ClassLoader sysCL) { + deoptMethods(KEY_SYSTEM_SERVER, sysCL); + } +} diff --git a/edxp-whale/src/main/java/com/lody/whale/VMHelper.java b/edxp-whale/src/main/java/com/lody/whale/VMHelper.java new file mode 100644 index 00000000..392d7fef --- /dev/null +++ b/edxp-whale/src/main/java/com/lody/whale/VMHelper.java @@ -0,0 +1,105 @@ +package com.lody.whale; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.HashMap; + +/** + * @author Lody + */ +class VMHelper { + + // Holds a mapping from Java type names to native type codes. + private static final HashMap, String> PRIMITIVE_TO_SIGNATURE; + + static { + PRIMITIVE_TO_SIGNATURE = new HashMap<>(9); + PRIMITIVE_TO_SIGNATURE.put(byte.class, "B"); + PRIMITIVE_TO_SIGNATURE.put(char.class, "C"); + PRIMITIVE_TO_SIGNATURE.put(short.class, "S"); + PRIMITIVE_TO_SIGNATURE.put(int.class, "I"); + PRIMITIVE_TO_SIGNATURE.put(long.class, "J"); + PRIMITIVE_TO_SIGNATURE.put(float.class, "F"); + PRIMITIVE_TO_SIGNATURE.put(double.class, "D"); + PRIMITIVE_TO_SIGNATURE.put(void.class, "V"); + PRIMITIVE_TO_SIGNATURE.put(boolean.class, "Z"); + } + + /** + * Returns the internal name of {@code clazz} (also known as the + * descriptor). + */ + private static String getSignature(final Class clazz) { + final String primitiveSignature = PRIMITIVE_TO_SIGNATURE.get(clazz); + if (primitiveSignature != null) { + return primitiveSignature; + } else if (clazz.isArray()) { + return "[" + getSignature(clazz.getComponentType()); + } else { + return "L" + clazz.getName().replace('.', '/') + ";"; + } + } + + /** + * Returns the native type codes of {@code clazz}. + */ + private static String getShortyType(final Class clazz) { + final String primitiveSignature = PRIMITIVE_TO_SIGNATURE.get(clazz); + if (primitiveSignature != null) { + return primitiveSignature; + } + return "L"; + } + + // @SuppressWarnings("ConstantConditions") + private static String getSignature(final Class retType, + final Class[] parameterTypes) { + final StringBuilder result = new StringBuilder(); + + result.append('('); + for (final Class parameterType : parameterTypes) { + result.append(getSignature(parameterType)); + } + result.append(")"); + result.append(getSignature(retType)); + + return result.toString(); + } + + private static String getShorty(final Class retType, + final Class[] parameterTypes) { + final StringBuilder result = new StringBuilder(); + + result.append(getShortyType(retType)); + for (final Class parameterType : parameterTypes) { + result.append(getShortyType(parameterType)); + } + + return result.toString(); + } + + static String getSignature(final Member m) { + if (m instanceof Method) { + final Method md = (Method) m; + return getSignature(md.getReturnType(), md.getParameterTypes()); + } + if (m instanceof Constructor) { + final Constructor c = (Constructor) m; + return getSignature(void.class, c.getParameterTypes()); + } + return null; + } + + static String getShorty(final Member m) { + if (m instanceof Method) { + final Method md = (Method) m; + return getShorty(md.getReturnType(), md.getParameterTypes()); + } + if (m instanceof Constructor) { + final Constructor c = (Constructor) m; + return getShorty(void.class, c.getParameterTypes()); + } + return null; + } +} diff --git a/edxp-whale/src/main/java/com/lody/whale/WhaleRuntime.java b/edxp-whale/src/main/java/com/lody/whale/WhaleRuntime.java new file mode 100644 index 00000000..2c0904e4 --- /dev/null +++ b/edxp-whale/src/main/java/com/lody/whale/WhaleRuntime.java @@ -0,0 +1,74 @@ +package com.lody.whale; + +import android.os.Build; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +import de.robv.android.xposed.XposedBridge; + +/** + * @author Lody + *

+ * NOTICE: Do not move or rename any methods in this class. + */ +public class WhaleRuntime { + + static { + System.loadLibrary("whale.edxp"); + } + + private static String getShorty(Member member) { + return VMHelper.getShorty(member); + } + + public static long[] countInstancesOfClasses(Class[] classes, boolean assignable) { + if (Build.VERSION.SDK_INT < 27) { + throw new UnsupportedOperationException("Not support countInstancesOfClasses on your device yet."); + } + try { + Class clazz = Class.forName("dalvik.system.VMDebug"); + Method method = clazz.getDeclaredMethod("countInstancesOfClasses", Class[].class, boolean.class); + return (long[]) method.invoke(null, classes, assignable); + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + public static Object[][] getInstancesOfClasses(Class[] classes, boolean assignable) { + if (Build.VERSION.SDK_INT < 28) { + throw new UnsupportedOperationException("Not support getInstancesOfClasses on your device yet."); + } + try { + Class clazz = Class.forName("dalvik.system.VMDebug"); + Method method = clazz.getDeclaredMethod("getInstancesOfClasses", Class[].class, boolean.class); + return (Object[][]) method.invoke(null, classes, assignable); + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + public static Object handleHookedMethod(Member member, long slot, Object additionInfo, Object thisObject, Object[] args) throws Throwable { + return XposedBridge.handleHookedMethod(member, slot, additionInfo, thisObject, args); + } + + public static native Object invokeOriginalMethodNative(long slot, Object thisObject, Object[] args) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException; + + public static native long getMethodSlot(Member member) throws IllegalArgumentException; + + public static native long hookMethodNative(Class declClass, Member method, Object additionInfo); + + public static native void setObjectClassNative(Object object, Class parent); + + public static native Object cloneToSubclassNative(Object object, Class subClass); + + public static native void removeFinalFlagNative(Class cl); + + public static native void enforceDisableHiddenAPIPolicy(); + + private static native void reserved0(); + + private static native void reserved1(); +} diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java index 2f0709ae..cf51c5f5 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java @@ -16,7 +16,7 @@ public class YahfaHookProvider implements HookProvider { } @Override - public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable { + public Object invokeOriginalMethod(Member method, long methodId, Object thisObject, Object[] args) throws Throwable { return DynamicBridge.invokeOriginalMethod(method, thisObject, args); } @@ -29,4 +29,9 @@ public class YahfaHookProvider implements HookProvider { public void deoptMethods(String packageName, ClassLoader classLoader) { PrebuiltMethodsDeopter.deoptMethods(packageName, classLoader); } + + @Override + public long getMethodId(Member member) { + return 0; + } } diff --git a/settings.gradle b/settings.gradle index c96662e1..c40d4f51 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook' \ No newline at end of file +include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook', ':edxp-whale' \ No newline at end of file diff --git a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java index 6fc716b2..cc88f03f 100644 --- a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java +++ b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java @@ -54,7 +54,7 @@ public class EdXpConfigGlobal { } @Override - public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) + public Object invokeOriginalMethod(Member method, long methodId, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { return null; } @@ -68,5 +68,10 @@ public class EdXpConfigGlobal { public void deoptMethods(String packageName, ClassLoader classLoader) { } + + @Override + public long getMethodId(Member member) { + return 0; + } }; } diff --git a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java index 671bd828..9e630e78 100644 --- a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java +++ b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java @@ -1,6 +1,5 @@ package com.elderdrivers.riru.edxp.hook; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import de.robv.android.xposed.XposedBridge; @@ -9,9 +8,11 @@ public interface HookProvider { void hookMethod(Member method, XposedBridge.AdditionalHookInfo additionalInfo); - Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable; + Object invokeOriginalMethod(Member method, long methodId, Object thisObject, Object[] args) throws Throwable; Member findMethodNative(Member hookMethod); void deoptMethods(String packageName, ClassLoader classLoader); + + long getMethodId(Member member); } diff --git a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java index 92d9fc2b..204a0b04 100644 --- a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java +++ b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java @@ -260,7 +260,7 @@ public final class XposedBridge { /** * This method is called as a replacement for hooked methods. */ - private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj, + public static Object handleHookedMethod(Member method, long originalMethodId, Object additionalInfoObj, Object thisObject, Object[] args) throws Throwable { AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj; @@ -398,12 +398,12 @@ public final class XposedBridge { EdXpConfigGlobal.getHookProvider().hookMethod(method, (AdditionalHookInfo) additionalInfoObj); } - private static Object invokeOriginalMethodNative(Member method, int methodId, + private static Object invokeOriginalMethodNative(Member method, long methodId, Class[] parameterTypes, Class returnType, Object thisObject, Object[] args) throws Throwable { - return EdXpConfigGlobal.getHookProvider().invokeOriginalMethod(method, thisObject, args); + return EdXpConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args); } /** @@ -452,7 +452,8 @@ public final class XposedBridge { throw new IllegalArgumentException("method must be of type Method or Constructor"); } - return invokeOriginalMethodNative(method, 0, parameterTypes, returnType, thisObject, args); + long methodId = EdXpConfigGlobal.getHookProvider().getMethodId(method); + return invokeOriginalMethodNative(method, methodId, parameterTypes, returnType, thisObject, args); } /*package*/ static void setObjectClass(Object obj, Class clazz) {