Introduce SandHook
This commit is contained in:
parent
696ede0209
commit
e858ebd70b
|
|
@ -47,6 +47,28 @@ afterEvaluate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pushTask.dependsOn(zipTask)
|
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 \
|
#define INJECT_DEX_PATH \
|
||||||
"/system/framework/edxp.jar:/system/framework/eddalvikdx.jar:/system/framework/eddexmaker.jar"
|
"/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
|
#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.annotation.SuppressLint;
|
||||||
import android.os.Build;
|
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.EdXpConfig;
|
||||||
import com.elderdrivers.riru.edxp.config.InstallerChooser;
|
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;
|
import com.elderdrivers.riru.edxp.yahfa.entry.hooker.XposedBlackListHooker;
|
||||||
|
|
||||||
public class YahfaEdxpConfig implements EdXpConfig {
|
public class YahfaEdxpConfig implements EdXpConfig {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package com.elderdrivers.riru.edxp.yahfa.config;
|
package com.elderdrivers.riru.edxp.yahfa.config;
|
||||||
|
|
||||||
import com.elderdrivers.riru.edxp.hook.HookProvider;
|
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.DexMakerUtils;
|
||||||
import com.elderdrivers.riru.edxp.yahfa.dexmaker.DynamicBridge;
|
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 java.lang.reflect.Member;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
@ -17,7 +16,7 @@ public class YahfaHookProvider implements HookProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
return DynamicBridge.invokeOriginalMethod(method, thisObject, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.elderdrivers.riru.edxp.yahfa.core;
|
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.yahfa.entry.hooker.OnePlusWorkAroundHooker;
|
||||||
import com.elderdrivers.riru.edxp.util.Utils;
|
import com.elderdrivers.riru.edxp.util.Utils;
|
||||||
|
|
||||||
|
|
@ -14,8 +14,8 @@ import java.util.Set;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
|
|
||||||
import static com.elderdrivers.riru.edxp.yahfa.Main.backupAndHookNative;
|
import static com.elderdrivers.riru.edxp.Main.backupAndHookNative;
|
||||||
import static com.elderdrivers.riru.edxp.yahfa.Main.findMethodNative;
|
import static com.elderdrivers.riru.edxp.Main.findMethodNative;
|
||||||
|
|
||||||
public class HookMain {
|
public class HookMain {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package com.elderdrivers.riru.edxp.yahfa.core;
|
||||||
|
|
||||||
import android.os.Build;
|
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 com.elderdrivers.riru.edxp.util.Utils;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ package com.elderdrivers.riru.edxp.yahfa.dexmaker;
|
||||||
import android.app.AndroidAppHelper;
|
import android.app.AndroidAppHelper;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
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.config.ConfigManager;
|
||||||
import com.elderdrivers.riru.edxp.yahfa.core.HookMain;
|
import com.elderdrivers.riru.edxp.yahfa.core.HookMain;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.elderdrivers.riru.edxp.yahfa.dexmaker;
|
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.io.File;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import android.annotation.TargetApi;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
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 com.elderdrivers.riru.edxp.yahfa.core.HookMain;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import android.content.res.CompatibilityInfo;
|
||||||
|
|
||||||
import com.elderdrivers.riru.common.KeepMembers;
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
import com.elderdrivers.riru.edxp.util.Utils;
|
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 com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.elderdrivers.riru.edxp.yahfa.entry.hooker;
|
package com.elderdrivers.riru.edxp.yahfa.entry.hooker;
|
||||||
|
|
||||||
import com.elderdrivers.riru.common.KeepMembers;
|
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 com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package com.elderdrivers.riru.edxp.yahfa.proxy;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
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.config.ConfigManager;
|
||||||
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
||||||
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
|
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 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;
|
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.elderdrivers.riru.edxp.yahfa.proxy;
|
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.config.ConfigManager;
|
||||||
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
|
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
|
||||||
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.elderdrivers.riru.edxp.yahfa.util;
|
package com.elderdrivers.riru.edxp.yahfa.util;
|
||||||
|
|
||||||
import com.elderdrivers.riru.edxp.util.Utils;
|
import com.elderdrivers.riru.edxp.util.Utils;
|
||||||
import com.elderdrivers.riru.edxp.yahfa.Main;
|
import com.elderdrivers.riru.edxp.Main;
|
||||||
|
|
||||||
import java.util.Arrays;
|
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);
|
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);
|
Member findMethodNative(Member hookMethod);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ public final class XposedBridge {
|
||||||
private static final Object[] EMPTY_ARRAY = new Object[0];
|
private static final Object[] EMPTY_ARRAY = new Object[0];
|
||||||
|
|
||||||
// built-in handlers
|
// 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<>();
|
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
|
||||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
|
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
|
||||||
|
|
||||||
|
|
@ -402,7 +402,7 @@ public final class XposedBridge {
|
||||||
Class<?>[] parameterTypes,
|
Class<?>[] parameterTypes,
|
||||||
Class<?> returnType,
|
Class<?> returnType,
|
||||||
Object thisObject, Object[] args)
|
Object thisObject, Object[] args)
|
||||||
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
throws Throwable {
|
||||||
return EdXpConfigGlobal.getHookProvider().invokeOriginalMethod(method, thisObject, args);
|
return EdXpConfigGlobal.getHookProvider().invokeOriginalMethod(method, thisObject, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -432,7 +432,7 @@ public final class XposedBridge {
|
||||||
* if an exception was thrown by the invoked method
|
* if an exception was thrown by the invoked method
|
||||||
*/
|
*/
|
||||||
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
||||||
throws NullPointerException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
throws Throwable {
|
||||||
if (args == null) {
|
if (args == null) {
|
||||||
args = EMPTY_ARRAY;
|
args = EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue