Introduce SandHook

This commit is contained in:
solohsu 2019-03-20 00:32:33 +08:00
parent 696ede0209
commit e858ebd70b
72 changed files with 8150 additions and 25 deletions

View File

@ -47,6 +47,28 @@ afterEvaluate {
}
pushTask.dependsOn(zipTask)
zipTask = task("zipSandhook${nameCapped}", type: Exec, dependsOn: ":edxp-sandhook:makeAndCopy${nameCapped}") {
workingDir '..'
commandLine 'sh', 'build.sh', \
project.name, \
"Sandhook-${project.version}-${nameLowered}", \
"${project.extensions['module_name']}"
}
pushTask = task("pushSandhook${nameCapped}", type: Exec) {
workingDir 'release'
def commands = ["adb", "push", "magisk-${project.extensions['module_name']}-Sandhook" +
"-${project.version}-${nameLowered}.zip", "/sdcard/"]
if (OperatingSystem.current().isWindows()) {
commandLine 'cmd', '/c', commands.join(" ")
} else {
commandLine commands
}
}
pushTask.dependsOn(zipTask)
}
}

View File

@ -10,6 +10,6 @@
#define INJECT_DEX_PATH \
"/system/framework/edxp.jar:/system/framework/eddalvikdx.jar:/system/framework/eddexmaker.jar"
#define ENTRY_CLASS_NAME "com.elderdrivers.riru.edxp.yahfa.Main"
#define ENTRY_CLASS_NAME "com.elderdrivers.riru.edxp.Main"
#endif //CONFIG_H

View File

@ -0,0 +1 @@
libsandhook.edxp.so

1
edxp-sandhook/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -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"
}
}
}

View File

@ -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.

33
edxp-sandhook/proguard-rules.pro vendored Normal file
View File

@ -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.** {*;}

View File

@ -0,0 +1 @@
<manifest package="com.elderdrivers.riru.edxp.sandhook" />

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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()
};
}

View File

@ -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()
};
}

View File

@ -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()
};
}

View File

@ -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()
};
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
// }
}

View File

@ -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;
}
}

View File

@ -0,0 +1,5 @@
package com.swift.sandhook.xposedcompat.hookstub;
public interface CallOriginCallBack {
long call(long... args) throws Throwable;
}

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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";
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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/";
}
}

View File

@ -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>();
}
}

View File

@ -1,4 +1,4 @@
package com.elderdrivers.riru.edxp.yahfa;
package com.elderdrivers.riru.edxp;
import android.annotation.SuppressLint;
import android.os.Build;

View File

@ -2,7 +2,7 @@ package com.elderdrivers.riru.edxp.yahfa.config;
import com.elderdrivers.riru.edxp.config.EdXpConfig;
import com.elderdrivers.riru.edxp.config.InstallerChooser;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.yahfa.entry.hooker.XposedBlackListHooker;
public class YahfaEdxpConfig implements EdXpConfig {

View File

@ -1,11 +1,10 @@
package com.elderdrivers.riru.edxp.yahfa.config;
import com.elderdrivers.riru.edxp.hook.HookProvider;
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
import com.elderdrivers.riru.edxp.yahfa.dexmaker.DexMakerUtils;
import com.elderdrivers.riru.edxp.yahfa.dexmaker.DynamicBridge;
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import de.robv.android.xposed.XposedBridge;
@ -17,7 +16,7 @@ public class YahfaHookProvider implements HookProvider {
}
@Override
public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
public Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable {
return DynamicBridge.invokeOriginalMethod(method, thisObject, args);
}

View File

@ -1,6 +1,6 @@
package com.elderdrivers.riru.edxp.yahfa.core;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.yahfa.entry.hooker.OnePlusWorkAroundHooker;
import com.elderdrivers.riru.edxp.util.Utils;
@ -14,8 +14,8 @@ import java.util.Set;
import de.robv.android.xposed.XposedHelpers;
import static com.elderdrivers.riru.edxp.yahfa.Main.backupAndHookNative;
import static com.elderdrivers.riru.edxp.yahfa.Main.findMethodNative;
import static com.elderdrivers.riru.edxp.Main.backupAndHookNative;
import static com.elderdrivers.riru.edxp.Main.findMethodNative;
public class HookMain {

View File

@ -2,7 +2,7 @@ package com.elderdrivers.riru.edxp.yahfa.core;
import android.os.Build;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.util.Utils;
import java.lang.reflect.Field;

View File

@ -3,9 +3,8 @@ package com.elderdrivers.riru.edxp.yahfa.dexmaker;
import android.app.AndroidAppHelper;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.config.ConfigManager;
import com.elderdrivers.riru.edxp.yahfa.core.HookMain;

View File

@ -1,6 +1,6 @@
package com.elderdrivers.riru.edxp.yahfa.dexmaker;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import java.io.File;
import java.lang.reflect.Constructor;

View File

@ -4,7 +4,7 @@ import android.annotation.TargetApi;
import android.os.Build;
import android.text.TextUtils;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.yahfa.core.HookMain;
import java.io.File;

View File

@ -8,7 +8,7 @@ import android.content.res.CompatibilityInfo;
import com.elderdrivers.riru.common.KeepMembers;
import com.elderdrivers.riru.edxp.util.Utils;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
import de.robv.android.xposed.XposedBridge;

View File

@ -1,7 +1,7 @@
package com.elderdrivers.riru.edxp.yahfa.entry.hooker;
import com.elderdrivers.riru.common.KeepMembers;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
import de.robv.android.xposed.XposedBridge;

View File

@ -2,7 +2,7 @@ package com.elderdrivers.riru.edxp.yahfa.proxy;
import android.text.TextUtils;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.config.ConfigManager;
import com.elderdrivers.riru.edxp.yahfa.entry.Router;
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
@ -11,7 +11,7 @@ import com.elderdrivers.riru.edxp.util.Utils;
import de.robv.android.xposed.XposedBridge;
import static com.elderdrivers.riru.edxp.yahfa.Main.isAppNeedHook;
import static com.elderdrivers.riru.edxp.Main.isAppNeedHook;
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
/**

View File

@ -1,6 +1,6 @@
package com.elderdrivers.riru.edxp.yahfa.proxy;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import com.elderdrivers.riru.edxp.config.ConfigManager;
import com.elderdrivers.riru.edxp.yahfa.util.PrebuiltMethodsDeopter;
import com.elderdrivers.riru.edxp.yahfa.entry.Router;

View File

@ -1,7 +1,7 @@
package com.elderdrivers.riru.edxp.yahfa.util;
import com.elderdrivers.riru.edxp.util.Utils;
import com.elderdrivers.riru.edxp.yahfa.Main;
import com.elderdrivers.riru.edxp.Main;
import java.util.Arrays;

View File

@ -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'

View File

@ -9,7 +9,7 @@ public interface HookProvider {
void hookMethod(Member method, XposedBridge.AdditionalHookInfo additionalInfo);
Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws Throwable;
Member findMethodNative(Member hookMethod);

View File

@ -60,7 +60,7 @@ public final class XposedBridge {
private static final Object[] EMPTY_ARRAY = new Object[0];
// built-in handlers
private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new HashMap<>();
public static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new HashMap<>();
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
@ -402,7 +402,7 @@ public final class XposedBridge {
Class<?>[] parameterTypes,
Class<?> returnType,
Object thisObject, Object[] args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
throws Throwable {
return EdXpConfigGlobal.getHookProvider().invokeOriginalMethod(method, thisObject, args);
}
@ -432,7 +432,7 @@ public final class XposedBridge {
* if an exception was thrown by the invoked method
*/
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
throws NullPointerException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
throws Throwable {
if (args == null) {
args = EMPTY_ARRAY;
}