New hook API (#2644)
This commit is contained in:
parent
bd7a9b91b2
commit
bee7938002
|
|
@ -42,9 +42,6 @@
|
||||||
getFrameworkVersion(...);
|
getFrameworkVersion(...);
|
||||||
getFrameworkVersionCode(...);
|
getFrameworkVersionCode(...);
|
||||||
getFrameworkPrivilege(...);
|
getFrameworkPrivilege(...);
|
||||||
featuredMethod(...);
|
|
||||||
hookBefore(...);
|
|
||||||
hookAfter(...);
|
|
||||||
hook(...);
|
hook(...);
|
||||||
deoptimize(...);
|
deoptimize(...);
|
||||||
invokeOrigin(...);
|
invokeOrigin(...);
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,12 @@
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.lsposed.lspd.nativebridge.HookBridge;
|
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Member;
|
import java.lang.reflect.Member;
|
||||||
import java.util.ConcurrentModificationException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.IXUnhook;
|
import de.robv.android.xposed.callbacks.IXUnhook;
|
||||||
import de.robv.android.xposed.callbacks.XCallback;
|
import de.robv.android.xposed.callbacks.XCallback;
|
||||||
import io.github.libxposed.api.XposedInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback class for method hooks.
|
* Callback class for method hooks.
|
||||||
|
|
@ -103,7 +95,7 @@ public abstract class XC_MethodHook extends XCallback {
|
||||||
/**
|
/**
|
||||||
* Wraps information about the method call and allows to influence it.
|
* Wraps information about the method call and allows to influence it.
|
||||||
*/
|
*/
|
||||||
public static final class MethodHookParam <T extends Executable> extends XCallback.Param implements XposedInterface.BeforeHookCallback<T>, XposedInterface.AfterHookCallback<T> {
|
public static final class MethodHookParam<T extends Executable> extends XCallback.Param {
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
|
@ -158,11 +150,6 @@ public abstract class XC_MethodHook extends XCallback {
|
||||||
return throwable;
|
return throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSkipped() {
|
|
||||||
return returnEarly;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if an exception was thrown by the method.
|
* Returns true if an exception was thrown by the method.
|
||||||
*/
|
*/
|
||||||
|
|
@ -189,62 +176,6 @@ public abstract class XC_MethodHook extends XCallback {
|
||||||
throw throwable;
|
throw throwable;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public T getOrigin() {
|
|
||||||
return (T) method;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Object getThis() {
|
|
||||||
return thisObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Object[] getArgs() {
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public <U> U getArg(int index) {
|
|
||||||
return (U) args[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <U> void setArg(int index, U value) {
|
|
||||||
args[index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void returnAndSkip(@Nullable Object returnValue) {
|
|
||||||
setResult(returnValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void throwAndSkip(@Nullable Throwable throwable) {
|
|
||||||
setThrowable(throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Object invokeOrigin() throws InvocationTargetException, IllegalAccessException {
|
|
||||||
return HookBridge.invokeOriginalMethod((Executable) method, thisObject, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public <U> U getExtra(@NonNull String key) {
|
|
||||||
return (U) extras.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException {
|
|
||||||
extras.put(key, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,10 @@ import java.lang.reflect.Member;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
|
@ -219,7 +221,7 @@ public final class XposedBridge {
|
||||||
throw new IllegalArgumentException("callback should not be null!");
|
throw new IllegalArgumentException("callback should not be null!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HookBridge.hookMethod((Executable) hookMethod, AdditionalHookInfo.class, callback.priority, callback)) {
|
if (!HookBridge.hookMethod(false, (Executable) hookMethod, AdditionalHookInfo.class, callback.priority, callback)) {
|
||||||
log("Failed to hook " + hookMethod);
|
log("Failed to hook " + hookMethod);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -238,7 +240,7 @@ public final class XposedBridge {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||||
if (hookMethod instanceof Executable) {
|
if (hookMethod instanceof Executable) {
|
||||||
HookBridge.unhookMethod((Executable) hookMethod, callback);
|
HookBridge.unhookMethod(false, (Executable) hookMethod, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,7 +440,7 @@ public final class XposedBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object[] callbacksSnapshot = HookBridge.callbackSnapshot(method);
|
Object[] callbacksSnapshot = HookBridge.callbackSnapshot(HookerCallback.class, method);
|
||||||
if (callbacksSnapshot == null || callbacksSnapshot.length == 0) {
|
if (callbacksSnapshot == null || callbacksSnapshot.length == 0) {
|
||||||
try {
|
try {
|
||||||
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args);
|
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args);
|
||||||
|
|
@ -446,6 +448,7 @@ public final class XposedBridge {
|
||||||
throw (Throwable) HookBridge.invokeOriginalMethod(getCause, ite);
|
throw (Throwable) HookBridge.invokeOriginalMethod(getCause, ite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Queue<Object> extras = new ArrayDeque<>(callbacksSnapshot.length);
|
||||||
|
|
||||||
// call "before method" callbacks
|
// call "before method" callbacks
|
||||||
int beforeIdx = 0;
|
int beforeIdx = 0;
|
||||||
|
|
@ -454,8 +457,9 @@ public final class XposedBridge {
|
||||||
var cb = callbacksSnapshot[beforeIdx];
|
var cb = callbacksSnapshot[beforeIdx];
|
||||||
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
|
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
|
||||||
((XC_MethodHook) cb).beforeHookedMethod(param);
|
((XC_MethodHook) cb).beforeHookedMethod(param);
|
||||||
} else if (HookBridge.instanceOf(cb, XposedInterface.BeforeHooker.class)) {
|
} else if (HookBridge.instanceOf(cb, HookerCallback.class)) {
|
||||||
((XposedInterface.BeforeHooker<T>) cb).before(param);
|
var hooker = (HookerCallback) cb;
|
||||||
|
extras.add(hooker.beforeInvocation.invoke(null, method, param.thisObject, param.args));
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
XposedBridge.log(t);
|
XposedBridge.log(t);
|
||||||
|
|
@ -492,8 +496,9 @@ public final class XposedBridge {
|
||||||
try {
|
try {
|
||||||
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
|
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
|
||||||
((XC_MethodHook) cb).afterHookedMethod(param);
|
((XC_MethodHook) cb).afterHookedMethod(param);
|
||||||
} else if (HookBridge.instanceOf(cb, XposedInterface.AfterHooker.class)) {
|
} else if (HookBridge.instanceOf(cb, HookerCallback.class)) {
|
||||||
((XposedInterface.AfterHooker<T>) cb).after(param);
|
var hooker = (HookerCallback) cb;
|
||||||
|
hooker.afterInvocation.invoke(null, extras.poll(), lastResult);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
XposedBridge.log(t);
|
XposedBridge.log(t);
|
||||||
|
|
@ -518,4 +523,14 @@ public final class XposedBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class HookerCallback {
|
||||||
|
Method beforeInvocation;
|
||||||
|
Method afterInvocation;
|
||||||
|
|
||||||
|
public HookerCallback(Method beforeInvocation, Method afterInvocation) {
|
||||||
|
this.beforeInvocation = beforeInvocation;
|
||||||
|
this.afterInvocation = afterInvocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,12 @@ import java.io.InputStream;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
@ -66,8 +68,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import io.github.libxposed.api.XposedContext;
|
import io.github.libxposed.api.XposedContext;
|
||||||
|
import io.github.libxposed.api.XposedInterface;
|
||||||
import io.github.libxposed.api.XposedModule;
|
import io.github.libxposed.api.XposedModule;
|
||||||
import io.github.libxposed.api.XposedModuleInterface;
|
import io.github.libxposed.api.XposedModuleInterface;
|
||||||
|
import io.github.libxposed.api.annotations.AfterInvocation;
|
||||||
|
import io.github.libxposed.api.annotations.BeforeInvocation;
|
||||||
|
import io.github.libxposed.api.annotations.XposedHooker;
|
||||||
import io.github.libxposed.api.errors.HookFailedError;
|
import io.github.libxposed.api.errors.HookFailedError;
|
||||||
import io.github.libxposed.api.utils.DexParser;
|
import io.github.libxposed.api.utils.DexParser;
|
||||||
|
|
||||||
|
|
@ -862,41 +868,68 @@ public class LSPosedContext extends XposedContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private <T extends Executable> MethodUnhooker<T> doHook(T hookMethod, int priority, Class<? extends Hooker> hooker) {
|
||||||
public Object featuredMethod(String name, Object... args) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T, U extends Executable> MethodUnhooker<T, U> doHook(U hookMethod, int priority, T callback) {
|
|
||||||
if (Modifier.isAbstract(hookMethod.getModifiers())) {
|
if (Modifier.isAbstract(hookMethod.getModifiers())) {
|
||||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod);
|
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod);
|
||||||
} else if (hookMethod.getDeclaringClass().getClassLoader() == LSPosedContext.class.getClassLoader()) {
|
} else if (hookMethod.getDeclaringClass().getClassLoader() == LSPosedContext.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")) {
|
} else if (hookMethod.getDeclaringClass() == Method.class && hookMethod.getName().equals("invoke")) {
|
||||||
throw new IllegalArgumentException("Cannot hook Method.invoke");
|
throw new IllegalArgumentException("Cannot hook Method.invoke");
|
||||||
}
|
} else if (hooker == null) {
|
||||||
|
|
||||||
if (callback == null) {
|
|
||||||
throw new IllegalArgumentException("hooker should not be null!");
|
throw new IllegalArgumentException("hooker should not be null!");
|
||||||
|
} else if (hooker.getAnnotation(XposedHooker.class) == null) {
|
||||||
|
throw new IllegalArgumentException("Hooker should be annotated with @XposedHooker");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HookBridge.hookMethod(hookMethod, XposedBridge.AdditionalHookInfo.class, priority, callback)) {
|
Method beforeInvocation = null, afterInvocation = null;
|
||||||
|
var modifiers = Modifier.PUBLIC | Modifier.STATIC;
|
||||||
|
for (var method : hooker.getDeclaredMethods()) {
|
||||||
|
if (method.getAnnotation(BeforeInvocation.class) != null) {
|
||||||
|
if (beforeInvocation != null) {
|
||||||
|
throw new IllegalArgumentException("More than one method annotated with @BeforeInvocation");
|
||||||
|
}
|
||||||
|
boolean valid;
|
||||||
|
valid = (method.getModifiers() & modifiers) == modifiers;
|
||||||
|
valid &= Arrays.equals(method.getParameterTypes(), new Class[]{Member.class, Object.class, Object[].class});
|
||||||
|
valid &= method.getReturnType().equals(Hooker.class);
|
||||||
|
if (!valid) {
|
||||||
|
throw new IllegalArgumentException("BeforeInvocation method format is invalid");
|
||||||
|
}
|
||||||
|
beforeInvocation = method;
|
||||||
|
}
|
||||||
|
if (method.getAnnotation(AfterInvocation.class) != null) {
|
||||||
|
if (afterInvocation != null) {
|
||||||
|
throw new IllegalArgumentException("More than one method annotated with @AfterInvocation");
|
||||||
|
}
|
||||||
|
boolean valid;
|
||||||
|
valid = (method.getModifiers() & modifiers) == modifiers;
|
||||||
|
valid &= Arrays.equals(method.getParameterTypes(), new Class[]{Hooker.class, Object.class});
|
||||||
|
valid &= method.getReturnType().equals(void.class);
|
||||||
|
if (!valid) {
|
||||||
|
throw new IllegalArgumentException("AfterInvocation method format is invalid");
|
||||||
|
}
|
||||||
|
afterInvocation = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (beforeInvocation == null) {
|
||||||
|
throw new IllegalArgumentException("No method annotated with @BeforeInvocation");
|
||||||
|
}
|
||||||
|
if (afterInvocation == null) {
|
||||||
|
throw new IllegalArgumentException("No method annotated with @AfterInvocation");
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback = new XposedBridge.HookerCallback(beforeInvocation, afterInvocation);
|
||||||
|
if (HookBridge.hookMethod(true, hookMethod, XposedBridge.AdditionalHookInfo.class, priority, callback)) {
|
||||||
return new MethodUnhooker<>() {
|
return new MethodUnhooker<>() {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public U getOrigin() {
|
public T getOrigin() {
|
||||||
return hookMethod;
|
return hookMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public T getHooker() {
|
|
||||||
return callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unhook() {
|
public void unhook() {
|
||||||
HookBridge.unhookMethod(hookMethod, callback);
|
HookBridge.unhookMethod(true, hookMethod, callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -905,73 +938,25 @@ public class LSPosedContext extends XposedContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public MethodUnhooker<BeforeHooker<Method>, Method> hookBefore(@NonNull Method origin, @NonNull BeforeHooker<Method> hooker) {
|
public MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public MethodUnhooker<AfterHooker<Method>, Method> hookAfter(@NonNull Method origin, @NonNull AfterHooker<Method> hooker) {
|
public MethodUnhooker<Method> hook(@NonNull Method origin, int priority, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public MethodUnhooker<Hooker<Method>, Method> hook(@NonNull Method origin, @NonNull Hooker<Method> hooker) {
|
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public MethodUnhooker<BeforeHooker<Method>, Method> hookBefore(@NonNull Method origin, int priority, @NonNull BeforeHooker<Method> hooker) {
|
|
||||||
return doHook(origin, priority, hooker);
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public MethodUnhooker<AfterHooker<Method>, Method> hookAfter(@NonNull Method origin, int priority, @NonNull AfterHooker<Method> hooker) {
|
public <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, priority, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public MethodUnhooker<Hooker<Method>, Method> hook(@NonNull Method origin, int priority, @NonNull Hooker<Method> hooker) {
|
|
||||||
return doHook(origin, priority, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public <T> MethodUnhooker<BeforeHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, @NonNull BeforeHooker<Constructor<T>> hooker) {
|
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public <T> MethodUnhooker<AfterHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, @NonNull AfterHooker<Constructor<T>> hooker) {
|
public <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public <T> MethodUnhooker<Hooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Hooker<Constructor<T>> hooker) {
|
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public <T> MethodUnhooker<BeforeHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, int priority, @NonNull BeforeHooker<Constructor<T>> hooker) {
|
|
||||||
return doHook(origin, priority, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public <T> MethodUnhooker<AfterHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, int priority, @NonNull AfterHooker<Constructor<T>> hooker) {
|
|
||||||
return doHook(origin, priority, hooker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public <T> MethodUnhooker<Hooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Hooker<Constructor<T>> hooker) {
|
|
||||||
return doHook(origin, priority, hooker);
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import java.lang.reflect.InvocationTargetException;
|
||||||
import dalvik.annotation.optimization.FastNative;
|
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(boolean useModernApi, Executable hookMethod, Class<?> hooker, int priority, Object callback);
|
||||||
|
|
||||||
public static native boolean unhookMethod(Executable hookMethod, Object callback);
|
public static native boolean unhookMethod(boolean useModernApi, Executable hookMethod, Object callback);
|
||||||
|
|
||||||
public static native boolean deoptimizeMethod(Executable method);
|
public static native boolean deoptimizeMethod(Executable method);
|
||||||
|
|
||||||
|
|
@ -24,5 +24,5 @@ public class HookBridge {
|
||||||
@FastNative
|
@FastNative
|
||||||
public static native boolean setTrusted(Object cookie);
|
public static native boolean setTrusted(Object cookie);
|
||||||
|
|
||||||
public static native Object[] callbackSnapshot(Executable method);
|
public static native Object[] callbackSnapshot(Class<?> hooker_callback, Executable method);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,19 @@
|
||||||
using namespace lsplant;
|
using namespace lsplant;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
struct CallbackType {
|
||||||
|
bool use_modern_api;
|
||||||
|
union {
|
||||||
|
jobject callback_object;
|
||||||
|
struct {
|
||||||
|
jmethodID before_method;
|
||||||
|
jmethodID after_method;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
struct HookItem {
|
struct HookItem {
|
||||||
std::multimap<jint, jobject, std::greater<>> callbacks {};
|
std::multimap<jint, CallbackType, std::greater<>> callbacks;
|
||||||
private:
|
private:
|
||||||
std::atomic<jobject> backup {nullptr};
|
std::atomic<jobject> backup {nullptr};
|
||||||
static_assert(decltype(backup)::is_always_lock_free);
|
static_assert(decltype(backup)::is_always_lock_free);
|
||||||
|
|
@ -57,10 +67,13 @@ std::shared_mutex hooked_lock;
|
||||||
absl::flat_hash_map<jmethodID, std::unique_ptr<HookItem>> hooked_methods;
|
absl::flat_hash_map<jmethodID, std::unique_ptr<HookItem>> hooked_methods;
|
||||||
|
|
||||||
jmethodID invoke = nullptr;
|
jmethodID invoke = nullptr;
|
||||||
|
jmethodID callback_ctor = nullptr;
|
||||||
|
jfieldID before_method_field = nullptr;
|
||||||
|
jfieldID after_method_field = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace lspd {
|
namespace lspd {
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jobject hookMethod,
|
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jboolean useModernApi, jobject hookMethod,
|
||||||
jclass hooker, jint priority, jobject callback) {
|
jclass hooker, jint priority, jobject callback) {
|
||||||
bool newHook = false;
|
bool newHook = false;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
|
@ -108,11 +121,32 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jobject hookMethod,
|
||||||
jobject backup = hook_item->GetBackup();
|
jobject backup = hook_item->GetBackup();
|
||||||
if (!backup) return JNI_FALSE;
|
if (!backup) return JNI_FALSE;
|
||||||
JNIMonitor monitor(env, backup);
|
JNIMonitor monitor(env, backup);
|
||||||
hook_item->callbacks.emplace(std::make_pair(priority, env->NewGlobalRef(callback)));
|
if (useModernApi) {
|
||||||
|
if (before_method_field == nullptr) {
|
||||||
|
auto callback_class = JNI_GetObjectClass(env, callback);
|
||||||
|
callback_ctor = JNI_GetMethodID(env, callback_class, "<init>", "(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V");
|
||||||
|
before_method_field = JNI_GetFieldID(env, callback_class, "beforeInvocation", "Ljava/lang/reflect/Method;");
|
||||||
|
after_method_field = JNI_GetFieldID(env, callback_class, "afterInvocation", "Ljava/lang/reflect/Method;");
|
||||||
|
}
|
||||||
|
auto before_method = JNI_GetObjectField(env, callback, before_method_field);
|
||||||
|
auto after_method = JNI_GetObjectField(env, callback, after_method_field);
|
||||||
|
auto callback_type = CallbackType {
|
||||||
|
.use_modern_api = true,
|
||||||
|
.before_method = env->FromReflectedMethod(before_method),
|
||||||
|
.after_method = env->FromReflectedMethod(after_method),
|
||||||
|
};
|
||||||
|
hook_item->callbacks.emplace(std::make_pair(priority, callback_type));
|
||||||
|
} else {
|
||||||
|
auto callback_type = CallbackType {
|
||||||
|
.use_modern_api = false,
|
||||||
|
.callback_object = env->NewGlobalRef(callback),
|
||||||
|
};
|
||||||
|
hook_item->callbacks.emplace(std::make_pair(priority, callback_type));
|
||||||
|
}
|
||||||
return JNI_TRUE;
|
return JNI_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jobject hookMethod, jobject callback) {
|
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jboolean useModernApi, jobject hookMethod, jobject callback) {
|
||||||
auto target = env->FromReflectedMethod(hookMethod);
|
auto target = env->FromReflectedMethod(hookMethod);
|
||||||
HookItem * hook_item = nullptr;
|
HookItem * hook_item = nullptr;
|
||||||
{
|
{
|
||||||
|
|
@ -125,10 +159,22 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jobject hookMethod, jo
|
||||||
jobject backup = hook_item->GetBackup();
|
jobject backup = hook_item->GetBackup();
|
||||||
if (!backup) return JNI_FALSE;
|
if (!backup) return JNI_FALSE;
|
||||||
JNIMonitor monitor(env, backup);
|
JNIMonitor monitor(env, backup);
|
||||||
|
jmethodID before = nullptr;
|
||||||
|
if (useModernApi) {
|
||||||
|
auto before_method = JNI_GetObjectField(env, callback, before_method_field);
|
||||||
|
before = env->FromReflectedMethod(before_method);
|
||||||
|
}
|
||||||
for (auto i = hook_item->callbacks.begin(); i != hook_item->callbacks.end(); ++i) {
|
for (auto i = hook_item->callbacks.begin(); i != hook_item->callbacks.end(); ++i) {
|
||||||
if (env->IsSameObject(i->second, callback)) {
|
if (useModernApi) {
|
||||||
hook_item->callbacks.erase(i);
|
if (i->second.use_modern_api && before == i->second.before_method) {
|
||||||
return JNI_TRUE;
|
hook_item->callbacks.erase(i);
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!i->second.use_modern_api && env->IsSameObject(i->second.callback_object, callback)) {
|
||||||
|
hook_item->callbacks.erase(i);
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return JNI_FALSE;
|
return JNI_FALSE;
|
||||||
|
|
@ -153,7 +199,7 @@ LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeOriginalMethod, jobject hookMet
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, allocateObject, jclass cls) {
|
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, allocateObject, jclass cls) {
|
||||||
return env->AllocObject(clazz);
|
return env->AllocObject(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeSpecialMethod, jobject method, jcharArray shorty,
|
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeSpecialMethod, jobject method, jcharArray shorty,
|
||||||
|
|
@ -269,7 +315,7 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, setTrusted, jobject cookie) {
|
||||||
return lsplant::MakeDexFileTrusted(env, cookie);
|
return lsplant::MakeDexFileTrusted(env, cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jobject method) {
|
LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jclass callback_class, jobject method) {
|
||||||
auto target = env->FromReflectedMethod(method);
|
auto target = env->FromReflectedMethod(method);
|
||||||
HookItem *hook_item = nullptr;
|
HookItem *hook_item = nullptr;
|
||||||
{
|
{
|
||||||
|
|
@ -284,21 +330,28 @@ LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jobject method
|
||||||
JNIMonitor monitor(env, backup);
|
JNIMonitor monitor(env, backup);
|
||||||
auto res = env->NewObjectArray((jsize) hook_item->callbacks.size(), env->FindClass("java/lang/Object"), nullptr);
|
auto res = env->NewObjectArray((jsize) hook_item->callbacks.size(), env->FindClass("java/lang/Object"), nullptr);
|
||||||
for (jsize i = 0; auto callback: hook_item->callbacks) {
|
for (jsize i = 0; auto callback: hook_item->callbacks) {
|
||||||
env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback.second));
|
if (callback.second.use_modern_api) {
|
||||||
|
auto before_method = JNI_ToReflectedMethod(env, clazz, callback.second.before_method, JNI_TRUE);
|
||||||
|
auto after_method = JNI_ToReflectedMethod(env, clazz, callback.second.after_method, JNI_TRUE);
|
||||||
|
auto callback_object = JNI_NewObject(env, callback_class, callback_ctor, before_method, after_method);
|
||||||
|
env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback_object));
|
||||||
|
} else {
|
||||||
|
env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback.second.callback_object));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JNINativeMethod gMethods[] = {
|
static JNINativeMethod gMethods[] = {
|
||||||
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(ZLjava/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, invokeSpecialMethod, "(Ljava/lang/reflect/Executable;[CLjava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
|
LSP_NATIVE_METHOD(HookBridge, invokeSpecialMethod, "(Ljava/lang/reflect/Executable;[CLjava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, allocateObject, "(Ljava/lang/Class;)Ljava/lang/Object;"),
|
LSP_NATIVE_METHOD(HookBridge, allocateObject, "(Ljava/lang/Class;)Ljava/lang/Object;"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/reflect/Executable;)[Ljava/lang/Object;"),
|
LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/Class;Ljava/lang/reflect/Executable;)[Ljava/lang/Object;"),
|
||||||
};
|
};
|
||||||
|
|
||||||
void RegisterHookBridge(JNIEnv *env) {
|
void RegisterHookBridge(JNIEnv *env) {
|
||||||
|
|
|
||||||
|
|
@ -149,11 +149,6 @@ public class LSPModuleService extends IXposedService.Stub {
|
||||||
return IXposedService.FRAMEWORK_PRIVILEGE_ROOT;
|
return IXposedService.FRAMEWORK_PRIVILEGE_ROOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bundle featuredMethod(String name, Bundle args) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getScope() throws RemoteException {
|
public List<String> getScope() throws RemoteException {
|
||||||
ensureModule();
|
ensureModule();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue