Avoid using system methods in callback (#1830)
This commit is contained in:
parent
1554ed08a8
commit
ec5f7847e9
|
|
@ -168,7 +168,7 @@ public final class XposedBridge {
|
||||||
*/
|
*/
|
||||||
public static void deoptimizeMethod(Member deoptimizedMethod) {
|
public static void deoptimizeMethod(Member deoptimizedMethod) {
|
||||||
if (!(deoptimizedMethod instanceof Executable)) {
|
if (!(deoptimizedMethod instanceof Executable)) {
|
||||||
throw new IllegalArgumentException("Only methods and constructors can be deoptimized: " + deoptimizedMethod.toString());
|
throw new IllegalArgumentException("Only methods and constructors can be deoptimized: " + deoptimizedMethod);
|
||||||
} else if (Modifier.isAbstract(deoptimizedMethod.getModifiers())) {
|
} else if (Modifier.isAbstract(deoptimizedMethod.getModifiers())) {
|
||||||
throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + deoptimizedMethod);
|
throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + deoptimizedMethod);
|
||||||
} else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) {
|
} else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) {
|
||||||
|
|
@ -193,16 +193,13 @@ public final class XposedBridge {
|
||||||
*/
|
*/
|
||||||
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
|
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||||
if (!(hookMethod instanceof Executable)) {
|
if (!(hookMethod instanceof Executable)) {
|
||||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
|
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod);
|
||||||
}
|
} else if (Modifier.isAbstract(hookMethod.getModifiers())) {
|
||||||
// No check interface because there may be default methods
|
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod);
|
||||||
/*else if (hookMethod.getDeclaringClass().isInterface()) {
|
|
||||||
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
|
|
||||||
}*/
|
|
||||||
else if (Modifier.isAbstract(hookMethod.getModifiers())) {
|
|
||||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
|
|
||||||
} else if (hookMethod.getDeclaringClass().getClassLoader() == XposedBridge.class.getClassLoader()) {
|
} else if (hookMethod.getDeclaringClass().getClassLoader() == XposedBridge.class.getClassLoader()) {
|
||||||
throw new IllegalArgumentException("Do not allow hooking inner methods");
|
throw new IllegalArgumentException("Do not allow hooking inner methods");
|
||||||
|
} else if (hookMethod.getDeclaringClass() == Method.class && hookMethod.getName().equals("invoke")) {
|
||||||
|
throw new IllegalArgumentException("Cannot hook Method.invoke");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
|
|
@ -383,26 +380,51 @@ public final class XposedBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AdditionalHookInfo {
|
public static class AdditionalHookInfo {
|
||||||
final Executable method;
|
private static final ClassCastException castException = new ClassCastException("Return value's type from hook callback does not match the hooked method");
|
||||||
final Object[][] callbacks;
|
private static final Method getCause;
|
||||||
|
private final Executable method;
|
||||||
|
private final Object[][] callbacks;
|
||||||
|
private final Class<?> returnType;
|
||||||
|
private final boolean isStatic;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Method tmp;
|
||||||
|
try {
|
||||||
|
tmp = InvocationTargetException.class.getMethod("getCause");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tmp = null;
|
||||||
|
}
|
||||||
|
getCause = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
private AdditionalHookInfo(Executable method, Object[][] callbacks) {
|
private AdditionalHookInfo(Executable method, Object[][] callbacks) {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
isStatic = Modifier.isStatic(method.getModifiers());
|
||||||
|
if (method instanceof Method) {
|
||||||
|
returnType = ((Method) method).getReturnType();
|
||||||
|
} else {
|
||||||
|
returnType = null;
|
||||||
|
}
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method is quite critical. We should try not to use system methods to avoid
|
||||||
|
// endless recursive
|
||||||
public Object callback(Object[] args) throws Throwable {
|
public Object callback(Object[] args) throws Throwable {
|
||||||
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
||||||
|
|
||||||
param.method = method;
|
param.method = method;
|
||||||
|
|
||||||
if (Modifier.isStatic(method.getModifiers())) {
|
if (isStatic) {
|
||||||
param.thisObject = null;
|
param.thisObject = null;
|
||||||
param.args = args;
|
param.args = args;
|
||||||
} else {
|
} else {
|
||||||
param.thisObject = args[0];
|
param.thisObject = args[0];
|
||||||
param.args = new Object[args.length - 1];
|
param.args = new Object[args.length - 1];
|
||||||
System.arraycopy(args, 1, param.args, 0, args.length - 1);
|
//noinspection ManualArrayCopy
|
||||||
|
for (int i = 0; i < args.length - 1; ++i) {
|
||||||
|
param.args[i] = args[i + 1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object[] callbacksSnapshot = callbacks[0];
|
Object[] callbacksSnapshot = callbacks[0];
|
||||||
|
|
@ -411,7 +433,7 @@ public final class XposedBridge {
|
||||||
try {
|
try {
|
||||||
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args);
|
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args);
|
||||||
} catch (InvocationTargetException ite) {
|
} catch (InvocationTargetException ite) {
|
||||||
throw ite.getCause();
|
throw (Throwable) HookBridge.invokeOriginalMethod(getCause, ite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,7 +463,7 @@ public final class XposedBridge {
|
||||||
try {
|
try {
|
||||||
param.setResult(HookBridge.invokeOriginalMethod(method, param.thisObject, param.args));
|
param.setResult(HookBridge.invokeOriginalMethod(method, param.thisObject, param.args));
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
param.setThrowable(e.getCause());
|
param.setThrowable((Throwable) HookBridge.invokeOriginalMethod(getCause, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -469,10 +491,8 @@ public final class XposedBridge {
|
||||||
throw param.getThrowable();
|
throw param.getThrowable();
|
||||||
else {
|
else {
|
||||||
var result = param.getResult();
|
var result = param.getResult();
|
||||||
if (method instanceof Method) {
|
if (returnType != null && !returnType.isPrimitive() && !HookBridge.instanceOf(result, returnType)) {
|
||||||
var returnType = ((Method) method).getReturnType();
|
throw castException;
|
||||||
if (!returnType.isPrimitive())
|
|
||||||
return returnType.cast(result);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,17 @@ package org.lsposed.lspd.nativebridge;
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
import dalvik.annotation.optimization.FastNative;
|
||||||
|
|
||||||
public class HookBridge {
|
public class HookBridge {
|
||||||
public static native boolean hookMethod(Executable hookMethod, Class<?> hooker, int priority, Object callback);
|
public static native boolean hookMethod(Executable hookMethod, Class<?> hooker, int priority, Object callback);
|
||||||
public static native boolean unhookMethod(Executable hookMethod, Object callback);
|
public static native boolean unhookMethod(Executable hookMethod, Object callback);
|
||||||
|
|
||||||
public static native boolean deoptimizeMethod(Executable method);
|
public static native boolean deoptimizeMethod(Executable method);
|
||||||
|
|
||||||
public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
|
@FastNative
|
||||||
|
public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
|
||||||
|
|
||||||
|
@FastNative
|
||||||
|
public static native boolean instanceOf(Object obj, Class<?> clazz);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ import android.content.res.XResources;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
|
import dalvik.annotation.optimization.FastNative;
|
||||||
|
|
||||||
public class ResourcesHook {
|
public class ResourcesHook {
|
||||||
|
|
||||||
public static native boolean initXResourcesNative();
|
public static native boolean initXResourcesNative();
|
||||||
|
|
@ -33,5 +35,6 @@ public class ResourcesHook {
|
||||||
|
|
||||||
public static native ClassLoader buildDummyClassLoader(ClassLoader parent, String resourceSuperClass, String typedArraySuperClass);
|
public static native ClassLoader buildDummyClassLoader(ClassLoader parent, String resourceSuperClass, String typedArraySuperClass);
|
||||||
|
|
||||||
|
@FastNative
|
||||||
public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes);
|
public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ std::shared_mutex hooked_lock;
|
||||||
// Rehashing invalidates iterators, changes ordering between elements, and changes which buckets elements appear in, but does not invalidate pointers or references to elements.
|
// Rehashing invalidates iterators, changes ordering between elements, and changes which buckets elements appear in, but does not invalidate pointers or references to elements.
|
||||||
std::unordered_map<jmethodID, HookItem> hooked_methods;
|
std::unordered_map<jmethodID, HookItem> hooked_methods;
|
||||||
|
|
||||||
jmethodID invoke = nullptr;
|
jobject (*native_invoke)(JNIEnv* env, jobject javaMethod, jobject javaReceiver,
|
||||||
|
jobjectArray javaArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
|
|
@ -166,7 +167,11 @@ LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeOriginalMethod, jobject hookMet
|
||||||
if (hook_item && hook_item->backup) {
|
if (hook_item && hook_item->backup) {
|
||||||
to_call = hook_item->backup;
|
to_call = hook_item->backup;
|
||||||
}
|
}
|
||||||
return env->CallObjectMethod(to_call, invoke, thiz, args);
|
return native_invoke(env, to_call, thiz, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, instanceOf, jobject object, jclass expected_class) {
|
||||||
|
return env->IsInstanceOf(object, expected_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
static JNINativeMethod gMethods[] = {
|
static JNINativeMethod gMethods[] = {
|
||||||
|
|
@ -174,13 +179,16 @@ static JNINativeMethod gMethods[] = {
|
||||||
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, deoptimizeMethod, "(Ljava/lang/reflect/Executable;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, deoptimizeMethod, "(Ljava/lang/reflect/Executable;)Z"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
|
LSP_NATIVE_METHOD(HookBridge, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
|
||||||
|
LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
|
||||||
};
|
};
|
||||||
|
|
||||||
void RegisterHookBridge(JNIEnv *env) {
|
void RegisterHookBridge(JNIEnv *env) {
|
||||||
auto method = env->FindClass("java/lang/reflect/Method");
|
auto method = env->FindClass("java/lang/reflect/Method");
|
||||||
invoke = env->GetMethodID(
|
auto invoke = env->GetMethodID(
|
||||||
method, "invoke",
|
method, "invoke",
|
||||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||||
|
native_invoke = reinterpret_cast<decltype(native_invoke)>(
|
||||||
|
lsplant::GetNativeFunction(env, env->ToReflectedMethod(method, invoke, false)));
|
||||||
env->DeleteLocalRef(method);
|
env->DeleteLocalRef(method);
|
||||||
REGISTER_LSP_NATIVE_METHODS(HookBridge);
|
REGISTER_LSP_NATIVE_METHODS(HookBridge);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dalvik.annotation.optimization;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
public @interface FastNative {
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue