Introduce SandHook
This commit is contained in:
parent
696ede0209
commit
e858ebd70b
|
|
@ -47,6 +47,28 @@ afterEvaluate {
|
|||
}
|
||||
|
||||
pushTask.dependsOn(zipTask)
|
||||
|
||||
|
||||
zipTask = task("zipSandhook${nameCapped}", type: Exec, dependsOn: ":edxp-sandhook:makeAndCopy${nameCapped}") {
|
||||
workingDir '..'
|
||||
commandLine 'sh', 'build.sh', \
|
||||
project.name, \
|
||||
"Sandhook-${project.version}-${nameLowered}", \
|
||||
"${project.extensions['module_name']}"
|
||||
}
|
||||
|
||||
pushTask = task("pushSandhook${nameCapped}", type: Exec) {
|
||||
workingDir 'release'
|
||||
def commands = ["adb", "push", "magisk-${project.extensions['module_name']}-Sandhook" +
|
||||
"-${project.version}-${nameLowered}.zip", "/sdcard/"]
|
||||
if (OperatingSystem.current().isWindows()) {
|
||||
commandLine 'cmd', '/c', commands.join(" ")
|
||||
} else {
|
||||
commandLine commands
|
||||
}
|
||||
}
|
||||
|
||||
pushTask.dependsOn(zipTask)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@
|
|||
#define INJECT_DEX_PATH \
|
||||
"/system/framework/edxp.jar:/system/framework/eddalvikdx.jar:/system/framework/eddexmaker.jar"
|
||||
|
||||
#define ENTRY_CLASS_NAME "com.elderdrivers.riru.edxp.yahfa.Main"
|
||||
#define ENTRY_CLASS_NAME "com.elderdrivers.riru.edxp.Main"
|
||||
|
||||
#endif //CONFIG_H
|
||||
|
|
@ -0,0 +1 @@
|
|||
libsandhook.edxp.so
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.elderdrivers.riru.edxp.sandhook"
|
||||
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')
|
||||
implementation 'com.swift.sandhook:hooklib:3.0.1'
|
||||
compileOnly project(':dexmaker')
|
||||
}
|
||||
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
|
||||
STUB_FILE_NAME = "MethodHookerStubs"
|
||||
|
||||
TEMP_STUB_CLASS_WRAPPER = """package com.swift.sandhook.xposedcompat.hookstub;
|
||||
|
||||
import static com.swift.sandhook.xposedcompat.hookstub.HookStubManager.hookBridge;
|
||||
import static com.swift.sandhook.xposedcompat.hookstub.HookStubManager.getMethodId;
|
||||
import static com.swift.sandhook.xposedcompat.hookstub.HookStubManager.originMethods;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexLog.printCallOriginError;
|
||||
|
||||
/**
|
||||
* this file is auto gen by genhookstubs.py
|
||||
* it is for sandhook internal hooker & backup methods
|
||||
**/
|
||||
public class MethodHookerStubs%d {
|
||||
%s
|
||||
}
|
||||
"""
|
||||
|
||||
TEMP_STUB_HOOK_METHOD_NAME = """stub_hook_%d"""
|
||||
TEMP_STUB_HOOK_BACKUP_NAME = """stub_backup_%d"""
|
||||
TEMP_STUB_CALL_ORIGIN_NAME = """call_origin_%d_%d"""
|
||||
|
||||
TEMP_STUB_GET_METHOD_ID_NAME = """getMethodId(%d, %d)"""
|
||||
|
||||
JAVA_TYPE_INT = "int"
|
||||
JAVA_CAST_INT = "(int)"
|
||||
JAVA_TYPE_LONG = "long"
|
||||
|
||||
TEMP_STUB_HOOK_METHOD = """
|
||||
public static %s %s(%s) throws Throwable {
|
||||
return %s hookBridge(%s, %s %s);
|
||||
}
|
||||
"""
|
||||
|
||||
TEMP_STUB_BACKUP_METHOD = """
|
||||
public static %s %s(%s) throws Throwable {
|
||||
try {
|
||||
printCallOriginError(originMethods[%s]);
|
||||
} catch (Throwable throwable) {}
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
|
||||
TEMP_STUB_CALL_ORIGIN_CLASS = """
|
||||
static class %s implements CallOriginCallBack {
|
||||
@Override
|
||||
public long call(long... args) throws Throwable {
|
||||
return %s(%s);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
TEMP_STUB_INFO = """
|
||||
public static boolean hasStubBackup = %s;
|
||||
public static int[] stubSizes = {%s};
|
||||
"""
|
||||
|
||||
|
||||
STUB_SIZES = [10,20,30,30,30,30,30,20,10,10,5,5,3]
|
||||
HAS_BACKUP = False;
|
||||
|
||||
|
||||
def getMethodId(args, index):
|
||||
return TEMP_STUB_GET_METHOD_ID_NAME % (args, index)
|
||||
|
||||
def getMethodHookName(index):
|
||||
return TEMP_STUB_HOOK_METHOD_NAME % index
|
||||
|
||||
def getMethodBackupName(index):
|
||||
return TEMP_STUB_HOOK_BACKUP_NAME % index
|
||||
|
||||
def getCallOriginClassName(args, index):
|
||||
return TEMP_STUB_CALL_ORIGIN_NAME % (args, index)
|
||||
|
||||
|
||||
def genArgsList(is64Bit, isDefine, length):
|
||||
args_list = ""
|
||||
for i in range(length):
|
||||
if (i != 0):
|
||||
args_list += ", "
|
||||
if isDefine:
|
||||
if (is64Bit):
|
||||
args_list += (JAVA_TYPE_LONG + " " + "a" + str(i))
|
||||
else:
|
||||
args_list += (JAVA_TYPE_INT + " " + "a" + str(i))
|
||||
else:
|
||||
args_list += ("a" + str(i))
|
||||
return args_list
|
||||
|
||||
|
||||
def genArgsListForCallOriginMethod(is64Bit, length):
|
||||
arg_name = """args[%s]"""
|
||||
args_list = ""
|
||||
for i in range(length):
|
||||
if (i != 0):
|
||||
args_list += ", "
|
||||
if (is64Bit):
|
||||
args_list += arg_name % i
|
||||
else:
|
||||
args_list += (JAVA_CAST_INT + arg_name % i)
|
||||
return args_list
|
||||
|
||||
|
||||
def genHookMethod(is64Bit, args, index):
|
||||
java_type = JAVA_TYPE_LONG if is64Bit else JAVA_TYPE_INT
|
||||
cast = "" if is64Bit else JAVA_CAST_INT
|
||||
args_list_pre = ", " if args > 0 else ""
|
||||
args_list = genArgsList(is64Bit, False, args)
|
||||
args_list_def = genArgsList(is64Bit, True, args)
|
||||
call_origin_obj = ("new " + getCallOriginClassName(args, index) + "()") if HAS_BACKUP else "null"
|
||||
method = TEMP_STUB_HOOK_METHOD % (java_type, getMethodHookName(index), args_list_def, cast, getMethodId(args, index), call_origin_obj, args_list_pre + args_list)
|
||||
return method
|
||||
|
||||
|
||||
def genBackupMethod(is64Bit, args, index):
|
||||
java_type = JAVA_TYPE_LONG if is64Bit else JAVA_TYPE_INT
|
||||
args_list_def = genArgsList(is64Bit, True, args)
|
||||
method = TEMP_STUB_BACKUP_METHOD % (java_type, getMethodBackupName(index), args_list_def, getMethodId(args, index))
|
||||
return method
|
||||
|
||||
def genCallOriginClass(is64Bit, args, index):
|
||||
method = TEMP_STUB_CALL_ORIGIN_CLASS % (getCallOriginClassName(args, index), getMethodBackupName(index), genArgsListForCallOriginMethod(is64Bit, args))
|
||||
return method
|
||||
|
||||
def genStubInfo():
|
||||
hasStub = "true" if HAS_BACKUP else "false"
|
||||
stubSizes = ""
|
||||
for args in range(len(STUB_SIZES)):
|
||||
if (args != 0):
|
||||
stubSizes += ", "
|
||||
stubSizes += str(STUB_SIZES[args])
|
||||
return TEMP_STUB_INFO % (hasStub, stubSizes)
|
||||
|
||||
def gen32Stub(packageDir):
|
||||
class_content = genStubInfo()
|
||||
class_name = STUB_FILE_NAME + "32"
|
||||
for args in range(len(STUB_SIZES)):
|
||||
for index in range(STUB_SIZES[args]):
|
||||
class_content += """\n\n\t//stub of arg size %d, index %d""" % (args, index)
|
||||
class_content += genHookMethod(False, args, index)
|
||||
if HAS_BACKUP:
|
||||
class_content += "\n"
|
||||
class_content += genCallOriginClass(False, args, index)
|
||||
class_content += "\n"
|
||||
class_content += genBackupMethod(False, args, index)
|
||||
class_content += "\n"
|
||||
class_str = TEMP_STUB_CLASS_WRAPPER % (32, class_content)
|
||||
javaFile = open(os.path.join(packageDir, class_name + ".java"), "w")
|
||||
javaFile.write(class_str)
|
||||
javaFile.close()
|
||||
|
||||
|
||||
def gen64Stub(packageDir):
|
||||
class_content = genStubInfo()
|
||||
class_name = STUB_FILE_NAME + "64"
|
||||
for args in range(len(STUB_SIZES)):
|
||||
for index in range(STUB_SIZES[args]):
|
||||
class_content += """\n\n\t//stub of arg size %d, index %d""" % (args, index)
|
||||
class_content += genHookMethod(True, args, index)
|
||||
if HAS_BACKUP:
|
||||
class_content += "\n"
|
||||
class_content += genCallOriginClass(True, args, index)
|
||||
class_content += "\n"
|
||||
class_content += genBackupMethod(True, args, index)
|
||||
class_content += "\n"
|
||||
class_str = TEMP_STUB_CLASS_WRAPPER % (64, class_content)
|
||||
javaFile = open(os.path.join(packageDir, class_name + ".java"), "w")
|
||||
javaFile.write(class_str)
|
||||
javaFile.close()
|
||||
|
||||
|
||||
def genStub(packageDir):
|
||||
for fileName in os.listdir(packageDir):
|
||||
if fileName.startswith(STUB_FILE_NAME):
|
||||
os.remove(os.path.join(packageDir, fileName))
|
||||
gen32Stub(packageDir)
|
||||
gen64Stub(packageDir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
genStub(os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
"src/main/java/com/swift/sandhook/xposedcompat/hookstub"))
|
||||
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.swift.sandhook.** {*;}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="com.elderdrivers.riru.edxp.sandhook" />
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
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.sandhook.BuildConfig;
|
||||
import com.elderdrivers.riru.edxp.config.InstallerChooser;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.sandhook.core.HookMethodResolver;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.Router;
|
||||
import com.elderdrivers.riru.edxp.sandhook.proxy.BlackWhiteListProxy;
|
||||
import com.elderdrivers.riru.edxp.sandhook.proxy.NormalProxy;
|
||||
import com.swift.sandhook.xposedcompat.methodgen.SandHookXposedBridge;
|
||||
|
||||
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());
|
||||
SandHookXposedBridge.setLibPath();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.sandhook.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.sandhook.entry.hooker.XposedBlackListHooker;
|
||||
|
||||
public class SandHookEdxpConfig 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,35 @@
|
|||
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;
|
||||
|
||||
public class SandHookProvider implements HookProvider {
|
||||
@Override
|
||||
public void hookMethod(Member method, XposedBridge.AdditionalHookInfo additionalInfo) {
|
||||
XposedCompat.hookMethod(method, additionalInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable {
|
||||
return SandHookXposedBridge.invokeOriginalMethod(method, thisObject, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Member findMethodNative(Member hookMethod) {
|
||||
return DexMakerUtils.findMethodNative(hookMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deoptMethods(String packageName, ClassLoader classLoader) {
|
||||
PrebuiltMethodsDeopter.deoptMethods(packageName, classLoader);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.core;
|
||||
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.OnePlusWorkAroundHooker;
|
||||
|
||||
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.sandhook.core;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
|
||||
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.sandhook.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,261 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.dexmaker;
|
||||
|
||||
import android.app.AndroidAppHelper;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.sandhook.core.HookMain;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
public class DexMakerUtils {
|
||||
|
||||
private static final boolean IN_MEMORY_DEX_ELIGIBLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
|
||||
public static boolean shouldUseInMemoryHook() {
|
||||
if (!IN_MEMORY_DEX_ELIGIBLE) {
|
||||
return false;
|
||||
}
|
||||
String packageName = AndroidAppHelper.currentPackageName();
|
||||
if (TextUtils.isEmpty(packageName)) { //default to true
|
||||
DexLog.w("packageName is empty, processName=" + Main.appProcessName
|
||||
+ ", appDataDir=" + Main.appDataDir);
|
||||
return true;
|
||||
}
|
||||
return !ConfigManager.shouldUseCompatMode(packageName);
|
||||
}
|
||||
|
||||
public static void autoBoxIfNecessary(Code code, Local<Object> target, Local source) {
|
||||
String boxMethod = "valueOf";
|
||||
TypeId<?> boxTypeId;
|
||||
TypeId typeId = source.getType();
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
boxTypeId = TypeId.get(Boolean.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BOOLEAN), target, source);
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
boxTypeId = TypeId.get(Byte.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BYTE), target, source);
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
boxTypeId = TypeId.get(Character.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.CHAR), target, source);
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
boxTypeId = TypeId.get(Double.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.DOUBLE), target, source);
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
boxTypeId = TypeId.get(Float.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.FLOAT), target, source);
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
boxTypeId = TypeId.get(Integer.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.INT), target, source);
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
boxTypeId = TypeId.get(Long.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.LONG), target, source);
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
boxTypeId = TypeId.get(Short.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.SHORT), target, source);
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
code.loadConstant(target, null);
|
||||
} else {
|
||||
code.move(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
public static void autoUnboxIfNecessary(Code code, Local target, Local source,
|
||||
Map<TypeId, Local> tmpLocals, boolean castObj) {
|
||||
String unboxMethod;
|
||||
TypeId typeId = target.getType();
|
||||
TypeId<?> boxTypeId;
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
unboxMethod = "booleanValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Boolean;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.BOOLEAN, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
unboxMethod = "byteValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Byte;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.BYTE, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
unboxMethod = "charValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Character;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.CHAR, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
unboxMethod = "doubleValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Double;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.DOUBLE, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
unboxMethod = "floatValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Float;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.FLOAT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
unboxMethod = "intValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Integer;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.INT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
unboxMethod = "longValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Long;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.LONG, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
unboxMethod = "shortValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Short;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.SHORT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
code.loadConstant(target, null);
|
||||
} else if (castObj) {
|
||||
code.cast(target, source);
|
||||
} else {
|
||||
code.move(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<TypeId, Local> createResultLocals(Code code) {
|
||||
HashMap<TypeId, Local> resultMap = new HashMap<>();
|
||||
Local<Boolean> booleanLocal = code.newLocal(TypeId.BOOLEAN);
|
||||
Local<Byte> byteLocal = code.newLocal(TypeId.BYTE);
|
||||
Local<Character> charLocal = code.newLocal(TypeId.CHAR);
|
||||
Local<Double> doubleLocal = code.newLocal(TypeId.DOUBLE);
|
||||
Local<Float> floatLocal = code.newLocal(TypeId.FLOAT);
|
||||
Local<Integer> intLocal = code.newLocal(TypeId.INT);
|
||||
Local<Long> longLocal = code.newLocal(TypeId.LONG);
|
||||
Local<Short> shortLocal = code.newLocal(TypeId.SHORT);
|
||||
Local<Void> voidLocal = code.newLocal(TypeId.VOID);
|
||||
Local<Object> objectLocal = code.newLocal(TypeId.OBJECT);
|
||||
|
||||
Local<Object> booleanObjLocal = code.newLocal(TypeId.get("Ljava/lang/Boolean;"));
|
||||
Local<Object> byteObjLocal = code.newLocal(TypeId.get("Ljava/lang/Byte;"));
|
||||
Local<Object> charObjLocal = code.newLocal(TypeId.get("Ljava/lang/Character;"));
|
||||
Local<Object> doubleObjLocal = code.newLocal(TypeId.get("Ljava/lang/Double;"));
|
||||
Local<Object> floatObjLocal = code.newLocal(TypeId.get("Ljava/lang/Float;"));
|
||||
Local<Object> intObjLocal = code.newLocal(TypeId.get("Ljava/lang/Integer;"));
|
||||
Local<Object> longObjLocal = code.newLocal(TypeId.get("Ljava/lang/Long;"));
|
||||
Local<Object> shortObjLocal = code.newLocal(TypeId.get("Ljava/lang/Short;"));
|
||||
Local<Object> voidObjLocal = code.newLocal(TypeId.get("Ljava/lang/Void;"));
|
||||
|
||||
// backup need initialized locals
|
||||
code.loadConstant(booleanLocal, false);
|
||||
code.loadConstant(byteLocal, (byte) 0);
|
||||
code.loadConstant(charLocal, '\0');
|
||||
code.loadConstant(doubleLocal, 0.0);
|
||||
code.loadConstant(floatLocal, 0.0f);
|
||||
code.loadConstant(intLocal, 0);
|
||||
code.loadConstant(longLocal, 0L);
|
||||
code.loadConstant(shortLocal, (short) 0);
|
||||
code.loadConstant(voidLocal, null);
|
||||
code.loadConstant(objectLocal, null);
|
||||
// all to null
|
||||
code.loadConstant(booleanObjLocal, null);
|
||||
code.loadConstant(byteObjLocal, null);
|
||||
code.loadConstant(charObjLocal, null);
|
||||
code.loadConstant(doubleObjLocal, null);
|
||||
code.loadConstant(floatObjLocal, null);
|
||||
code.loadConstant(intObjLocal, null);
|
||||
code.loadConstant(longObjLocal, null);
|
||||
code.loadConstant(shortObjLocal, null);
|
||||
code.loadConstant(voidObjLocal, null);
|
||||
// package all
|
||||
resultMap.put(TypeId.BOOLEAN, booleanLocal);
|
||||
resultMap.put(TypeId.BYTE, byteLocal);
|
||||
resultMap.put(TypeId.CHAR, charLocal);
|
||||
resultMap.put(TypeId.DOUBLE, doubleLocal);
|
||||
resultMap.put(TypeId.FLOAT, floatLocal);
|
||||
resultMap.put(TypeId.INT, intLocal);
|
||||
resultMap.put(TypeId.LONG, longLocal);
|
||||
resultMap.put(TypeId.SHORT, shortLocal);
|
||||
resultMap.put(TypeId.VOID, voidLocal);
|
||||
resultMap.put(TypeId.OBJECT, objectLocal);
|
||||
|
||||
resultMap.put(TypeId.get("Ljava/lang/Boolean;"), booleanObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Byte;"), byteObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Character;"), charObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Double;"), doubleObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Float;"), floatObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Integer;"), intObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Long;"), longObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Short;"), shortObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Void;"), voidObjLocal);
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
public static TypeId getObjTypeIdIfPrimitive(TypeId typeId) {
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
return TypeId.get("Ljava/lang/Boolean;");
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
return TypeId.get("Ljava/lang/Byte;");
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
return TypeId.get("Ljava/lang/Character;");
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
return TypeId.get("Ljava/lang/Double;");
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
return TypeId.get("Ljava/lang/Float;");
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
return TypeId.get("Ljava/lang/Integer;");
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
return TypeId.get("Ljava/lang/Long;");
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
return TypeId.get("Ljava/lang/Short;");
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
return TypeId.get("Ljava/lang/Void;");
|
||||
} else {
|
||||
return typeId;
|
||||
}
|
||||
}
|
||||
|
||||
public static void returnRightValue(Code code, Class<?> returnType, Map<Class, Local> resultLocals) {
|
||||
String unboxMethod;
|
||||
TypeId<?> boxTypeId;
|
||||
code.returnValue(resultLocals.get(returnType));
|
||||
}
|
||||
|
||||
public static String getSha1Hex(String text) {
|
||||
final MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-1");
|
||||
byte[] result = digest.digest(text.getBytes("UTF-8"));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : result) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
DexLog.e("error hashing target method: " + text, e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static Member findMethodNative(Member hookMethod) {
|
||||
MethodInfo methodInfo = new MethodInfo(hookMethod);
|
||||
Class declaringClass = methodInfo.getClassForSure();
|
||||
Member reflectMethod = (Member) HookMain.findMethod(
|
||||
declaringClass, methodInfo.methodName, methodInfo.methodSig);
|
||||
if (reflectMethod == null) {
|
||||
DexLog.e("method not found: name="
|
||||
+ methodInfo.methodName + ", sig=" + methodInfo.methodSig);
|
||||
reflectMethod = hookMethod;
|
||||
}
|
||||
return reflectMethod;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.dexmaker;
|
||||
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getPackageName;
|
||||
import static com.elderdrivers.riru.edxp.util.ProcessUtils.getCurrentProcessName;
|
||||
import static com.elderdrivers.riru.edxp.sandhook.dexmaker.DexMakerUtils.shouldUseInMemoryHook;
|
||||
|
||||
public final class DynamicBridge {
|
||||
|
||||
private static final HashMap<Member, Method> hookedInfo = new HashMap<>();
|
||||
private static final HookerDexMaker dexMaker = new HookerDexMaker();
|
||||
private static final AtomicBoolean dexPathInited = new AtomicBoolean(false);
|
||||
private static File dexDir;
|
||||
|
||||
/**
|
||||
* Reset dexPathInited flag once we enter child process
|
||||
* since it might have been set to true in zygote process
|
||||
*/
|
||||
public static void onForkPost() {
|
||||
dexPathInited.set(false);
|
||||
}
|
||||
|
||||
public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||
DexLog.d("hooking " + hookMethod);
|
||||
if (!checkMember(hookMethod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hookedInfo.containsKey(hookMethod)) {
|
||||
DexLog.w("already hook method:" + hookMethod.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
DexLog.d("start to generate class for: " + hookMethod);
|
||||
try {
|
||||
// for Android Oreo and later use InMemoryClassLoader
|
||||
if (!shouldUseInMemoryHook()) {
|
||||
setupDexCachePath();
|
||||
}
|
||||
dexMaker.start(hookMethod, additionalHookInfo,
|
||||
hookMethod.getDeclaringClass().getClassLoader(), getDexDirPath());
|
||||
hookedInfo.put(hookMethod, dexMaker.getCallBackupMethod());
|
||||
} catch (Exception e) {
|
||||
DexLog.e("error occur when generating dex. dexDir=" + dexDir, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getDexDirPath() {
|
||||
if (dexDir == null) {
|
||||
return null;
|
||||
}
|
||||
return dexDir.getAbsolutePath();
|
||||
}
|
||||
|
||||
private static void setupDexCachePath() {
|
||||
// using file based DexClassLoader
|
||||
if (!dexPathInited.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// we always choose to use device encrypted storage data on android N and later
|
||||
// in case some app is installing hooks before phone is unlocked
|
||||
String fixedAppDataDir = getDataPathPrefix() + getPackageName(Main.appDataDir) + "/";
|
||||
dexDir = new File(fixedAppDataDir, "/cache/edhookers/"
|
||||
+ getCurrentProcessName(Main.appProcessName).replace(":", "_") + "/");
|
||||
dexDir.mkdirs();
|
||||
} catch (Throwable throwable) {
|
||||
DexLog.e("error when init dex path", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkMember(Member member) {
|
||||
|
||||
if (member instanceof Method) {
|
||||
return true;
|
||||
} else if (member instanceof Constructor<?>) {
|
||||
return true;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
DexLog.e("Cannot hook interfaces: " + member.toString());
|
||||
return false;
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
DexLog.e("Cannot hook abstract methods: " + member.toString());
|
||||
return false;
|
||||
} else {
|
||||
DexLog.e("Only methods and constructors can be hooked: " + member.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
Method callBackup = hookedInfo.get(method);
|
||||
if (callBackup == null) {
|
||||
throw new IllegalStateException("method not hooked, cannot call original method.");
|
||||
}
|
||||
if (!Modifier.isStatic(callBackup.getModifiers())) {
|
||||
throw new IllegalStateException("original method is not static, something must be wrong!");
|
||||
}
|
||||
callBackup.setAccessible(true);
|
||||
if (args == null) {
|
||||
args = new Object[0];
|
||||
}
|
||||
final int argsSize = args.length;
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
return callBackup.invoke(null, args);
|
||||
} else {
|
||||
Object[] newArgs = new Object[argsSize + 1];
|
||||
newArgs[0] = thisObject;
|
||||
for (int i = 1; i < newArgs.length; i++) {
|
||||
newArgs[i] = args[i - 1];
|
||||
}
|
||||
return callBackup.invoke(null, newArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,569 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.dexmaker;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.sandhook.core.HookMain;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import dalvik.system.InMemoryDexClassLoader;
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import external.com.android.dx.BinaryOp;
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.Comparison;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.FieldId;
|
||||
import external.com.android.dx.Label;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.MethodId;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.sandhook.dexmaker.DexMakerUtils.autoBoxIfNecessary;
|
||||
import static com.elderdrivers.riru.edxp.sandhook.dexmaker.DexMakerUtils.autoUnboxIfNecessary;
|
||||
import static com.elderdrivers.riru.edxp.sandhook.dexmaker.DexMakerUtils.createResultLocals;
|
||||
import static com.elderdrivers.riru.edxp.sandhook.dexmaker.DexMakerUtils.getObjTypeIdIfPrimitive;
|
||||
|
||||
public class HookerDexMaker {
|
||||
|
||||
public static final String METHOD_NAME_BACKUP = "backup";
|
||||
public static final String METHOD_NAME_HOOK = "hook";
|
||||
public static final String METHOD_NAME_CALL_BACKUP = "callBackup";
|
||||
public static final String METHOD_NAME_SETUP = "setup";
|
||||
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
|
||||
private static final String CLASS_DESC_PREFIX = "L";
|
||||
/**
|
||||
* Note: this identifier is used in native codes to pass class access verification.
|
||||
*/
|
||||
private static final String CLASS_NAME_PREFIX = "EdHooker_";
|
||||
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
|
||||
private static final String FIELD_NAME_METHOD = "method";
|
||||
private static final String PARAMS_FIELD_NAME_METHOD = "method";
|
||||
private static final String PARAMS_FIELD_NAME_THIS_OBJECT = "thisObject";
|
||||
private static final String PARAMS_FIELD_NAME_ARGS = "args";
|
||||
private static final String CALLBACK_METHOD_NAME_BEFORE = "callBeforeHookedMethod";
|
||||
private static final String CALLBACK_METHOD_NAME_AFTER = "callAfterHookedMethod";
|
||||
private static final String PARAMS_METHOD_NAME_IS_EARLY_RETURN = "isEarlyReturn";
|
||||
private static final TypeId<Throwable> throwableTypeId = TypeId.get(Throwable.class);
|
||||
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
|
||||
private static final TypeId<XC_MethodHook> callbackTypeId = TypeId.get(XC_MethodHook.class);
|
||||
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
|
||||
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
|
||||
private static final TypeId<XposedBridge.CopyOnWriteSortedSet> callbacksTypeId
|
||||
= TypeId.get(XposedBridge.CopyOnWriteSortedSet.class);
|
||||
private static final TypeId<XC_MethodHook.MethodHookParam> paramTypeId
|
||||
= TypeId.get(XC_MethodHook.MethodHookParam.class);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setResultMethodId =
|
||||
paramTypeId.getMethod(TypeId.VOID, "setResult", TypeId.OBJECT);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setThrowableMethodId =
|
||||
paramTypeId.getMethod(TypeId.VOID, "setThrowable", throwableTypeId);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Object> getResultMethodId =
|
||||
paramTypeId.getMethod(TypeId.OBJECT, "getResult");
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Throwable> getThrowableMethodId =
|
||||
paramTypeId.getMethod(throwableTypeId, "getThrowable");
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Boolean> hasThrowableMethodId =
|
||||
paramTypeId.getMethod(TypeId.BOOLEAN, "hasThrowable");
|
||||
private static final MethodId<XC_MethodHook, Void> callAfterCallbackMethodId =
|
||||
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_AFTER, paramTypeId);
|
||||
private static final MethodId<XC_MethodHook, Void> callBeforeCallbackMethodId =
|
||||
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_BEFORE, paramTypeId);
|
||||
private static final FieldId<XC_MethodHook.MethodHookParam, Boolean> returnEarlyFieldId =
|
||||
paramTypeId.getField(TypeId.BOOLEAN, "returnEarly");
|
||||
private static final TypeId<XposedBridge> xposedBridgeTypeId = TypeId.get(XposedBridge.class);
|
||||
private static final MethodId<XposedBridge, Void> logThrowableMethodId =
|
||||
xposedBridgeTypeId.getMethod(TypeId.VOID, "log", throwableTypeId);
|
||||
private static final MethodId<XposedBridge, Void> logStrMethodId =
|
||||
xposedBridgeTypeId.getMethod(TypeId.VOID, "log", TypeId.STRING);
|
||||
|
||||
private static AtomicLong sClassNameSuffix = new AtomicLong(1);
|
||||
|
||||
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
|
||||
private FieldId<?, Member> mMethodFieldId;
|
||||
private MethodId<?, ?> mBackupMethodId;
|
||||
private MethodId<?, ?> mCallBackupMethodId;
|
||||
private MethodId<?, ?> mHookMethodId;
|
||||
|
||||
private TypeId<?> mHookerTypeId;
|
||||
private TypeId<?>[] mParameterTypeIds;
|
||||
private Class<?>[] mActualParameterTypes;
|
||||
private Class<?> mReturnType;
|
||||
private TypeId<?> mReturnTypeId;
|
||||
private boolean mIsStatic;
|
||||
// TODO use this to generate methods
|
||||
private boolean mHasThrowable;
|
||||
|
||||
private DexMaker mDexMaker;
|
||||
private Member mMember;
|
||||
private XposedBridge.AdditionalHookInfo mHookInfo;
|
||||
private ClassLoader mAppClassLoader;
|
||||
private Class<?> mHookClass;
|
||||
private Method mHookMethod;
|
||||
private Method mBackupMethod;
|
||||
private Method mCallBackupMethod;
|
||||
private String mDexDirPath;
|
||||
|
||||
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
|
||||
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
|
||||
int offset = 0;
|
||||
if (!isStatic) {
|
||||
parameterTypeIds[0] = TypeId.OBJECT;
|
||||
offset = 1;
|
||||
}
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
|
||||
}
|
||||
return parameterTypeIds;
|
||||
}
|
||||
|
||||
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
if (isStatic) {
|
||||
return parameterTypes;
|
||||
}
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = parameterSize + 1;
|
||||
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
|
||||
int offset = 1;
|
||||
newParameterTypes[0] = Object.class;
|
||||
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
|
||||
return newParameterTypes;
|
||||
}
|
||||
|
||||
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||
ClassLoader appClassLoader, String dexDirPath) throws Exception {
|
||||
if (member instanceof Method) {
|
||||
Method method = (Method) member;
|
||||
mIsStatic = Modifier.isStatic(method.getModifiers());
|
||||
mReturnType = method.getReturnType();
|
||||
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|
||||
|| mReturnType.isPrimitive()) {
|
||||
mReturnTypeId = TypeId.get(mReturnType);
|
||||
} else {
|
||||
// all others fallback to plain Object for convenience
|
||||
mReturnType = Object.class;
|
||||
mReturnTypeId = TypeId.OBJECT;
|
||||
}
|
||||
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = method.getExceptionTypes().length > 0;
|
||||
} else if (member instanceof Constructor) {
|
||||
Constructor constructor = (Constructor) member;
|
||||
mIsStatic = false;
|
||||
mReturnType = void.class;
|
||||
mReturnTypeId = TypeId.VOID;
|
||||
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = constructor.getExceptionTypes().length > 0;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
|
||||
}
|
||||
mMember = member;
|
||||
mHookInfo = hookInfo;
|
||||
mDexDirPath = dexDirPath;
|
||||
if (appClassLoader == null
|
||||
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
|
||||
mAppClassLoader = this.getClass().getClassLoader();
|
||||
} else {
|
||||
mAppClassLoader = appClassLoader;
|
||||
}
|
||||
doMake();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private void doMake() throws Exception {
|
||||
final boolean useInMemoryCl = TextUtils.isEmpty(mDexDirPath);
|
||||
mDexMaker = new DexMaker();
|
||||
ClassLoader loader;
|
||||
// Generate a Hooker class.
|
||||
String className = CLASS_NAME_PREFIX;
|
||||
if (!useInMemoryCl) {
|
||||
// if not using InMemoryDexClassLoader, className is also used as dex file name
|
||||
// so it should be different from each other
|
||||
String suffix = DexMakerUtils.getSha1Hex(mMember.toString());
|
||||
if (TextUtils.isEmpty(suffix)) { // just in case
|
||||
suffix = String.valueOf(sClassNameSuffix.getAndIncrement());
|
||||
}
|
||||
className = className + suffix;
|
||||
if (!new File(mDexDirPath, className).exists()) {
|
||||
// if file exists, reuse it and skip generating
|
||||
doGenerate(className);
|
||||
}
|
||||
// load dex file from disk
|
||||
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath), className);
|
||||
} else {
|
||||
// do everything in memory
|
||||
doGenerate(className);
|
||||
byte[] dexBytes = mDexMaker.generate();
|
||||
loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader);
|
||||
}
|
||||
|
||||
mHookClass = loader.loadClass(className);
|
||||
// Execute our newly-generated code in-process.
|
||||
mHookClass.getMethod(METHOD_NAME_SETUP, Member.class, XposedBridge.AdditionalHookInfo.class)
|
||||
.invoke(null, mMember, mHookInfo);
|
||||
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
|
||||
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
|
||||
mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes);
|
||||
Main.setMethodNonCompilable(mCallBackupMethod);
|
||||
HookMain.backupAndHook(mMember, mHookMethod, mBackupMethod);
|
||||
}
|
||||
|
||||
private void doGenerate(String className) {
|
||||
String classDesc = CLASS_DESC_PREFIX + className + ";";
|
||||
mHookerTypeId = TypeId.get(classDesc);
|
||||
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
|
||||
generateFields();
|
||||
generateSetupMethod();
|
||||
generateBackupMethod();
|
||||
generateHookMethod();
|
||||
generateCallBackupMethod();
|
||||
}
|
||||
|
||||
public Method getHookMethod() {
|
||||
return mHookMethod;
|
||||
}
|
||||
|
||||
public Method getBackupMethod() {
|
||||
return mBackupMethod;
|
||||
}
|
||||
|
||||
public Method getCallBackupMethod() {
|
||||
return mCallBackupMethod;
|
||||
}
|
||||
|
||||
public Class getHookClass() {
|
||||
return mHookClass;
|
||||
}
|
||||
|
||||
private void generateFields() {
|
||||
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
|
||||
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
|
||||
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
|
||||
}
|
||||
|
||||
private void generateSetupMethod() {
|
||||
MethodId<?, Void> setupMethodId = mHookerTypeId.getMethod(
|
||||
TypeId.VOID, METHOD_NAME_SETUP, memberTypeId, hookInfoTypeId);
|
||||
Code code = mDexMaker.declare(setupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
// init logic
|
||||
// get parameters
|
||||
Local<Member> method = code.getParameter(0, memberTypeId);
|
||||
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.getParameter(1, hookInfoTypeId);
|
||||
// save params to static
|
||||
code.sput(mMethodFieldId, method);
|
||||
code.sput(mHookInfoFieldId, hookInfo);
|
||||
code.returnVoid();
|
||||
}
|
||||
|
||||
private void generateBackupMethod() {
|
||||
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
|
||||
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
// do nothing
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// we have limited the returnType to primitives or Object, so this should be safe
|
||||
code.returnValue(resultLocals.get(mReturnTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateCallBackupMethod() {
|
||||
mCallBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_CALL_BACKUP, mParameterTypeIds);
|
||||
Code code = mDexMaker.declare(mCallBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
// just call backup and return its result
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
Local result = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||
code.returnValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateHookMethod() {
|
||||
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
|
||||
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
// code starts
|
||||
|
||||
// prepare common labels
|
||||
Label noHookReturn = new Label();
|
||||
Label incrementAndCheckBefore = new Label();
|
||||
Label tryBeforeCatch = new Label();
|
||||
Label noExceptionBefore = new Label();
|
||||
Label checkAndCallBackup = new Label();
|
||||
Label beginCallBefore = new Label();
|
||||
Label beginCallAfter = new Label();
|
||||
Label tryOrigCatch = new Label();
|
||||
Label noExceptionOrig = new Label();
|
||||
Label tryAfterCatch = new Label();
|
||||
Label decrementAndCheckAfter = new Label();
|
||||
Label noBackupThrowable = new Label();
|
||||
Label throwThrowable = new Label();
|
||||
// prepare locals
|
||||
Local<Boolean> disableHooks = code.newLocal(TypeId.BOOLEAN);
|
||||
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.newLocal(hookInfoTypeId);
|
||||
Local<XposedBridge.CopyOnWriteSortedSet> callbacks = code.newLocal(callbacksTypeId);
|
||||
Local<Object[]> snapshot = code.newLocal(objArrayTypeId);
|
||||
Local<Integer> snapshotLen = code.newLocal(TypeId.INT);
|
||||
Local<Object> callbackObj = code.newLocal(TypeId.OBJECT);
|
||||
Local<XC_MethodHook> callback = code.newLocal(callbackTypeId);
|
||||
|
||||
Local<Object> resultObj = code.newLocal(TypeId.OBJECT); // as a temp Local
|
||||
Local<Integer> one = code.newLocal(TypeId.INT);
|
||||
Local<Object> nullObj = code.newLocal(TypeId.OBJECT);
|
||||
Local<Throwable> throwable = code.newLocal(throwableTypeId);
|
||||
|
||||
Local<XC_MethodHook.MethodHookParam> param = code.newLocal(paramTypeId);
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||
Local<Boolean> returnEarly = code.newLocal(TypeId.BOOLEAN);
|
||||
|
||||
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||
|
||||
Local<Integer> beforeIdx = code.newLocal(TypeId.INT);
|
||||
Local<Object> lastResult = code.newLocal(TypeId.OBJECT);
|
||||
Local<Throwable> lastThrowable = code.newLocal(throwableTypeId);
|
||||
Local<Boolean> hasThrowable = code.newLocal(TypeId.BOOLEAN);
|
||||
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
code.loadConstant(args, null);
|
||||
code.loadConstant(argIndex, 0);
|
||||
code.loadConstant(one, 1);
|
||||
code.loadConstant(snapshotLen, 0);
|
||||
code.loadConstant(nullObj, null);
|
||||
|
||||
// check XposedBridge.disableHooks flag
|
||||
|
||||
FieldId<XposedBridge, Boolean> disableHooksField =
|
||||
xposedBridgeTypeId.getField(TypeId.BOOLEAN, "disableHooks");
|
||||
code.sget(disableHooksField, disableHooks);
|
||||
// disableHooks == true => no hooking
|
||||
code.compareZ(Comparison.NE, noHookReturn, disableHooks);
|
||||
|
||||
// check callbacks length
|
||||
code.sget(mHookInfoFieldId, hookInfo);
|
||||
code.iget(hookInfoTypeId.getField(callbacksTypeId, "callbacks"), callbacks, hookInfo);
|
||||
code.invokeVirtual(callbacksTypeId.getMethod(objArrayTypeId, "getSnapshot"), snapshot, callbacks);
|
||||
code.arrayLength(snapshotLen, snapshot);
|
||||
// snapshotLen == 0 => no hooking
|
||||
code.compareZ(Comparison.EQ, noHookReturn, snapshotLen);
|
||||
|
||||
// start hooking
|
||||
|
||||
// prepare hooking locals
|
||||
int paramsSize = mParameterTypeIds.length;
|
||||
int offset = 0;
|
||||
// thisObject
|
||||
if (mIsStatic) {
|
||||
// thisObject = null
|
||||
code.loadConstant(thisObject, null);
|
||||
} else {
|
||||
// thisObject = args[0]
|
||||
offset = 1;
|
||||
code.move(thisObject, allArgsLocals[0]);
|
||||
}
|
||||
// actual args (exclude thisObject if this is not a static method)
|
||||
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||
code.newArray(args, actualParamSize);
|
||||
for (int i = offset; i < paramsSize; i++) {
|
||||
Local parameter = allArgsLocals[i];
|
||||
// save parameter to resultObj as Object
|
||||
autoBoxIfNecessary(code, resultObj, parameter);
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
// save Object to args
|
||||
code.aput(args, argIndex, resultObj);
|
||||
}
|
||||
// create param
|
||||
code.newInstance(param, paramTypeId.getConstructor());
|
||||
// set method, thisObject, args
|
||||
code.sget(mMethodFieldId, method);
|
||||
code.iput(paramTypeId.getField(memberTypeId, "method"), param, method);
|
||||
code.iput(paramTypeId.getField(TypeId.OBJECT, "thisObject"), param, thisObject);
|
||||
code.iput(paramTypeId.getField(objArrayTypeId, "args"), param, args);
|
||||
|
||||
// call beforeCallbacks
|
||||
code.loadConstant(beforeIdx, 0);
|
||||
|
||||
code.mark(beginCallBefore);
|
||||
// start of try
|
||||
code.addCatchClause(throwableTypeId, tryBeforeCatch);
|
||||
|
||||
code.aget(callbackObj, snapshot, beforeIdx);
|
||||
code.cast(callback, callbackObj);
|
||||
code.invokeVirtual(callBeforeCallbackMethodId, null, callback, param);
|
||||
code.jump(noExceptionBefore);
|
||||
|
||||
// end of try
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
|
||||
// start of catch
|
||||
code.mark(tryBeforeCatch);
|
||||
code.moveException(throwable);
|
||||
code.invokeStatic(logThrowableMethodId, null, throwable);
|
||||
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||
code.loadConstant(returnEarly, false);
|
||||
code.iput(returnEarlyFieldId, param, returnEarly);
|
||||
code.jump(incrementAndCheckBefore);
|
||||
|
||||
// no exception when calling beforeCallbacks
|
||||
code.mark(noExceptionBefore);
|
||||
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||
// if returnEarly == false, continue
|
||||
code.compareZ(Comparison.EQ, incrementAndCheckBefore, returnEarly);
|
||||
// returnEarly == true, break
|
||||
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||
code.jump(checkAndCallBackup);
|
||||
|
||||
// increment and check to continue
|
||||
code.mark(incrementAndCheckBefore);
|
||||
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||
code.compare(Comparison.LT, beginCallBefore, beforeIdx, snapshotLen);
|
||||
|
||||
// check and call backup
|
||||
code.mark(checkAndCallBackup);
|
||||
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||
// if returnEarly == true, go to call afterCallbacks directly
|
||||
code.compareZ(Comparison.NE, noExceptionOrig, returnEarly);
|
||||
// try to call backup
|
||||
// try start
|
||||
code.addCatchClause(throwableTypeId, tryOrigCatch);
|
||||
// we have to load args[] to paramLocals
|
||||
// because args[] may be changed in beforeHookedMethod
|
||||
// should consider first param is thisObj if hooked method is not static
|
||||
offset = mIsStatic ? 0 : 1;
|
||||
for (int i = offset; i < allArgsLocals.length; i++) {
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
code.aget(resultObj, args, argIndex);
|
||||
autoUnboxIfNecessary(code, allArgsLocals[i], resultObj, resultLocals, true);
|
||||
}
|
||||
// get pre-created Local with a matching typeId
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
// TODO maybe keep preset result to do some magic?
|
||||
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||
} else {
|
||||
Local returnedResult = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, returnedResult, allArgsLocals);
|
||||
// save returnedResult to resultObj as a Object
|
||||
autoBoxIfNecessary(code, resultObj, returnedResult);
|
||||
// save resultObj to param
|
||||
code.invokeVirtual(setResultMethodId, null, param, resultObj);
|
||||
}
|
||||
// go to call afterCallbacks
|
||||
code.jump(noExceptionOrig);
|
||||
// try end
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
// catch
|
||||
code.mark(tryOrigCatch);
|
||||
code.moveException(throwable);
|
||||
// exception occurred when calling backup, save throwable to param
|
||||
code.invokeVirtual(setThrowableMethodId, null, param, throwable);
|
||||
|
||||
code.mark(noExceptionOrig);
|
||||
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||
|
||||
// call afterCallbacks
|
||||
code.mark(beginCallAfter);
|
||||
// save results of backup calling
|
||||
code.invokeVirtual(getResultMethodId, lastResult, param);
|
||||
code.invokeVirtual(getThrowableMethodId, lastThrowable, param);
|
||||
// try start
|
||||
code.addCatchClause(throwableTypeId, tryAfterCatch);
|
||||
code.aget(callbackObj, snapshot, beforeIdx);
|
||||
code.cast(callback, callbackObj);
|
||||
code.invokeVirtual(callAfterCallbackMethodId, null, callback, param);
|
||||
// all good, just continue
|
||||
code.jump(decrementAndCheckAfter);
|
||||
// try end
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
// catch
|
||||
code.mark(tryAfterCatch);
|
||||
code.moveException(throwable);
|
||||
code.invokeStatic(logThrowableMethodId, null, throwable);
|
||||
// if lastThrowable == null, go to recover lastResult
|
||||
code.compareZ(Comparison.EQ, noBackupThrowable, lastThrowable);
|
||||
// lastThrowable != null, recover lastThrowable
|
||||
code.invokeVirtual(setThrowableMethodId, null, param, lastThrowable);
|
||||
// continue
|
||||
code.jump(decrementAndCheckAfter);
|
||||
code.mark(noBackupThrowable);
|
||||
// recover lastResult and continue
|
||||
code.invokeVirtual(setResultMethodId, null, param, lastResult);
|
||||
// decrement and check continue
|
||||
code.mark(decrementAndCheckAfter);
|
||||
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||
code.compareZ(Comparison.GE, beginCallAfter, beforeIdx);
|
||||
|
||||
// callbacks end
|
||||
// return
|
||||
code.invokeVirtual(hasThrowableMethodId, hasThrowable, param);
|
||||
// if hasThrowable, throw the throwable and return
|
||||
code.compareZ(Comparison.NE, throwThrowable, hasThrowable);
|
||||
// return getResult
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// getResult always return an Object, so save to resultObj
|
||||
code.invokeVirtual(getResultMethodId, resultObj, param);
|
||||
// have to unbox it if returnType is primitive
|
||||
// casting Object
|
||||
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||
code.cast(matchObjLocal, resultObj);
|
||||
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
|
||||
// return
|
||||
code.returnValue(toReturn);
|
||||
}
|
||||
// throw throwable
|
||||
code.mark(throwThrowable);
|
||||
code.invokeVirtual(getThrowableMethodId, throwable, param);
|
||||
code.throwValue(throwable);
|
||||
|
||||
// call backup and return
|
||||
code.mark(noHookReturn);
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
Local result = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||
code.returnValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
private Local[] createParameterLocals(Code code) {
|
||||
Local[] paramLocals = new Local[mParameterTypeIds.length];
|
||||
for (int i = 0; i < mParameterTypeIds.length; i++) {
|
||||
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
|
||||
}
|
||||
return paramLocals;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.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,126 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.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.sandhook.config.SandHookEdxpConfig;
|
||||
import com.elderdrivers.riru.edxp.sandhook.config.SandHookProvider;
|
||||
import com.elderdrivers.riru.edxp.sandhook.core.HookMain;
|
||||
import com.elderdrivers.riru.edxp.sandhook.dexmaker.DynamicBridge;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.bootstrap.AppBootstrapHookInfo;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.bootstrap.SysBootstrapHookInfo;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.bootstrap.SysInnerHookInfo;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.bootstrap.WorkAroundHookInfo;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.SystemMainHooker;
|
||||
import com.swift.sandhook.xposedcompat.methodgen.SandHookXposedBridge;
|
||||
|
||||
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;
|
||||
DynamicBridge.onForkPost();
|
||||
SandHookXposedBridge.onForkPost();
|
||||
}
|
||||
|
||||
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 SandHookEdxpConfig();
|
||||
EdXpConfigGlobal.sHookProvider = new SandHookProvider();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.entry.bootstrap;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.HandleBindAppHooker;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.LoadedApkConstructorHooker;
|
||||
import com.elderdrivers.riru.edxp.sandhook.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.sandhook.entry.bootstrap;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.HandleBindAppHooker;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.LoadedApkConstructorHooker;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.OnePlusWorkAroundHooker;
|
||||
import com.elderdrivers.riru.edxp.sandhook.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.sandhook.entry.bootstrap;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.StartBootstrapServicesHooker;
|
||||
|
||||
public class SysInnerHookInfo implements KeepMembers {
|
||||
public static String[] hookItemNames = {
|
||||
StartBootstrapServicesHooker.class.getName()
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.entry.bootstrap;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.hooker.OnePlusWorkAroundHooker;
|
||||
|
||||
public class WorkAroundHookInfo implements KeepMembers {
|
||||
public static String[] hookItemNames = {
|
||||
OnePlusWorkAroundHooker.class.getName()
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.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.sandhook.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.sandhook.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.sandhook.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.sandhook.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.sandhook.entry.hooker;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.sandhook.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.sandhook.entry.hooker;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.sandhook.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.sandhook.entry.hooker;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.Router;
|
||||
import com.elderdrivers.riru.edxp.sandhook.util.PrebuiltMethodsDeopter;
|
||||
|
||||
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.sandhook.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.sandhook.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.sandhook.proxy;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.util.ProcessUtils;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.Router;
|
||||
import com.elderdrivers.riru.edxp.sandhook.util.PrebuiltMethodsDeopter;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
|
||||
import static com.elderdrivers.riru.edxp.Main.isAppNeedHook;
|
||||
|
||||
/**
|
||||
* 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.sandhook.proxy;
|
||||
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.Router;
|
||||
import com.elderdrivers.riru.edxp.sandhook.util.PrebuiltMethodsDeopter;
|
||||
|
||||
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.sandhook.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.sandhook.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.sandhook.util.InlinedMethodCallers.KEY_BOOT_IMAGE;
|
||||
import static com.elderdrivers.riru.edxp.sandhook.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,48 @@
|
|||
package com.swift.sandhook.xposedcompat;
|
||||
|
||||
import com.swift.sandhook.xposedcompat.classloaders.ComposeClassLoader;
|
||||
import com.swift.sandhook.xposedcompat.methodgen.SandHookXposedBridge;
|
||||
import com.swift.sandhook.xposedcompat.utils.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
public class XposedCompat {
|
||||
|
||||
//try to use internal stub hooker & backup method to speed up hook
|
||||
public static volatile boolean useInternalStub = true;
|
||||
public static volatile boolean useNewDexMaker = true;
|
||||
public static volatile boolean retryWhenCallOriginError = false;
|
||||
|
||||
private static ClassLoader sandHookXposedClassLoader;
|
||||
|
||||
public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||
SandHookXposedBridge.hookMethod(hookMethod, additionalHookInfo);
|
||||
}
|
||||
|
||||
public static ClassLoader getSandHookXposedClassLoader(ClassLoader appOriginClassLoader, ClassLoader sandBoxHostClassLoader) {
|
||||
if (sandHookXposedClassLoader != null) {
|
||||
return sandHookXposedClassLoader;
|
||||
} else {
|
||||
sandHookXposedClassLoader = new ComposeClassLoader(sandBoxHostClassLoader, appOriginClassLoader);
|
||||
return sandHookXposedClassLoader;
|
||||
}
|
||||
}
|
||||
|
||||
// public static boolean clearCache() {
|
||||
// try {
|
||||
// FileUtils.delete(cacheDir);
|
||||
// cacheDir.mkdirs();
|
||||
// return true;
|
||||
// } catch (Throwable throwable) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void clearOatCache() {
|
||||
// SandHookXposedBridge.clearOatFile();
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.swift.sandhook.xposedcompat.classloaders;
|
||||
|
||||
/**
|
||||
* Created by weishu on 17/11/30.
|
||||
*/
|
||||
|
||||
public class ComposeClassLoader extends ClassLoader {
|
||||
|
||||
private final ClassLoader mAppClassLoader;
|
||||
public ComposeClassLoader(ClassLoader parent, ClassLoader appClassLoader) {
|
||||
super(parent);
|
||||
mAppClassLoader = appClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
Class clazz = null;
|
||||
|
||||
try {
|
||||
clazz = mAppClassLoader.loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// IGNORE.
|
||||
}
|
||||
if (clazz == null) {
|
||||
clazz = super.loadClass(name, resolve);
|
||||
}
|
||||
|
||||
if (clazz == null) {
|
||||
throw new ClassNotFoundException();
|
||||
}
|
||||
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.swift.sandhook.xposedcompat.hookstub;
|
||||
|
||||
public interface CallOriginCallBack {
|
||||
long call(long... args) throws Throwable;
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.swift.sandhook.xposedcompat.hookstub;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.utils.ParamWrapper;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
public class HookMethodEntity {
|
||||
|
||||
public Member origin;
|
||||
public Method hook;
|
||||
public Method backup;
|
||||
public Class[] parType;
|
||||
public Class retType;
|
||||
|
||||
public HookMethodEntity(Member origin, Method hook, Method backup) {
|
||||
this.origin = origin;
|
||||
this.hook = hook;
|
||||
this.backup = backup;
|
||||
}
|
||||
|
||||
public Object[] getArgs(long... addresses) {
|
||||
if (addresses == null || addresses.length == 0)
|
||||
return new Object[0];
|
||||
if (parType == null || parType.length == 0)
|
||||
return new Object[0];
|
||||
int argStart = 0;
|
||||
if (!isStatic()) {
|
||||
argStart = 1;
|
||||
}
|
||||
Object[] args = new Object[parType.length];
|
||||
for (int i = argStart;i < parType.length + argStart;i++) {
|
||||
args[i - argStart] = getArg(i - argStart, addresses[i]);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public long[] getArgsAddress(long[] oldAddress, Object... args) {
|
||||
if (oldAddress == null || oldAddress.length == 0)
|
||||
return new long[0];
|
||||
long[] addresses;
|
||||
int argStart = 0;
|
||||
if (!isStatic()) {
|
||||
argStart = 1;
|
||||
addresses = new long[oldAddress.length + 1];
|
||||
addresses[0] = oldAddress[0];
|
||||
} else {
|
||||
addresses = new long[oldAddress.length];
|
||||
}
|
||||
for (int i = 0;i < parType.length;i++) {
|
||||
addresses[i + argStart] = ParamWrapper.objectToAddress(parType[i], args[i]);
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public Object getThis(long address) {
|
||||
if (isStatic())
|
||||
return null;
|
||||
return SandHook.getObject(address);
|
||||
}
|
||||
|
||||
public Object getArg(int index, long address) {
|
||||
return ParamWrapper.addressToObject(parType[index], address);
|
||||
}
|
||||
|
||||
public Object getResult(long address) {
|
||||
if (isVoid())
|
||||
return null;
|
||||
return ParamWrapper.addressToObject(retType, address);
|
||||
}
|
||||
|
||||
public long getResultAddress(Object result) {
|
||||
if (isVoid())
|
||||
return 0;
|
||||
return ParamWrapper.objectToAddress(retType, result);
|
||||
}
|
||||
|
||||
public boolean isVoid() {
|
||||
return retType == null || Void.TYPE.equals(retType);
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
return origin instanceof Constructor;
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(origin.getModifiers());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
package com.swift.sandhook.xposedcompat.hookstub;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.SandHookMethodResolver;
|
||||
import com.swift.sandhook.utils.ParamWrapper;
|
||||
import com.swift.sandhook.wrapper.BackupMethodStubs;
|
||||
import com.swift.sandhook.xposedcompat.utils.DexLog;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
|
||||
import static de.robv.android.xposed.XposedBridge.sHookedMethodCallbacks;
|
||||
|
||||
public class HookStubManager {
|
||||
|
||||
|
||||
public static int MAX_STUB_ARGS = 0;
|
||||
|
||||
public static int[] stubSizes;
|
||||
|
||||
public static boolean hasStubBackup = false;
|
||||
|
||||
public static AtomicInteger[] curUseStubIndexes;
|
||||
|
||||
public static int ALL_STUB = 0;
|
||||
|
||||
public static Member[] originMethods;
|
||||
public static HookMethodEntity[] hookMethodEntities;
|
||||
|
||||
private static final Map<Member, XposedBridge.CopyOnWriteSortedSet<XC_MethodHook>> hookCallbacks
|
||||
= sHookedMethodCallbacks;
|
||||
|
||||
static {
|
||||
Class stubClass = SandHook.is64Bit() ? MethodHookerStubs64.class : MethodHookerStubs32.class;
|
||||
stubSizes = (int[]) XposedHelpers.getStaticObjectField(stubClass, "stubSizes");
|
||||
Boolean hasBackup = (Boolean) XposedHelpers.getStaticObjectField(stubClass, "hasStubBackup");
|
||||
hasStubBackup = hasBackup == null ? false : hasBackup;
|
||||
if (stubSizes != null && stubSizes.length > 0) {
|
||||
MAX_STUB_ARGS = stubSizes.length - 1;
|
||||
curUseStubIndexes = new AtomicInteger[MAX_STUB_ARGS + 1];
|
||||
for (int i = 0; i < MAX_STUB_ARGS + 1; i++) {
|
||||
curUseStubIndexes[i] = new AtomicInteger(0);
|
||||
ALL_STUB += stubSizes[i];
|
||||
}
|
||||
originMethods = new Member[ALL_STUB];
|
||||
hookMethodEntities = new HookMethodEntity[ALL_STUB];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static HookMethodEntity getHookMethodEntity(Member origin) {
|
||||
|
||||
if (!support()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class[] parType;
|
||||
Class retType;
|
||||
boolean isStatic = Modifier.isStatic(origin.getModifiers());
|
||||
|
||||
if (origin instanceof Method) {
|
||||
Method method = (Method) origin;
|
||||
retType = method.getReturnType();
|
||||
parType = method.getParameterTypes();
|
||||
} else if (origin instanceof Constructor) {
|
||||
Constructor constructor = (Constructor) origin;
|
||||
retType = Void.TYPE;
|
||||
parType = constructor.getParameterTypes();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ParamWrapper.support(retType))
|
||||
return null;
|
||||
|
||||
int needStubArgCount = isStatic ? 0 : 1;
|
||||
|
||||
if (parType != null) {
|
||||
needStubArgCount += parType.length;
|
||||
if (needStubArgCount > MAX_STUB_ARGS)
|
||||
return null;
|
||||
for (Class par:parType) {
|
||||
if (!ParamWrapper.support(par))
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
parType = new Class[0];
|
||||
}
|
||||
|
||||
synchronized (HookStubManager.class) {
|
||||
StubMethodsInfo stubMethodInfo = getStubMethodPair(SandHook.is64Bit(), needStubArgCount);
|
||||
if (stubMethodInfo == null)
|
||||
return null;
|
||||
HookMethodEntity entity = new HookMethodEntity(origin, stubMethodInfo.hook, stubMethodInfo.backup);
|
||||
entity.retType = retType;
|
||||
entity.parType = parType;
|
||||
int id = getMethodId(stubMethodInfo.args, stubMethodInfo.index);
|
||||
originMethods[id] = origin;
|
||||
hookMethodEntities[id] = entity;
|
||||
if (hasStubBackup && !tryCompileAndResolveCallOriginMethod(entity.backup, stubMethodInfo.args, stubMethodInfo.index)) {
|
||||
DexLog.w("internal stub <" + entity.hook.getName() + "> call origin compile failure, skip use internal stub");
|
||||
return null;
|
||||
} else {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMethodId(int args, int index) {
|
||||
int id = index;
|
||||
for (int i = 0;i < args;i++) {
|
||||
id += stubSizes[i];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public static String getHookMethodName(int index) {
|
||||
return "stub_hook_" + index;
|
||||
}
|
||||
|
||||
public static String getBackupMethodName(int index) {
|
||||
return "stub_backup_" + index;
|
||||
}
|
||||
|
||||
public static String getCallOriginClassName(int args, int index) {
|
||||
return "call_origin_" + args + "_" + index;
|
||||
}
|
||||
|
||||
|
||||
static class StubMethodsInfo {
|
||||
int args = 0;
|
||||
int index = 0;
|
||||
Method hook;
|
||||
Method backup;
|
||||
|
||||
public StubMethodsInfo(int args, int index, Method hook, Method backup) {
|
||||
this.args = args;
|
||||
this.index = index;
|
||||
this.hook = hook;
|
||||
this.backup = backup;
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized StubMethodsInfo getStubMethodPair(boolean is64Bit, int stubArgs) {
|
||||
|
||||
stubArgs = getMatchStubArgsCount(stubArgs);
|
||||
|
||||
if (stubArgs < 0)
|
||||
return null;
|
||||
|
||||
int curUseStubIndex = curUseStubIndexes[stubArgs].getAndIncrement();
|
||||
Class[] pars = getFindMethodParTypes(is64Bit, stubArgs);
|
||||
try {
|
||||
if (is64Bit) {
|
||||
Method hook = MethodHookerStubs64.class.getDeclaredMethod(getHookMethodName(curUseStubIndex), pars);
|
||||
Method backup = hasStubBackup ? MethodHookerStubs64.class.getDeclaredMethod(getBackupMethodName(curUseStubIndex), pars) : BackupMethodStubs.getStubMethod();
|
||||
if (hook == null || backup == null)
|
||||
return null;
|
||||
return new StubMethodsInfo(stubArgs, curUseStubIndex, hook, backup);
|
||||
} else {
|
||||
Method hook = MethodHookerStubs32.class.getDeclaredMethod(getHookMethodName(curUseStubIndex), pars);
|
||||
Method backup = hasStubBackup ? MethodHookerStubs32.class.getDeclaredMethod(getBackupMethodName(curUseStubIndex), pars) : BackupMethodStubs.getStubMethod();
|
||||
if (hook == null || backup == null)
|
||||
return null;
|
||||
return new StubMethodsInfo(stubArgs, curUseStubIndex, hook, backup);
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Method getCallOriginMethod(int args, int index) {
|
||||
Class stubClass = SandHook.is64Bit() ? MethodHookerStubs64.class : MethodHookerStubs32.class;
|
||||
String className = stubClass.getName();
|
||||
className += "$";
|
||||
className += getCallOriginClassName(args, index);
|
||||
try {
|
||||
Class callOriginClass = Class.forName(className, true, stubClass.getClassLoader());
|
||||
return callOriginClass.getDeclaredMethod("call", long[].class);
|
||||
} catch (Throwable e) {
|
||||
Log.e("HookStubManager", "load call origin class error!", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean tryCompileAndResolveCallOriginMethod(Method backupMethod, int args, int index) {
|
||||
Method method = getCallOriginMethod(args, index);
|
||||
if (method != null) {
|
||||
SandHookMethodResolver.resolveMethod(method, backupMethod);
|
||||
return SandHook.compileMethod(method);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMatchStubArgsCount(int stubArgs) {
|
||||
for (int i = stubArgs;i <= MAX_STUB_ARGS;i++) {
|
||||
if (curUseStubIndexes[i].get() < stubSizes[i])
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Class[] getFindMethodParTypes(boolean is64Bit, int stubArgs) {
|
||||
if (stubArgs == 0)
|
||||
return null;
|
||||
Class[] args = new Class[stubArgs];
|
||||
if (is64Bit) {
|
||||
for (int i = 0;i < stubArgs;i++) {
|
||||
args[i] = long.class;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0;i < stubArgs;i++) {
|
||||
args[i] = int.class;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public static long hookBridge(int id, CallOriginCallBack callOrigin, long... stubArgs) throws Throwable {
|
||||
|
||||
Member originMethod = originMethods[id];
|
||||
HookMethodEntity entity = hookMethodEntities[id];
|
||||
|
||||
Object thiz = null;
|
||||
Object[] args = null;
|
||||
|
||||
if (hasArgs(stubArgs)) {
|
||||
thiz = entity.getThis(stubArgs[0]);
|
||||
args = entity.getArgs(stubArgs);
|
||||
}
|
||||
|
||||
if (XposedBridge.disableHooks) {
|
||||
if (hasStubBackup) {
|
||||
return callOrigin.call(stubArgs);
|
||||
} else {
|
||||
return callOrigin(entity, originMethod, thiz, args);
|
||||
}
|
||||
}
|
||||
|
||||
DexLog.printMethodHookIn(originMethod);
|
||||
|
||||
Object[] snapshot = hookCallbacks.get(originMethod).getSnapshot();
|
||||
if (snapshot == null || snapshot.length == 0) {
|
||||
if (hasStubBackup) {
|
||||
return callOrigin.call(stubArgs);
|
||||
} else {
|
||||
return callOrigin(entity, originMethod, thiz, args);
|
||||
}
|
||||
}
|
||||
|
||||
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
||||
|
||||
param.method = originMethod;
|
||||
param.thisObject = thiz;
|
||||
param.args = args;
|
||||
|
||||
int beforeIdx = 0;
|
||||
do {
|
||||
try {
|
||||
((XC_MethodHook) snapshot[beforeIdx]).callBeforeHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
// reset result (ignoring what the unexpectedly exiting callback did)
|
||||
param.setResult(null);
|
||||
param.returnEarly = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.returnEarly) {
|
||||
// skip remaining "before" callbacks and corresponding "after" callbacks
|
||||
beforeIdx++;
|
||||
break;
|
||||
}
|
||||
} while (++beforeIdx < snapshot.length);
|
||||
|
||||
// call original method if not requested otherwise
|
||||
if (!param.returnEarly) {
|
||||
try {
|
||||
if (hasStubBackup) {
|
||||
//prepare new args
|
||||
long[] newArgs = entity.getArgsAddress(stubArgs, param.args);
|
||||
param.setResult(entity.getResult(callOrigin.call(newArgs)));
|
||||
} else {
|
||||
param.setResult(SandHook.callOriginMethod(originMethod, thiz, param.args));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
XposedBridge.log(e);
|
||||
param.setThrowable(e);
|
||||
}
|
||||
}
|
||||
|
||||
// call "after method" callbacks
|
||||
int afterIdx = beforeIdx - 1;
|
||||
do {
|
||||
Object lastResult = param.getResult();
|
||||
Throwable lastThrowable = param.getThrowable();
|
||||
|
||||
try {
|
||||
((XC_MethodHook) snapshot[afterIdx]).callAfterHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
if (lastThrowable == null)
|
||||
param.setResult(lastResult);
|
||||
else
|
||||
param.setThrowable(lastThrowable);
|
||||
}
|
||||
} while (--afterIdx >= 0);
|
||||
if (!param.hasThrowable()) {
|
||||
return entity.getResultAddress(param.getResult());
|
||||
} else {
|
||||
throw param.getThrowable();
|
||||
}
|
||||
}
|
||||
|
||||
public static Object hookBridge(Member origin, Object thiz, Object... args) throws Throwable {
|
||||
|
||||
|
||||
if (XposedBridge.disableHooks) {
|
||||
return SandHook.callOriginMethod(origin, thiz, args);
|
||||
}
|
||||
|
||||
DexLog.printMethodHookIn(origin);
|
||||
|
||||
Object[] snapshot = hookCallbacks.get(origin).getSnapshot();
|
||||
if (snapshot == null || snapshot.length == 0) {
|
||||
return SandHook.callOriginMethod(origin, thiz, args);
|
||||
}
|
||||
|
||||
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
||||
|
||||
param.method = origin;
|
||||
param.thisObject = thiz;
|
||||
param.args = args;
|
||||
|
||||
int beforeIdx = 0;
|
||||
do {
|
||||
try {
|
||||
((XC_MethodHook) snapshot[beforeIdx]).callBeforeHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
// reset result (ignoring what the unexpectedly exiting callback did)
|
||||
param.setResult(null);
|
||||
param.returnEarly = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.returnEarly) {
|
||||
// skip remaining "before" callbacks and corresponding "after" callbacks
|
||||
beforeIdx++;
|
||||
break;
|
||||
}
|
||||
} while (++beforeIdx < snapshot.length);
|
||||
|
||||
// call original method if not requested otherwise
|
||||
if (!param.returnEarly) {
|
||||
try {
|
||||
param.setResult(SandHook.callOriginMethod(origin, thiz, param.args));
|
||||
} catch (Throwable e) {
|
||||
XposedBridge.log(e);
|
||||
param.setThrowable(e);
|
||||
}
|
||||
}
|
||||
|
||||
// call "after method" callbacks
|
||||
int afterIdx = beforeIdx - 1;
|
||||
do {
|
||||
Object lastResult = param.getResult();
|
||||
Throwable lastThrowable = param.getThrowable();
|
||||
|
||||
try {
|
||||
((XC_MethodHook) snapshot[afterIdx]).callAfterHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
if (lastThrowable == null)
|
||||
param.setResult(lastResult);
|
||||
else
|
||||
param.setThrowable(lastThrowable);
|
||||
}
|
||||
} while (--afterIdx >= 0);
|
||||
if (!param.hasThrowable()) {
|
||||
return param.getResult();
|
||||
} else {
|
||||
throw param.getThrowable();
|
||||
}
|
||||
}
|
||||
|
||||
public static long callOrigin(HookMethodEntity entity, Member origin, Object thiz, Object[] args) throws Throwable {
|
||||
Object res = SandHook.callOriginMethod(origin, thiz, args);
|
||||
return entity.getResultAddress(res);
|
||||
}
|
||||
|
||||
private static boolean hasArgs(long... args) {
|
||||
return args != null && args.length > 0;
|
||||
}
|
||||
|
||||
public static boolean support() {
|
||||
return MAX_STUB_ARGS > 0 && SandHook.canGetObject() && SandHook.canGetObjectAddress();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,22 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ErrorCatch {
|
||||
|
||||
public static Object callOriginError(Member originMethod, Method backupMethod, Object thiz, Object[] args) throws Throwable {
|
||||
if (XposedCompat.retryWhenCallOriginError) {
|
||||
Log.w("SandHook", "method <" + originMethod.toString() + "> use invoke to call origin!");
|
||||
return SandHook.callOriginMethod(originMethod, backupMethod, thiz, args);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
public interface HookMaker {
|
||||
void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||
ClassLoader appClassLoader, String dexDirPath) throws Exception;
|
||||
Method getHookMethod();
|
||||
Method getBackupMethod();
|
||||
Method getCallBackupMethod();
|
||||
}
|
||||
|
|
@ -0,0 +1,698 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.SandHookMethodResolver;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
import com.swift.sandhook.xposedcompat.utils.DexLog;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Map;
|
||||
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import external.com.android.dx.BinaryOp;
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.Comparison;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.FieldId;
|
||||
import external.com.android.dx.Label;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.MethodId;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.MD5;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoBoxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoUnboxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.createResultLocals;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getObjTypeIdIfPrimitive;
|
||||
|
||||
public class HookerDexMaker implements HookMaker {
|
||||
|
||||
public static final String METHOD_NAME_BACKUP = "backup";
|
||||
public static final String METHOD_NAME_HOOK = "hook";
|
||||
public static final String METHOD_NAME_CALL_BACKUP = "callBackup";
|
||||
public static final String METHOD_NAME_SETUP = "setup";
|
||||
public static final String METHOD_NAME_LOG = "printMethodHookIn";
|
||||
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
|
||||
private static final String CLASS_DESC_PREFIX = "L";
|
||||
private static final String CLASS_NAME_PREFIX = "SandHooker";
|
||||
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
|
||||
private static final String FIELD_NAME_METHOD = "method";
|
||||
private static final String FIELD_NAME_BACKUP_METHOD = "backupMethod";
|
||||
private static final String PARAMS_FIELD_NAME_METHOD = "method";
|
||||
private static final String PARAMS_FIELD_NAME_THIS_OBJECT = "thisObject";
|
||||
private static final String PARAMS_FIELD_NAME_ARGS = "args";
|
||||
private static final String CALLBACK_METHOD_NAME_BEFORE = "callBeforeHookedMethod";
|
||||
private static final String CALLBACK_METHOD_NAME_AFTER = "callAfterHookedMethod";
|
||||
private static final TypeId<Throwable> throwableTypeId = TypeId.get(Throwable.class);
|
||||
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
|
||||
private static final TypeId<Method> methodTypeId = TypeId.get(Method.class);
|
||||
private static final TypeId<XC_MethodHook> callbackTypeId = TypeId.get(XC_MethodHook.class);
|
||||
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
|
||||
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
|
||||
private static final TypeId<XposedBridge.CopyOnWriteSortedSet> callbacksTypeId
|
||||
= TypeId.get(XposedBridge.CopyOnWriteSortedSet.class);
|
||||
private static final TypeId<XC_MethodHook.MethodHookParam> paramTypeId
|
||||
= TypeId.get(XC_MethodHook.MethodHookParam.class);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setResultMethodId =
|
||||
paramTypeId.getMethod(TypeId.VOID, "setResult", TypeId.OBJECT);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setThrowableMethodId =
|
||||
paramTypeId.getMethod(TypeId.VOID, "setThrowable", throwableTypeId);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Object> getResultMethodId =
|
||||
paramTypeId.getMethod(TypeId.OBJECT, "getResult");
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Throwable> getThrowableMethodId =
|
||||
paramTypeId.getMethod(throwableTypeId, "getThrowable");
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Boolean> hasThrowableMethodId =
|
||||
paramTypeId.getMethod(TypeId.BOOLEAN, "hasThrowable");
|
||||
private static final MethodId<XC_MethodHook, Void> callAfterCallbackMethodId =
|
||||
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_AFTER, paramTypeId);
|
||||
private static final MethodId<XC_MethodHook, Void> callBeforeCallbackMethodId =
|
||||
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_BEFORE, paramTypeId);
|
||||
private static final FieldId<XC_MethodHook.MethodHookParam, Boolean> returnEarlyFieldId =
|
||||
paramTypeId.getField(TypeId.BOOLEAN, "returnEarly");
|
||||
private static final TypeId<XposedBridge> xposedBridgeTypeId = TypeId.get(XposedBridge.class);
|
||||
private static final MethodId<XposedBridge, Void> logThrowableMethodId =
|
||||
xposedBridgeTypeId.getMethod(TypeId.VOID, "log", throwableTypeId);
|
||||
|
||||
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
|
||||
private FieldId<?, Member> mMethodFieldId;
|
||||
private FieldId<?, Method> mBackupMethodFieldId;
|
||||
private MethodId<?, ?> mBackupMethodId;
|
||||
private MethodId<?, ?> mCallBackupMethodId;
|
||||
private MethodId<?, ?> mHookMethodId;
|
||||
private MethodId<?, ?> mPrintLogMethodId;
|
||||
private MethodId<?, ?> mSandHookCallOriginMethodId;
|
||||
|
||||
private TypeId<?> mHookerTypeId;
|
||||
private TypeId<?>[] mParameterTypeIds;
|
||||
private Class<?>[] mActualParameterTypes;
|
||||
private Class<?> mReturnType;
|
||||
private TypeId<?> mReturnTypeId;
|
||||
private boolean mIsStatic;
|
||||
// TODO use this to generate methods
|
||||
private boolean mHasThrowable;
|
||||
|
||||
private DexMaker mDexMaker;
|
||||
private Member mMember;
|
||||
private XposedBridge.AdditionalHookInfo mHookInfo;
|
||||
private ClassLoader mAppClassLoader;
|
||||
private Class<?> mHookClass;
|
||||
private Method mHookMethod;
|
||||
private Method mBackupMethod;
|
||||
private Method mCallBackupMethod;
|
||||
private String mDexDirPath;
|
||||
|
||||
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
|
||||
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
|
||||
int offset = 0;
|
||||
if (!isStatic) {
|
||||
parameterTypeIds[0] = TypeId.OBJECT;
|
||||
offset = 1;
|
||||
}
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
|
||||
}
|
||||
return parameterTypeIds;
|
||||
}
|
||||
|
||||
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
if (isStatic) {
|
||||
return parameterTypes;
|
||||
}
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = parameterSize + 1;
|
||||
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
|
||||
int offset = 1;
|
||||
newParameterTypes[0] = Object.class;
|
||||
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
|
||||
return newParameterTypes;
|
||||
}
|
||||
|
||||
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||
ClassLoader appClassLoader, String dexDirPath) throws Exception {
|
||||
if (member instanceof Method) {
|
||||
Method method = (Method) member;
|
||||
mIsStatic = Modifier.isStatic(method.getModifiers());
|
||||
mReturnType = method.getReturnType();
|
||||
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|
||||
|| mReturnType.isPrimitive()) {
|
||||
mReturnTypeId = TypeId.get(mReturnType);
|
||||
} else {
|
||||
// all others fallback to plain Object for convenience
|
||||
mReturnType = Object.class;
|
||||
mReturnTypeId = TypeId.OBJECT;
|
||||
}
|
||||
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = method.getExceptionTypes().length > 0;
|
||||
} else if (member instanceof Constructor) {
|
||||
Constructor constructor = (Constructor) member;
|
||||
mIsStatic = false;
|
||||
mReturnType = void.class;
|
||||
mReturnTypeId = TypeId.VOID;
|
||||
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = constructor.getExceptionTypes().length > 0;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
|
||||
}
|
||||
mMember = member;
|
||||
mHookInfo = hookInfo;
|
||||
mDexDirPath = dexDirPath;
|
||||
if (appClassLoader == null
|
||||
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
|
||||
mAppClassLoader = this.getClass().getClassLoader();
|
||||
} else {
|
||||
mAppClassLoader = appClassLoader;
|
||||
}
|
||||
|
||||
mDexMaker = new DexMaker();
|
||||
// Generate a Hooker class.
|
||||
String className = getClassName(mMember);
|
||||
String dexName = className + ".jar";
|
||||
|
||||
HookWrapper.HookEntity hookEntity = null;
|
||||
//try load cache first
|
||||
try {
|
||||
ClassLoader loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath), dexName);
|
||||
if (loader != null) {
|
||||
hookEntity = loadHookerClass(loader, className);
|
||||
}
|
||||
} catch (Throwable throwable) {}
|
||||
|
||||
//do generate
|
||||
if (hookEntity == null) {
|
||||
hookEntity = doMake(className, dexName);
|
||||
}
|
||||
SandHook.hook(hookEntity);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity doMake(String className, String dexName) throws Exception {
|
||||
mHookerTypeId = TypeId.get(CLASS_DESC_PREFIX + className + ";");
|
||||
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
|
||||
generateFields();
|
||||
generateSetupMethod();
|
||||
if (XposedCompat.retryWhenCallOriginError) {
|
||||
generateBackupAndCallOriginCheckMethod();
|
||||
} else {
|
||||
generateBackupMethod();
|
||||
}
|
||||
generateCallBackupMethod();
|
||||
generateHookMethod();
|
||||
|
||||
ClassLoader loader;
|
||||
if (TextUtils.isEmpty(mDexDirPath)) {
|
||||
throw new IllegalArgumentException("dexDirPath should not be empty!!!");
|
||||
}
|
||||
// Create the dex file and load it.
|
||||
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath), dexName);
|
||||
return loadHookerClass(loader, className);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity loadHookerClass(ClassLoader loader, String className) throws Exception {
|
||||
mHookClass = loader.loadClass(className);
|
||||
// Execute our newly-generated code in-process.
|
||||
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
|
||||
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
|
||||
mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes);
|
||||
SandHook.resolveStaticMethod(mCallBackupMethod);
|
||||
SandHookMethodResolver.resolveMethod(mCallBackupMethod, mBackupMethod);
|
||||
SandHook.compileMethod(mCallBackupMethod);
|
||||
mHookClass.getMethod(METHOD_NAME_SETUP, Member.class, Method.class, XposedBridge.AdditionalHookInfo.class).invoke(null, mMember, mBackupMethod, mHookInfo);
|
||||
return new HookWrapper.HookEntity(mMember, mHookMethod, mBackupMethod);
|
||||
}
|
||||
|
||||
private String getClassName(Member originMethod) {
|
||||
return CLASS_NAME_PREFIX + "_" + MD5(originMethod.toString());
|
||||
}
|
||||
|
||||
public Method getHookMethod() {
|
||||
return mHookMethod;
|
||||
}
|
||||
|
||||
public Method getBackupMethod() {
|
||||
return mBackupMethod;
|
||||
}
|
||||
|
||||
public Method getCallBackupMethod() {
|
||||
return mCallBackupMethod;
|
||||
}
|
||||
|
||||
public Class getHookClass() {
|
||||
return mHookClass;
|
||||
}
|
||||
|
||||
private void generateFields() {
|
||||
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
|
||||
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
|
||||
mBackupMethodFieldId = mHookerTypeId.getField(methodTypeId, FIELD_NAME_BACKUP_METHOD);
|
||||
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mBackupMethodFieldId, Modifier.STATIC, null);
|
||||
}
|
||||
|
||||
private void generateSetupMethod() {
|
||||
MethodId<?, Void> setupMethodId = mHookerTypeId.getMethod(
|
||||
TypeId.VOID, METHOD_NAME_SETUP, memberTypeId, methodTypeId, hookInfoTypeId);
|
||||
Code code = mDexMaker.declare(setupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
// init logic
|
||||
// get parameters
|
||||
Local<Member> method = code.getParameter(0, memberTypeId);
|
||||
Local<Method> backupMethod = code.getParameter(1, methodTypeId);
|
||||
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.getParameter(2, hookInfoTypeId);
|
||||
// save params to static
|
||||
code.sput(mMethodFieldId, method);
|
||||
code.sput(mBackupMethodFieldId, backupMethod);
|
||||
code.sput(mHookInfoFieldId, hookInfo);
|
||||
code.returnVoid();
|
||||
}
|
||||
|
||||
private void generateBackupMethod() {
|
||||
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
|
||||
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
MethodId<?, ?> errLogMethod = TypeId.get(DexLog.class).getMethod(TypeId.get(Void.TYPE), "printCallOriginError", memberTypeId);
|
||||
|
||||
|
||||
//very very important!!!!!!!!!!!
|
||||
//add a try cache block avoid inline
|
||||
Label tryCatchBlock = new Label();
|
||||
|
||||
code.addCatchClause(throwableTypeId, tryCatchBlock);
|
||||
code.sget(mMethodFieldId, method);
|
||||
code.invokeStatic(errLogMethod, null, method);
|
||||
// start of try
|
||||
code.mark(tryCatchBlock);
|
||||
|
||||
// do nothing
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// we have limited the returnType to primitives or Object, so this should be safe
|
||||
code.returnValue(resultLocals.get(mReturnTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateBackupAndCallOriginCheckMethod() {
|
||||
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
|
||||
mSandHookCallOriginMethodId = TypeId.get(ErrorCatch.class).getMethod(TypeId.get(Object.class), "callOriginError", memberTypeId, methodTypeId, TypeId.get(Object.class), TypeId.get(Object[].class));
|
||||
MethodId<?, ?> errLogMethod = TypeId.get(DexLog.class).getMethod(TypeId.get(Void.TYPE), "printCallOriginError", methodTypeId);
|
||||
|
||||
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
Local<Method> backupMethod = code.newLocal(methodTypeId);
|
||||
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||
Local<Object> resultObj = code.newLocal(TypeId.OBJECT);
|
||||
Label tryCatchBlock = new Label();
|
||||
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
|
||||
|
||||
//very very important!!!!!!!!!!!
|
||||
//add a try cache block avoid inline
|
||||
|
||||
|
||||
// start of try
|
||||
code.addCatchClause(throwableTypeId, tryCatchBlock);
|
||||
code.sget(mMethodFieldId, method);
|
||||
code.invokeStatic(errLogMethod, null, method);
|
||||
//call origin by invoke
|
||||
code.loadConstant(args, null);
|
||||
code.loadConstant(argIndex, 0);
|
||||
code.sget(mBackupMethodFieldId, backupMethod);
|
||||
int paramsSize = mParameterTypeIds.length;
|
||||
int offset = 0;
|
||||
// thisObject
|
||||
if (mIsStatic) {
|
||||
// thisObject = null
|
||||
code.loadConstant(thisObject, null);
|
||||
} else {
|
||||
// thisObject = args[0]
|
||||
offset = 1;
|
||||
code.move(thisObject, allArgsLocals[0]);
|
||||
}
|
||||
|
||||
// offset = mIsStatic ? 0 : 1;
|
||||
// for (int i = offset; i < allArgsLocals.length; i++) {
|
||||
// code.loadConstant(argIndex, i - offset);
|
||||
// code.aget(resultObj, args, argIndex);
|
||||
// autoUnboxIfNecessary(code, allArgsLocals[i], resultObj, resultLocals, true);
|
||||
// }
|
||||
|
||||
// actual args (exclude thisObject if this is not a static method)
|
||||
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||
code.newArray(args, actualParamSize);
|
||||
for (int i = offset; i < paramsSize; i++) {
|
||||
Local parameter = allArgsLocals[i];
|
||||
// save parameter to resultObj as Object
|
||||
autoBoxIfNecessary(code, resultObj, parameter);
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
// save Object to args
|
||||
code.aput(args, argIndex, resultObj);
|
||||
}
|
||||
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mSandHookCallOriginMethodId, null, method, backupMethod, thisObject, args);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
code.invokeStatic(mSandHookCallOriginMethodId, resultObj, method, backupMethod, thisObject, args);
|
||||
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||
code.cast(matchObjLocal, resultObj);
|
||||
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
|
||||
code.returnValue(toReturn);
|
||||
}
|
||||
|
||||
code.mark(tryCatchBlock);
|
||||
// do nothing
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// we have limited the returnType to primitives or Object, so this should be safe
|
||||
code.returnValue(resultLocals.get(mReturnTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateCallBackupMethod() {
|
||||
mCallBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_CALL_BACKUP, mParameterTypeIds);
|
||||
Code code = mDexMaker.declare(mCallBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
// just call backup and return its result
|
||||
|
||||
Local localOrigin = code.newLocal(memberTypeId);
|
||||
Local localBackup = code.newLocal(methodTypeId);
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
|
||||
code.sget(mMethodFieldId, localOrigin);
|
||||
code.sget(mBackupMethodFieldId, localBackup);
|
||||
|
||||
MethodId methodId = TypeId.get(SandHook.class).getMethod(TypeId.get(Void.TYPE), "ensureBackupMethod", memberTypeId, methodTypeId);
|
||||
code.invokeStatic(methodId, null, localOrigin, localBackup);
|
||||
|
||||
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
Local result = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||
code.returnValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateHookMethod() {
|
||||
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
|
||||
mPrintLogMethodId = TypeId.get(DexLog.class).getMethod(TypeId.get(Void.TYPE), METHOD_NAME_LOG, TypeId.get(Member.class));
|
||||
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
// code starts
|
||||
|
||||
// prepare common labels
|
||||
Label noHookReturn = new Label();
|
||||
Label incrementAndCheckBefore = new Label();
|
||||
Label tryBeforeCatch = new Label();
|
||||
Label noExceptionBefore = new Label();
|
||||
Label checkAndCallBackup = new Label();
|
||||
Label beginCallBefore = new Label();
|
||||
Label beginCallAfter = new Label();
|
||||
Label tryOrigCatch = new Label();
|
||||
Label noExceptionOrig = new Label();
|
||||
Label tryAfterCatch = new Label();
|
||||
Label decrementAndCheckAfter = new Label();
|
||||
Label noBackupThrowable = new Label();
|
||||
Label throwThrowable = new Label();
|
||||
// prepare locals
|
||||
Local<Boolean> disableHooks = code.newLocal(TypeId.BOOLEAN);
|
||||
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.newLocal(hookInfoTypeId);
|
||||
Local<XposedBridge.CopyOnWriteSortedSet> callbacks = code.newLocal(callbacksTypeId);
|
||||
Local<Object[]> snapshot = code.newLocal(objArrayTypeId);
|
||||
Local<Integer> snapshotLen = code.newLocal(TypeId.INT);
|
||||
Local<Object> callbackObj = code.newLocal(TypeId.OBJECT);
|
||||
Local<XC_MethodHook> callback = code.newLocal(callbackTypeId);
|
||||
|
||||
Local<Object> resultObj = code.newLocal(TypeId.OBJECT); // as a temp Local
|
||||
Local<Integer> one = code.newLocal(TypeId.INT);
|
||||
Local<Object> nullObj = code.newLocal(TypeId.OBJECT);
|
||||
Local<Throwable> throwable = code.newLocal(throwableTypeId);
|
||||
|
||||
Local<XC_MethodHook.MethodHookParam> param = code.newLocal(paramTypeId);
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||
Local<Boolean> returnEarly = code.newLocal(TypeId.BOOLEAN);
|
||||
|
||||
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||
|
||||
Local<Integer> beforeIdx = code.newLocal(TypeId.INT);
|
||||
Local<Object> lastResult = code.newLocal(TypeId.OBJECT);
|
||||
Local<Throwable> lastThrowable = code.newLocal(throwableTypeId);
|
||||
Local<Boolean> hasThrowable = code.newLocal(TypeId.BOOLEAN);
|
||||
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
code.loadConstant(args, null);
|
||||
code.loadConstant(argIndex, 0);
|
||||
code.loadConstant(one, 1);
|
||||
code.loadConstant(snapshotLen, 0);
|
||||
code.loadConstant(nullObj, null);
|
||||
|
||||
code.sget(mMethodFieldId, method);
|
||||
//print log
|
||||
code.invokeStatic(mPrintLogMethodId, null, method);
|
||||
|
||||
// check XposedBridge.disableHooks flag
|
||||
|
||||
FieldId<XposedBridge, Boolean> disableHooksField =
|
||||
xposedBridgeTypeId.getField(TypeId.BOOLEAN, "disableHooks");
|
||||
code.sget(disableHooksField, disableHooks);
|
||||
// disableHooks == true => no hooking
|
||||
code.compareZ(Comparison.NE, noHookReturn, disableHooks);
|
||||
|
||||
// check callbacks length
|
||||
code.sget(mHookInfoFieldId, hookInfo);
|
||||
code.iget(hookInfoTypeId.getField(callbacksTypeId, "callbacks"), callbacks, hookInfo);
|
||||
code.invokeVirtual(callbacksTypeId.getMethod(objArrayTypeId, "getSnapshot"), snapshot, callbacks);
|
||||
code.arrayLength(snapshotLen, snapshot);
|
||||
// snapshotLen == 0 => no hooking
|
||||
code.compareZ(Comparison.EQ, noHookReturn, snapshotLen);
|
||||
|
||||
// start hooking
|
||||
|
||||
// prepare hooking locals
|
||||
int paramsSize = mParameterTypeIds.length;
|
||||
int offset = 0;
|
||||
// thisObject
|
||||
if (mIsStatic) {
|
||||
// thisObject = null
|
||||
code.loadConstant(thisObject, null);
|
||||
} else {
|
||||
// thisObject = args[0]
|
||||
offset = 1;
|
||||
code.move(thisObject, allArgsLocals[0]);
|
||||
}
|
||||
// actual args (exclude thisObject if this is not a static method)
|
||||
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||
code.newArray(args, actualParamSize);
|
||||
for (int i = offset; i < paramsSize; i++) {
|
||||
Local parameter = allArgsLocals[i];
|
||||
// save parameter to resultObj as Object
|
||||
autoBoxIfNecessary(code, resultObj, parameter);
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
// save Object to args
|
||||
code.aput(args, argIndex, resultObj);
|
||||
}
|
||||
// create param
|
||||
code.newInstance(param, paramTypeId.getConstructor());
|
||||
// set method, thisObject, args
|
||||
code.iput(paramTypeId.getField(memberTypeId, PARAMS_FIELD_NAME_METHOD), param, method);
|
||||
code.iput(paramTypeId.getField(TypeId.OBJECT, PARAMS_FIELD_NAME_THIS_OBJECT), param, thisObject);
|
||||
code.iput(paramTypeId.getField(objArrayTypeId, PARAMS_FIELD_NAME_ARGS), param, args);
|
||||
|
||||
// call beforeCallbacks
|
||||
code.loadConstant(beforeIdx, 0);
|
||||
|
||||
code.mark(beginCallBefore);
|
||||
// start of try
|
||||
code.addCatchClause(throwableTypeId, tryBeforeCatch);
|
||||
|
||||
code.aget(callbackObj, snapshot, beforeIdx);
|
||||
code.cast(callback, callbackObj);
|
||||
code.invokeVirtual(callBeforeCallbackMethodId, null, callback, param);
|
||||
code.jump(noExceptionBefore);
|
||||
|
||||
// end of try
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
|
||||
// start of catch
|
||||
code.mark(tryBeforeCatch);
|
||||
code.moveException(throwable);
|
||||
code.invokeStatic(logThrowableMethodId, null, throwable);
|
||||
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||
code.loadConstant(returnEarly, false);
|
||||
code.iput(returnEarlyFieldId, param, returnEarly);
|
||||
code.jump(incrementAndCheckBefore);
|
||||
|
||||
// no exception when calling beforeCallbacks
|
||||
code.mark(noExceptionBefore);
|
||||
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||
// if returnEarly == false, continue
|
||||
code.compareZ(Comparison.EQ, incrementAndCheckBefore, returnEarly);
|
||||
// returnEarly == true, break
|
||||
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||
code.jump(checkAndCallBackup);
|
||||
|
||||
// increment and check to continue
|
||||
code.mark(incrementAndCheckBefore);
|
||||
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||
code.compare(Comparison.LT, beginCallBefore, beforeIdx, snapshotLen);
|
||||
|
||||
// check and call backup
|
||||
code.mark(checkAndCallBackup);
|
||||
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||
// if returnEarly == true, go to call afterCallbacks directly
|
||||
code.compareZ(Comparison.NE, noExceptionOrig, returnEarly);
|
||||
// try to call backup
|
||||
// try start
|
||||
code.addCatchClause(throwableTypeId, tryOrigCatch);
|
||||
// we have to load args[] to paramLocals
|
||||
// because args[] may be changed in beforeHookedMethod
|
||||
// should consider first param is thisObj if hooked method is not static
|
||||
offset = mIsStatic ? 0 : 1;
|
||||
for (int i = offset; i < allArgsLocals.length; i++) {
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
code.aget(resultObj, args, argIndex);
|
||||
autoUnboxIfNecessary(code, allArgsLocals[i], resultObj, resultLocals, true);
|
||||
}
|
||||
// get pre-created Local with a matching typeId
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
// TODO maybe keep preset result to do some magic?
|
||||
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||
} else {
|
||||
Local returnedResult = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, returnedResult, allArgsLocals);
|
||||
// save returnedResult to resultObj as a Object
|
||||
autoBoxIfNecessary(code, resultObj, returnedResult);
|
||||
// save resultObj to param
|
||||
code.invokeVirtual(setResultMethodId, null, param, resultObj);
|
||||
}
|
||||
// go to call afterCallbacks
|
||||
code.jump(noExceptionOrig);
|
||||
// try end
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
// catch
|
||||
code.mark(tryOrigCatch);
|
||||
code.moveException(throwable);
|
||||
// exception occurred when calling backup, save throwable to param
|
||||
code.invokeVirtual(setThrowableMethodId, null, param, throwable);
|
||||
|
||||
code.mark(noExceptionOrig);
|
||||
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||
|
||||
// call afterCallbacks
|
||||
code.mark(beginCallAfter);
|
||||
// save results of backup calling
|
||||
code.invokeVirtual(getResultMethodId, lastResult, param);
|
||||
code.invokeVirtual(getThrowableMethodId, lastThrowable, param);
|
||||
// try start
|
||||
code.addCatchClause(throwableTypeId, tryAfterCatch);
|
||||
code.aget(callbackObj, snapshot, beforeIdx);
|
||||
code.cast(callback, callbackObj);
|
||||
code.invokeVirtual(callAfterCallbackMethodId, null, callback, param);
|
||||
// all good, just continue
|
||||
code.jump(decrementAndCheckAfter);
|
||||
// try end
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
// catch
|
||||
code.mark(tryAfterCatch);
|
||||
code.moveException(throwable);
|
||||
code.invokeStatic(logThrowableMethodId, null, throwable);
|
||||
// if lastThrowable == null, go to recover lastResult
|
||||
code.compareZ(Comparison.EQ, noBackupThrowable, lastThrowable);
|
||||
// lastThrowable != null, recover lastThrowable
|
||||
code.invokeVirtual(setThrowableMethodId, null, param, lastThrowable);
|
||||
// continue
|
||||
code.jump(decrementAndCheckAfter);
|
||||
code.mark(noBackupThrowable);
|
||||
// recover lastResult and continue
|
||||
code.invokeVirtual(setResultMethodId, null, param, lastResult);
|
||||
// decrement and check continue
|
||||
code.mark(decrementAndCheckAfter);
|
||||
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||
code.compareZ(Comparison.GE, beginCallAfter, beforeIdx);
|
||||
|
||||
// callbacks end
|
||||
// return
|
||||
code.invokeVirtual(hasThrowableMethodId, hasThrowable, param);
|
||||
// if hasThrowable, throw the throwable and return
|
||||
code.compareZ(Comparison.NE, throwThrowable, hasThrowable);
|
||||
// return getResult
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// getResult always return an Object, so save to resultObj
|
||||
code.invokeVirtual(getResultMethodId, resultObj, param);
|
||||
// have to unbox it if returnType is primitive
|
||||
// casting Object
|
||||
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||
code.cast(matchObjLocal, resultObj);
|
||||
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
|
||||
// return
|
||||
code.returnValue(toReturn);
|
||||
}
|
||||
// throw throwable
|
||||
code.mark(throwThrowable);
|
||||
code.invokeVirtual(getThrowableMethodId, throwable, param);
|
||||
code.throwValue(throwable);
|
||||
|
||||
// call backup and return
|
||||
code.mark(noHookReturn);
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
Local result = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||
code.returnValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
private Local[] createParameterLocals(Code code) {
|
||||
Local[] paramLocals = new Local[mParameterTypeIds.length];
|
||||
for (int i = 0; i < mParameterTypeIds.length; i++) {
|
||||
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
|
||||
}
|
||||
return paramLocals;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
import com.swift.sandhook.xposedcompat.hookstub.HookStubManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Map;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.FieldId;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.MethodId;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.MD5;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoBoxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoUnboxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.createResultLocals;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getObjTypeIdIfPrimitive;
|
||||
|
||||
public class HookerDexMakerNew implements HookMaker {
|
||||
|
||||
public static final String METHOD_NAME_BACKUP = "backup";
|
||||
public static final String METHOD_NAME_HOOK = "hook";
|
||||
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
|
||||
private static final String CLASS_DESC_PREFIX = "L";
|
||||
private static final String CLASS_NAME_PREFIX = "SandHookerN";
|
||||
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
|
||||
private static final String FIELD_NAME_METHOD = "method";
|
||||
private static final String FIELD_NAME_BACKUP_METHOD = "backupMethod";
|
||||
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
|
||||
private static final TypeId<Method> methodTypeId = TypeId.get(Method.class);
|
||||
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
|
||||
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
|
||||
|
||||
|
||||
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
|
||||
private FieldId<?, Member> mMethodFieldId;
|
||||
private FieldId<?, Method> mBackupMethodFieldId;
|
||||
private MethodId<?, ?> mHookMethodId;
|
||||
private MethodId<?, ?> mBackupMethodId;
|
||||
private MethodId<?, ?> mSandHookBridgeMethodId;
|
||||
|
||||
private TypeId<?> mHookerTypeId;
|
||||
private TypeId<?>[] mParameterTypeIds;
|
||||
private Class<?>[] mActualParameterTypes;
|
||||
private Class<?> mReturnType;
|
||||
private TypeId<?> mReturnTypeId;
|
||||
private boolean mIsStatic;
|
||||
// TODO use this to generate methods
|
||||
private boolean mHasThrowable;
|
||||
|
||||
private DexMaker mDexMaker;
|
||||
private Member mMember;
|
||||
private XposedBridge.AdditionalHookInfo mHookInfo;
|
||||
private ClassLoader mAppClassLoader;
|
||||
private Class<?> mHookClass;
|
||||
private Method mHookMethod;
|
||||
private Method mBackupMethod;
|
||||
private String mDexDirPath;
|
||||
|
||||
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
|
||||
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
|
||||
int offset = 0;
|
||||
if (!isStatic) {
|
||||
parameterTypeIds[0] = TypeId.OBJECT;
|
||||
offset = 1;
|
||||
}
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
|
||||
}
|
||||
return parameterTypeIds;
|
||||
}
|
||||
|
||||
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
if (isStatic) {
|
||||
return parameterTypes;
|
||||
}
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = parameterSize + 1;
|
||||
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
|
||||
int offset = 1;
|
||||
newParameterTypes[0] = Object.class;
|
||||
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
|
||||
return newParameterTypes;
|
||||
}
|
||||
|
||||
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||
ClassLoader appClassLoader, String dexDirPath) throws Exception {
|
||||
if (member instanceof Method) {
|
||||
Method method = (Method) member;
|
||||
mIsStatic = Modifier.isStatic(method.getModifiers());
|
||||
mReturnType = method.getReturnType();
|
||||
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|
||||
|| mReturnType.isPrimitive()) {
|
||||
mReturnTypeId = TypeId.get(mReturnType);
|
||||
} else {
|
||||
// all others fallback to plain Object for convenience
|
||||
mReturnType = Object.class;
|
||||
mReturnTypeId = TypeId.OBJECT;
|
||||
}
|
||||
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = method.getExceptionTypes().length > 0;
|
||||
} else if (member instanceof Constructor) {
|
||||
Constructor constructor = (Constructor) member;
|
||||
mIsStatic = false;
|
||||
mReturnType = void.class;
|
||||
mReturnTypeId = TypeId.VOID;
|
||||
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = constructor.getExceptionTypes().length > 0;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
|
||||
}
|
||||
mMember = member;
|
||||
mHookInfo = hookInfo;
|
||||
mDexDirPath = dexDirPath;
|
||||
if (appClassLoader == null
|
||||
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
|
||||
mAppClassLoader = this.getClass().getClassLoader();
|
||||
} else {
|
||||
mAppClassLoader = appClassLoader;
|
||||
}
|
||||
|
||||
mDexMaker = new DexMaker();
|
||||
// Generate a Hooker class.
|
||||
String className = getClassName(mMember);
|
||||
String dexName = className + ".jar";
|
||||
|
||||
HookWrapper.HookEntity hookEntity = null;
|
||||
//try load cache first
|
||||
try {
|
||||
ClassLoader loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath), dexName);
|
||||
if (loader != null) {
|
||||
hookEntity = loadHookerClass(loader, className);
|
||||
}
|
||||
} catch (Throwable throwable) {}
|
||||
|
||||
//do generate
|
||||
if (hookEntity == null) {
|
||||
hookEntity = doMake(className, dexName);
|
||||
}
|
||||
SandHook.hook(hookEntity);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity doMake(String className, String dexName) throws Exception {
|
||||
mHookerTypeId = TypeId.get(CLASS_DESC_PREFIX + className + ";");
|
||||
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
|
||||
generateFields();
|
||||
generateHookMethod();
|
||||
generateBackupMethod();
|
||||
|
||||
ClassLoader loader;
|
||||
if (TextUtils.isEmpty(mDexDirPath)) {
|
||||
throw new IllegalArgumentException("dexDirPath should not be empty!!!");
|
||||
}
|
||||
// Create the dex file and load it.
|
||||
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath), dexName);
|
||||
return loadHookerClass(loader, className);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity loadHookerClass(ClassLoader loader, String className) throws Exception {
|
||||
mHookClass = loader.loadClass(className);
|
||||
// Execute our newly-generated code in-process.
|
||||
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
|
||||
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP);
|
||||
setup(mHookClass);
|
||||
return new HookWrapper.HookEntity(mMember, mHookMethod, mBackupMethod, false);
|
||||
}
|
||||
|
||||
private void setup(Class mHookClass) {
|
||||
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_METHOD, mMember);
|
||||
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_BACKUP_METHOD, mBackupMethod);
|
||||
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_HOOK_INFO, mHookInfo);
|
||||
}
|
||||
|
||||
private String getClassName(Member originMethod) {
|
||||
return CLASS_NAME_PREFIX + "_" + MD5(originMethod.toString());
|
||||
}
|
||||
|
||||
public Method getHookMethod() {
|
||||
return mHookMethod;
|
||||
}
|
||||
|
||||
public Method getBackupMethod() {
|
||||
return mBackupMethod;
|
||||
}
|
||||
|
||||
public Method getCallBackupMethod() {
|
||||
return mBackupMethod;
|
||||
}
|
||||
|
||||
public Class getHookClass() {
|
||||
return mHookClass;
|
||||
}
|
||||
|
||||
private void generateFields() {
|
||||
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
|
||||
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
|
||||
mBackupMethodFieldId = mHookerTypeId.getField(methodTypeId, FIELD_NAME_BACKUP_METHOD);
|
||||
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mBackupMethodFieldId, Modifier.STATIC, null);
|
||||
}
|
||||
|
||||
private void generateBackupMethod() {
|
||||
mBackupMethodId = mHookerTypeId.getMethod(TypeId.VOID, METHOD_NAME_BACKUP);
|
||||
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
code.returnVoid();
|
||||
}
|
||||
|
||||
private void generateHookMethod() {
|
||||
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
|
||||
mSandHookBridgeMethodId = TypeId.get(HookStubManager.class).getMethod(TypeId.get(Object.class), "hookBridge", memberTypeId, TypeId.get(Object.class), TypeId.get(Object[].class));
|
||||
|
||||
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
// Local<Method> backupMethod = code.newLocal(methodTypeId);
|
||||
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||
Local<Object> resultObj = code.newLocal(TypeId.OBJECT);
|
||||
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
|
||||
code.sget(mMethodFieldId, method);
|
||||
code.loadConstant(args, null);
|
||||
code.loadConstant(argIndex, 0);
|
||||
// code.sget(mBackupMethodFieldId, backupMethod);
|
||||
int paramsSize = mParameterTypeIds.length;
|
||||
int offset = 0;
|
||||
// thisObject
|
||||
if (mIsStatic) {
|
||||
// thisObject = null
|
||||
code.loadConstant(thisObject, null);
|
||||
} else {
|
||||
// thisObject = args[0]
|
||||
offset = 1;
|
||||
code.move(thisObject, allArgsLocals[0]);
|
||||
}
|
||||
|
||||
// actual args (exclude thisObject if this is not a static method)
|
||||
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||
code.newArray(args, actualParamSize);
|
||||
for (int i = offset; i < paramsSize; i++) {
|
||||
Local parameter = allArgsLocals[i];
|
||||
// save parameter to resultObj as Object
|
||||
autoBoxIfNecessary(code, resultObj, parameter);
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
// save Object to args
|
||||
code.aput(args, argIndex, resultObj);
|
||||
}
|
||||
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mSandHookBridgeMethodId, null, method, thisObject, args);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
code.invokeStatic(mSandHookBridgeMethodId, resultObj, method, thisObject, args);
|
||||
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||
code.cast(matchObjLocal, resultObj);
|
||||
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
|
||||
code.returnValue(toReturn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Local[] createParameterLocals(Code code) {
|
||||
Local[] paramLocals = new Local[mParameterTypeIds.length];
|
||||
for (int i = 0; i < mParameterTypeIds.length; i++) {
|
||||
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
|
||||
}
|
||||
return paramLocals;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import android.os.Process;
|
||||
import android.os.Trace;
|
||||
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.SandHookConfig;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
import com.swift.sandhook.xposedcompat.hookstub.HookMethodEntity;
|
||||
import com.swift.sandhook.xposedcompat.hookstub.HookStubManager;
|
||||
import com.swift.sandhook.xposedcompat.utils.DexLog;
|
||||
import com.swift.sandhook.xposedcompat.utils.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getPackageName;
|
||||
import static com.elderdrivers.riru.edxp.util.ProcessUtils.getCurrentProcessName;
|
||||
|
||||
public final class SandHookXposedBridge {
|
||||
|
||||
private static final HashMap<Member, Method> hookedInfo = new HashMap<>();
|
||||
private static HookMaker hookMaker = XposedCompat.useNewDexMaker ? new HookerDexMakerNew() : new HookerDexMaker();
|
||||
private static final AtomicBoolean dexPathInited = new AtomicBoolean(false);
|
||||
private static File dexDir;
|
||||
|
||||
public static Map<Member, HookMethodEntity> entityMap = new HashMap<>();
|
||||
|
||||
public static void onForkPost() {
|
||||
dexPathInited.set(false);
|
||||
}
|
||||
|
||||
public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||
|
||||
if (!checkMember(hookMethod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hookedInfo.containsKey(hookMethod) || entityMap.containsKey(hookMethod)) {
|
||||
DexLog.w("already hook method:" + hookMethod.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setupDexCachePath();
|
||||
Trace.beginSection("SandHook-Xposed");
|
||||
long timeStart = System.currentTimeMillis();
|
||||
HookMethodEntity stub = null;
|
||||
if (XposedCompat.useInternalStub) {
|
||||
stub = HookStubManager.getHookMethodEntity(hookMethod);
|
||||
}
|
||||
if (stub != null) {
|
||||
SandHook.hook(new HookWrapper.HookEntity(hookMethod, stub.hook, stub.backup, false));
|
||||
entityMap.put(hookMethod, stub);
|
||||
} else {
|
||||
hookMaker.start(hookMethod, additionalHookInfo,
|
||||
null, dexDir == null ? null : dexDir.getAbsolutePath());
|
||||
hookedInfo.put(hookMethod, hookMaker.getCallBackupMethod());
|
||||
}
|
||||
DexLog.d("hook method <" + hookMethod.toString() + "> cost " + (System.currentTimeMillis() - timeStart) + " ms, by " + (stub != null ? "internal stub." : "dex maker"));
|
||||
Trace.endSection();
|
||||
} catch (Exception e) {
|
||||
DexLog.e("error occur when hook method <" + hookMethod.toString() + ">", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupDexCachePath() {
|
||||
// using file based DexClassLoader
|
||||
if (!dexPathInited.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// we always choose to use device encrypted storage data on android N and later
|
||||
// in case some app is installing hooks before phone is unlocked
|
||||
String fixedAppDataDir = getDataPathPrefix() + getPackageName(Main.appDataDir) + "/";
|
||||
dexDir = new File(fixedAppDataDir, "/cache/sandxposed/"
|
||||
+ getCurrentProcessName(Main.appProcessName).replace(":", "_") + "/");
|
||||
dexDir.mkdirs();
|
||||
} catch (Throwable throwable) {
|
||||
com.elderdrivers.riru.edxp.sandhook.dexmaker.DexLog.e("error when init dex path", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
// public static void clearOatFile() {
|
||||
// String fixedAppDataDir = XposedCompat.cacheDir.getAbsolutePath();
|
||||
// File dexOatDir = new File(fixedAppDataDir, "/sandxposed/oat/");
|
||||
// if (!dexOatDir.exists())
|
||||
// return;
|
||||
// try {
|
||||
// FileUtils.delete(dexOatDir);
|
||||
// dexOatDir.mkdirs();
|
||||
// } catch (Throwable throwable) {
|
||||
// }
|
||||
// }
|
||||
|
||||
private static boolean checkMember(Member member) {
|
||||
|
||||
if (member instanceof Method) {
|
||||
return true;
|
||||
} else if (member instanceof Constructor<?>) {
|
||||
return true;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
DexLog.e("Cannot hook interfaces: " + member.toString());
|
||||
return false;
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
DexLog.e("Cannot hook abstract methods: " + member.toString());
|
||||
return false;
|
||||
} else {
|
||||
DexLog.e("Only methods and constructors can be hooked: " + member.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
||||
throws Throwable {
|
||||
Method callBackup = hookedInfo.get(method);
|
||||
if (callBackup == null) {
|
||||
//method hook use internal stub
|
||||
return SandHook.callOriginMethod(method, thisObject, args);
|
||||
}
|
||||
if (!Modifier.isStatic(callBackup.getModifiers())) {
|
||||
throw new IllegalStateException("original method is not static, something must be wrong!");
|
||||
}
|
||||
callBackup.setAccessible(true);
|
||||
if (args == null) {
|
||||
args = new Object[0];
|
||||
}
|
||||
final int argsSize = args.length;
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
return callBackup.invoke(null, args);
|
||||
} else {
|
||||
Object[] newArgs = new Object[argsSize + 1];
|
||||
newArgs[0] = thisObject;
|
||||
for (int i = 1; i < newArgs.length; i++) {
|
||||
newArgs[i] = args[i - 1];
|
||||
}
|
||||
return callBackup.invoke(null, newArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLibPath() {
|
||||
if (Process.is64Bit()) {
|
||||
SandHookConfig.libSandHookPath = "/system/lib64/libsandhook.edxp.so";
|
||||
} else {
|
||||
SandHookConfig.libSandHookPath = "/system/lib/libsandhook.edxp.so";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ApplicationUtils {
|
||||
|
||||
private static Class classActivityThread;
|
||||
private static Method currentApplicationMethod;
|
||||
|
||||
static Application application;
|
||||
|
||||
public static Application currentApplication() {
|
||||
if (application != null)
|
||||
return application;
|
||||
if (currentApplicationMethod == null) {
|
||||
try {
|
||||
classActivityThread = Class.forName("android.app.ActivityThread");
|
||||
currentApplicationMethod = classActivityThread.getDeclaredMethod("currentApplication");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (currentApplicationMethod == null)
|
||||
return null;
|
||||
try {
|
||||
application = (Application) currentApplicationMethod.invoke(null);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return application;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.elderdrivers.riru.edxp.sandhook.BuildConfig;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
|
||||
public class ClassLoaderUtils {
|
||||
|
||||
public static final String DEXPATH = "/system/framework/edxposed.dex:/system/framework/eddalvikdx.dex:/system/framework/eddexmaker.dex";
|
||||
|
||||
public static void replaceParentClassLoader(ClassLoader appClassLoader) {
|
||||
if (appClassLoader == null) {
|
||||
DexLog.e("appClassLoader is null, you might be kidding me?");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ClassLoader curCL = ClassLoaderUtils.class.getClassLoader();
|
||||
ClassLoader parent = appClassLoader;
|
||||
ClassLoader lastChild = appClassLoader;
|
||||
while (parent != null) {
|
||||
ClassLoader tmp = parent.getParent();
|
||||
if (tmp == curCL) {
|
||||
DexLog.d("replacing has been done before, skip.");
|
||||
return;
|
||||
}
|
||||
if (tmp == null) {
|
||||
DexLog.d("before replacing =========================================>");
|
||||
dumpClassLoaders(appClassLoader);
|
||||
Field parentField = ClassLoader.class.getDeclaredField("parent");
|
||||
parentField.setAccessible(true);
|
||||
parentField.set(curCL, parent);
|
||||
parentField.set(lastChild, curCL);
|
||||
DexLog.d("after replacing ==========================================>");
|
||||
dumpClassLoaders(appClassLoader);
|
||||
}
|
||||
lastChild = parent;
|
||||
parent = tmp;
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
DexLog.e("error when replacing class loader.", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpClassLoaders(ClassLoader classLoader) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
while (classLoader != null) {
|
||||
DexLog.d(classLoader + " =>");
|
||||
classLoader = classLoader.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ClassLoader> getAppClassLoader() {
|
||||
List<ClassLoader> cacheLoaders = new ArrayList<>(0);
|
||||
try {
|
||||
DexLog.d("start getting app classloader");
|
||||
Class appLoadersClass = Class.forName("android.app.ApplicationLoaders");
|
||||
Field loadersField = appLoadersClass.getDeclaredField("gApplicationLoaders");
|
||||
loadersField.setAccessible(true);
|
||||
Object loaders = loadersField.get(null);
|
||||
Field mLoaderMapField = loaders.getClass().getDeclaredField("mLoaders");
|
||||
mLoaderMapField.setAccessible(true);
|
||||
ArrayMap<String, ClassLoader> mLoaderMap = (ArrayMap<String, ClassLoader>) mLoaderMapField.get(loaders);
|
||||
DexLog.d("mLoaders size = " + mLoaderMap.size());
|
||||
cacheLoaders = new ArrayList<>(mLoaderMap.values());
|
||||
} catch (Exception ex) {
|
||||
DexLog.e("error get app class loader.", ex);
|
||||
}
|
||||
return cacheLoaders;
|
||||
}
|
||||
|
||||
private static HashSet<ClassLoader> classLoaders = new HashSet<>();
|
||||
|
||||
public static boolean addPathToClassLoader(ClassLoader classLoader) {
|
||||
if (!(classLoader instanceof PathClassLoader)) {
|
||||
DexLog.w(classLoader + " is not a BaseDexClassLoader!!!");
|
||||
return false;
|
||||
}
|
||||
if (classLoaders.contains(classLoader)) {
|
||||
DexLog.d(classLoader + " has been hooked before");
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
//baseDexClassLoader.addDexPath(DEXPATH);
|
||||
} else {
|
||||
DexUtils.injectDexAtFirst(DEXPATH, baseDexClassLoader);
|
||||
}
|
||||
classLoaders.add(classLoader);
|
||||
return true;
|
||||
} catch (Throwable throwable) {
|
||||
DexLog.e("error when addPath to ClassLoader: " + classLoader, throwable);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
|
||||
public class DexLog {
|
||||
|
||||
public static final String TAG = "SandXposed-dexmaker";
|
||||
|
||||
public static boolean DEBUG = true;
|
||||
|
||||
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) {
|
||||
return Log.d(TAG, s);
|
||||
}
|
||||
|
||||
public static void printMethodHookIn(Member member) {
|
||||
if (DEBUG && member != null) {
|
||||
Log.d("SandHook-Xposed", "method <" + member.toString() + "> hook in");
|
||||
}
|
||||
}
|
||||
|
||||
public static void printCallOriginError(Member member) {
|
||||
if (member != null) {
|
||||
Log.e("SandHook-Xposed", "method <" + member.toString() + "> call origin error!");
|
||||
}
|
||||
}
|
||||
|
||||
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,224 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
public class DexMakerUtils {
|
||||
|
||||
|
||||
private static volatile Method addInstMethod, specMethod;
|
||||
|
||||
public static void autoBoxIfNecessary(Code code, Local<Object> target, Local source) {
|
||||
String boxMethod = "valueOf";
|
||||
TypeId<?> boxTypeId;
|
||||
TypeId typeId = source.getType();
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
boxTypeId = TypeId.get(Boolean.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BOOLEAN), target, source);
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
boxTypeId = TypeId.get(Byte.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BYTE), target, source);
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
boxTypeId = TypeId.get(Character.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.CHAR), target, source);
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
boxTypeId = TypeId.get(Double.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.DOUBLE), target, source);
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
boxTypeId = TypeId.get(Float.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.FLOAT), target, source);
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
boxTypeId = TypeId.get(Integer.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.INT), target, source);
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
boxTypeId = TypeId.get(Long.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.LONG), target, source);
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
boxTypeId = TypeId.get(Short.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.SHORT), target, source);
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
code.loadConstant(target, null);
|
||||
} else {
|
||||
code.move(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
public static void autoUnboxIfNecessary(Code code, Local target, Local source,
|
||||
Map<TypeId, Local> tmpLocals, boolean castObj) {
|
||||
String unboxMethod;
|
||||
TypeId typeId = target.getType();
|
||||
TypeId<?> boxTypeId;
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
unboxMethod = "booleanValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Boolean;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.BOOLEAN, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
unboxMethod = "byteValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Byte;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.BYTE, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
unboxMethod = "charValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Character;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.CHAR, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
unboxMethod = "doubleValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Double;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.DOUBLE, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
unboxMethod = "floatValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Float;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.FLOAT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
unboxMethod = "intValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Integer;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.INT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
unboxMethod = "longValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Long;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.LONG, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
unboxMethod = "shortValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Short;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.SHORT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
code.loadConstant(target, null);
|
||||
} else if (castObj) {
|
||||
code.cast(target, source);
|
||||
} else {
|
||||
code.move(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<TypeId, Local> createResultLocals(Code code) {
|
||||
HashMap<TypeId, Local> resultMap = new HashMap<>();
|
||||
Local<Boolean> booleanLocal = code.newLocal(TypeId.BOOLEAN);
|
||||
Local<Byte> byteLocal = code.newLocal(TypeId.BYTE);
|
||||
Local<Character> charLocal = code.newLocal(TypeId.CHAR);
|
||||
Local<Double> doubleLocal = code.newLocal(TypeId.DOUBLE);
|
||||
Local<Float> floatLocal = code.newLocal(TypeId.FLOAT);
|
||||
Local<Integer> intLocal = code.newLocal(TypeId.INT);
|
||||
Local<Long> longLocal = code.newLocal(TypeId.LONG);
|
||||
Local<Short> shortLocal = code.newLocal(TypeId.SHORT);
|
||||
Local<Void> voidLocal = code.newLocal(TypeId.VOID);
|
||||
Local<Object> objectLocal = code.newLocal(TypeId.OBJECT);
|
||||
|
||||
Local<Object> booleanObjLocal = code.newLocal(TypeId.get("Ljava/lang/Boolean;"));
|
||||
Local<Object> byteObjLocal = code.newLocal(TypeId.get("Ljava/lang/Byte;"));
|
||||
Local<Object> charObjLocal = code.newLocal(TypeId.get("Ljava/lang/Character;"));
|
||||
Local<Object> doubleObjLocal = code.newLocal(TypeId.get("Ljava/lang/Double;"));
|
||||
Local<Object> floatObjLocal = code.newLocal(TypeId.get("Ljava/lang/Float;"));
|
||||
Local<Object> intObjLocal = code.newLocal(TypeId.get("Ljava/lang/Integer;"));
|
||||
Local<Object> longObjLocal = code.newLocal(TypeId.get("Ljava/lang/Long;"));
|
||||
Local<Object> shortObjLocal = code.newLocal(TypeId.get("Ljava/lang/Short;"));
|
||||
Local<Object> voidObjLocal = code.newLocal(TypeId.get("Ljava/lang/Void;"));
|
||||
|
||||
// backup need initialized locals
|
||||
code.loadConstant(booleanLocal, false);
|
||||
code.loadConstant(byteLocal, (byte) 0);
|
||||
code.loadConstant(charLocal, '\0');
|
||||
code.loadConstant(doubleLocal,0.0);
|
||||
code.loadConstant(floatLocal,0.0f);
|
||||
code.loadConstant(intLocal, 0);
|
||||
code.loadConstant(longLocal, 0L);
|
||||
code.loadConstant(shortLocal, (short) 0);
|
||||
code.loadConstant(voidLocal, null);
|
||||
code.loadConstant(objectLocal, null);
|
||||
// all to null
|
||||
code.loadConstant(booleanObjLocal, null);
|
||||
code.loadConstant(byteObjLocal, null);
|
||||
code.loadConstant(charObjLocal, null);
|
||||
code.loadConstant(doubleObjLocal, null);
|
||||
code.loadConstant(floatObjLocal, null);
|
||||
code.loadConstant(intObjLocal, null);
|
||||
code.loadConstant(longObjLocal, null);
|
||||
code.loadConstant(shortObjLocal, null);
|
||||
code.loadConstant(voidObjLocal, null);
|
||||
// package all
|
||||
resultMap.put(TypeId.BOOLEAN, booleanLocal);
|
||||
resultMap.put(TypeId.BYTE, byteLocal);
|
||||
resultMap.put(TypeId.CHAR, charLocal);
|
||||
resultMap.put(TypeId.DOUBLE, doubleLocal);
|
||||
resultMap.put(TypeId.FLOAT, floatLocal);
|
||||
resultMap.put(TypeId.INT, intLocal);
|
||||
resultMap.put(TypeId.LONG, longLocal);
|
||||
resultMap.put(TypeId.SHORT, shortLocal);
|
||||
resultMap.put(TypeId.VOID, voidLocal);
|
||||
resultMap.put(TypeId.OBJECT, objectLocal);
|
||||
|
||||
resultMap.put(TypeId.get("Ljava/lang/Boolean;"), booleanObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Byte;"), byteObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Character;"), charObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Double;"), doubleObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Float;"), floatObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Integer;"), intObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Long;"), longObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Short;"), shortObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Void;"), voidObjLocal);
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
public static TypeId getObjTypeIdIfPrimitive(TypeId typeId) {
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
return TypeId.get("Ljava/lang/Boolean;");
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
return TypeId.get("Ljava/lang/Byte;");
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
return TypeId.get("Ljava/lang/Character;");
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
return TypeId.get("Ljava/lang/Double;");
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
return TypeId.get("Ljava/lang/Float;");
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
return TypeId.get("Ljava/lang/Integer;");
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
return TypeId.get("Ljava/lang/Long;");
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
return TypeId.get("Ljava/lang/Short;");
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
return TypeId.get("Ljava/lang/Void;");
|
||||
} else {
|
||||
return typeId;
|
||||
}
|
||||
}
|
||||
|
||||
public static void returnRightValue(Code code, Class<?> returnType, Map<Class, Local> resultLocals) {
|
||||
String unboxMethod;
|
||||
TypeId<?> boxTypeId;
|
||||
code.returnValue(resultLocals.get(returnType));
|
||||
}
|
||||
|
||||
public static String MD5(String source) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
messageDigest.update(source.getBytes());
|
||||
return new BigInteger(1, messageDigest.digest()).toString(32);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import dalvik.system.BaseDexClassLoader;
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
/**
|
||||
* For 6.0 only.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public class DexUtils {
|
||||
|
||||
public static void injectDexAtFirst(String dexPath, BaseDexClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, null, dexPath, classLoader);
|
||||
Object baseDexElements = getDexElements(getPathList(classLoader));
|
||||
Object newDexElements = getDexElements(getPathList(dexClassLoader));
|
||||
Object allDexElements = combineArray(newDexElements, baseDexElements);
|
||||
Object pathList = getPathList(classLoader);
|
||||
setField(pathList, pathList.getClass(), "dexElements", allDexElements);
|
||||
}
|
||||
|
||||
private static Object getDexElements(Object paramObject)
|
||||
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
|
||||
return getField(paramObject, paramObject.getClass(), "dexElements");
|
||||
}
|
||||
|
||||
private static Object getPathList(Object baseDexClassLoader)
|
||||
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
|
||||
}
|
||||
|
||||
private static Object combineArray(Object firstArray, Object secondArray) {
|
||||
Class<?> localClass = firstArray.getClass().getComponentType();
|
||||
int firstArrayLength = Array.getLength(firstArray);
|
||||
int allLength = firstArrayLength + Array.getLength(secondArray);
|
||||
Object result = Array.newInstance(localClass, allLength);
|
||||
for (int k = 0; k < allLength; ++k) {
|
||||
if (k < firstArrayLength) {
|
||||
Array.set(result, k, Array.get(firstArray, k));
|
||||
} else {
|
||||
Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Object getField(Object obj, Class<?> cl, String field)
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||
Field localField = cl.getDeclaredField(field);
|
||||
localField.setAccessible(true);
|
||||
return localField.get(obj);
|
||||
}
|
||||
|
||||
public static void setField(Object obj, Class<?> cl, String field, Object value)
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||
Field localField = cl.getDeclaredField(field);
|
||||
localField.setAccessible(true);
|
||||
localField.set(obj, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
public static final boolean IS_USING_PROTECTED_STORAGE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||
|
||||
/**
|
||||
* Delete a file or a directory and its children.
|
||||
*
|
||||
* @param file The directory to delete.
|
||||
* @throws IOException Exception when problem occurs during deleting the directory.
|
||||
*/
|
||||
public static void delete(File file) throws IOException {
|
||||
|
||||
for (File childFile : file.listFiles()) {
|
||||
|
||||
if (childFile.isDirectory()) {
|
||||
delete(childFile);
|
||||
} else {
|
||||
if (!childFile.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
public static String readLine(File file) {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
return reader.readLine();
|
||||
} catch (Throwable throwable) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeLine(File file, String line) {
|
||||
try {
|
||||
file.createNewFile();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
writer.write(line);
|
||||
writer.flush();
|
||||
} catch (Throwable throwable) {
|
||||
DexLog.e("error writing line to file " + file + ": " + throwable.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPackageName(String dataDir) {
|
||||
if (TextUtils.isEmpty(dataDir)) {
|
||||
DexLog.e("getPackageName using empty dataDir");
|
||||
return "";
|
||||
}
|
||||
int lastIndex = dataDir.lastIndexOf("/");
|
||||
if (lastIndex < 0) {
|
||||
return dataDir;
|
||||
}
|
||||
return dataDir.substring(lastIndex + 1);
|
||||
}
|
||||
|
||||
public static String getDataPathPrefix() {
|
||||
return IS_USING_PROTECTED_STORAGE ? "/data/user_de/0/" : "/data/data/";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by swift_gan on 2017/11/23.
|
||||
*/
|
||||
|
||||
public class ProcessUtils {
|
||||
|
||||
private static volatile String processName = null;
|
||||
|
||||
public static String getProcessName(Context context) {
|
||||
if (!TextUtils.isEmpty(processName))
|
||||
return processName;
|
||||
processName = doGetProcessName(context);
|
||||
return processName;
|
||||
}
|
||||
|
||||
private static String doGetProcessName(Context context) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
|
||||
if (runningApps == null) {
|
||||
return null;
|
||||
}
|
||||
for (ActivityManager.RunningAppProcessInfo proInfo : runningApps) {
|
||||
if (proInfo.pid == android.os.Process.myPid()) {
|
||||
if (proInfo.processName != null) {
|
||||
return proInfo.processName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return context.getPackageName();
|
||||
}
|
||||
|
||||
public static boolean isMainProcess(Context context) {
|
||||
String processName = getProcessName(context);
|
||||
String pkgName = context.getPackageName();
|
||||
if (!TextUtils.isEmpty(processName) && !TextUtils.equals(processName, pkgName)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) {
|
||||
final PackageManager packageManager = context.getPackageManager();
|
||||
|
||||
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
mainIntent.setPackage(packageName);
|
||||
|
||||
final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
|
||||
return apps != null ? apps : new ArrayList<ResolveInfo>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.elderdrivers.riru.edxp.yahfa;
|
||||
package com.elderdrivers.riru.edxp;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
|
|
@ -2,7 +2,7 @@ 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.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.yahfa.entry.hooker.XposedBlackListHooker;
|
||||
|
||||
public class YahfaEdxpConfig implements EdXpConfig {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package com.elderdrivers.riru.edxp.yahfa.config;
|
||||
|
||||
import com.elderdrivers.riru.edxp.hook.HookProvider;
|
||||
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
|
||||
import com.elderdrivers.riru.edxp.yahfa.dexmaker.DexMakerUtils;
|
||||
import com.elderdrivers.riru.edxp.yahfa.dexmaker.DynamicBridge;
|
||||
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
|
@ -17,7 +16,7 @@ public class YahfaHookProvider implements HookProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable {
|
||||
return DynamicBridge.invokeOriginalMethod(method, thisObject, args);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.elderdrivers.riru.edxp.yahfa.core;
|
||||
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.yahfa.entry.hooker.OnePlusWorkAroundHooker;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
|
||||
|
|
@ -14,8 +14,8 @@ import java.util.Set;
|
|||
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.yahfa.Main.backupAndHookNative;
|
||||
import static com.elderdrivers.riru.edxp.yahfa.Main.findMethodNative;
|
||||
import static com.elderdrivers.riru.edxp.Main.backupAndHookNative;
|
||||
import static com.elderdrivers.riru.edxp.Main.findMethodNative;
|
||||
|
||||
public class HookMain {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.elderdrivers.riru.edxp.yahfa.core;
|
|||
|
||||
import android.os.Build;
|
||||
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ package com.elderdrivers.riru.edxp.yahfa.dexmaker;
|
|||
import android.app.AndroidAppHelper;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.yahfa.core.HookMain;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.elderdrivers.riru.edxp.yahfa.dexmaker;
|
||||
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.annotation.TargetApi;
|
|||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.yahfa.core.HookMain;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import android.content.res.CompatibilityInfo;
|
|||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.elderdrivers.riru.edxp.yahfa.entry.hooker;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepMembers;
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.elderdrivers.riru.edxp.yahfa.proxy;
|
|||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
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;
|
||||
|
|
@ -11,7 +11,7 @@ import com.elderdrivers.riru.edxp.util.Utils;
|
|||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.yahfa.Main.isAppNeedHook;
|
||||
import static com.elderdrivers.riru.edxp.Main.isAppNeedHook;
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.elderdrivers.riru.edxp.yahfa.proxy;
|
||||
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.elderdrivers.riru.edxp.yahfa.util;
|
||||
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-yahfa', ':edxp-common'
|
||||
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook'
|
||||
|
|
@ -9,7 +9,7 @@ public interface HookProvider {
|
|||
|
||||
void hookMethod(Member method, XposedBridge.AdditionalHookInfo additionalInfo);
|
||||
|
||||
Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
|
||||
Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable;
|
||||
|
||||
Member findMethodNative(Member hookMethod);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public final class XposedBridge {
|
|||
private static final Object[] EMPTY_ARRAY = new Object[0];
|
||||
|
||||
// built-in handlers
|
||||
private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new HashMap<>();
|
||||
public static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new HashMap<>();
|
||||
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
|
||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
|
||||
|
||||
|
|
@ -402,7 +402,7 @@ public final class XposedBridge {
|
|||
Class<?>[] parameterTypes,
|
||||
Class<?> returnType,
|
||||
Object thisObject, Object[] args)
|
||||
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
throws Throwable {
|
||||
return EdXpConfigGlobal.getHookProvider().invokeOriginalMethod(method, thisObject, args);
|
||||
}
|
||||
|
||||
|
|
@ -432,7 +432,7 @@ public final class XposedBridge {
|
|||
* if an exception was thrown by the invoked method
|
||||
*/
|
||||
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
||||
throws NullPointerException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
throws Throwable {
|
||||
if (args == null) {
|
||||
args = EMPTY_ARRAY;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue