Introduce Whale hook for Android
This commit is contained in:
parent
e858ebd70b
commit
d0b1d7c52b
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
libsandhook.edxp.so
|
||||
libwhale.edxp.so
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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.** {*;}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="com.elderdrivers.riru.edxp.whale" />
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<Class<?>> 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<Class<?>> 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. " + "<init>" + ": " + "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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = "<init>";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = "<init>";
|
||||
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#<init> 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#<init> ends: " + mAppDir);
|
||||
|
||||
if (XposedBlackListHooker.shouldDisableHooks(packageName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packageName.equals("android")) {
|
||||
Router.logD("LoadedApk#<init> is android, skip: " + mAppDir);
|
||||
return;
|
||||
}
|
||||
|
||||
// mIncludeCode checking should go ahead of loadedPackagesInProcess added checking
|
||||
if (!XposedHelpers.getBooleanField(loadedApk, "mIncludeCode")) {
|
||||
Router.logD("LoadedApk#<init> mIncludeCode == false: " + mAppDir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!XposedInit.loadedPackagesInProcess.add(packageName)) {
|
||||
Router.logD("LoadedApk#<init> has been loaded before, skip: " + mAppDir);
|
||||
return;
|
||||
}
|
||||
|
||||
// OnePlus magic...
|
||||
if (Log.getStackTraceString(new Throwable()).
|
||||
contains("android.app.ActivityThread$ApplicationThread.schedulePreload")) {
|
||||
Router.logD("LoadedApk#<init> 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.<init>", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void backup(Object thiz, ActivityThread activityThread,
|
||||
ApplicationInfo aInfo, CompatibilityInfo compatInfo,
|
||||
ClassLoader baseLoader, boolean securityViolation,
|
||||
boolean includeCode, boolean registerPackage) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<String> getBlackList() {
|
||||
try {
|
||||
PREFERENCES.reload();
|
||||
Set<String> result = PREFERENCES.getStringSet(PREF_KEY_BLACK_LIST, new HashSet<String>());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
* <p>
|
||||
* 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<String, String[][]> 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<String, String[][]> getAll() {
|
||||
return CALLERS;
|
||||
}
|
||||
|
||||
public static String[][] get(String where) {
|
||||
return CALLERS.get(where);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Class<?>, 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook'
|
||||
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook', ':edxp-whale'
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue