Implement hook APIs

This commit is contained in:
LoveSy 2023-01-03 21:47:55 +08:00 committed by LoveSy
parent 06c65a5a61
commit c77617c3e1
5 changed files with 174 additions and 32 deletions

View File

@ -20,10 +20,20 @@
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.InvocationTargetException;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.util.ConcurrentModificationException;
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.XposedInterface;
/** /**
* Callback class for method hooks. * Callback class for method hooks.
@ -93,7 +103,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 extends XCallback.Param { public static final class MethodHookParam <T> extends XCallback.Param implements XposedInterface.BeforeHookCallback<T>, XposedInterface.AfterHookCallback<T> {
/** /**
* @hide * @hide
*/ */
@ -121,6 +131,8 @@ public abstract class XC_MethodHook extends XCallback {
private Throwable throwable = null; private Throwable throwable = null;
public boolean returnEarly = false; public boolean returnEarly = false;
private final HashMap<String, Object> extras = new HashMap<>();
/** /**
* Returns the result of the method call. * Returns the result of the method call.
*/ */
@ -146,6 +158,11 @@ 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.
*/ */
@ -172,6 +189,51 @@ 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;
}
@Override
public void returnAndSkip(@Nullable Object returnValue) {
setResult(returnValue);
}
@Override
public void throwAndSkip(@Nullable Throwable throwable) {
setThrowable(throwable);
}
@Nullable
@Override
public Object invokeOrigin(@Nullable Object thisObject, Object[] args) 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);
}
} }
/** /**

View File

@ -43,6 +43,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
import de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LoadPackage; import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.libxposed.XposedInterface; import io.github.libxposed.XposedInterface;
import io.github.libxposed.XposedModuleInterface;
/** /**
* This class contains most of Xposed's central logic, such as initialization and callbacks used by * This class contains most of Xposed's central logic, such as initialization and callbacks used by
@ -395,7 +396,7 @@ public final class XposedBridge {
} }
} }
public static class AdditionalHookInfo { public static class AdditionalHookInfo<T> {
private final Object params; private final Object params;
private AdditionalHookInfo(Executable method) { private AdditionalHookInfo(Executable method) {
@ -416,7 +417,7 @@ public final class XposedBridge {
// This method is quite critical. We should try not to use system methods to avoid // This method is quite critical. We should try not to use system methods to avoid
// endless recursive // 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<T> param = new XC_MethodHook.MethodHookParam<>();
var array = ((Object[]) params); var array = ((Object[]) params);
@ -451,7 +452,12 @@ public final class XposedBridge {
int beforeIdx = 0; int beforeIdx = 0;
do { do {
try { try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param); var cb = callbacksSnapshot[beforeIdx];
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
((XC_MethodHook) cb).beforeHookedMethod(param);
} else if (HookBridge.instanceOf(cb, XposedInterface.BeforeMethodHooker.class)) {
((XposedInterface.BeforeMethodHooker<T>) cb).before(param);
}
} catch (Throwable t) { } catch (Throwable t) {
XposedBridge.log(t); XposedBridge.log(t);
@ -483,8 +489,13 @@ public final class XposedBridge {
Object lastResult = param.getResult(); Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable(); Throwable lastThrowable = param.getThrowable();
var cb = callbacksSnapshot[afterIdx];
try { try {
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param); if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
((XC_MethodHook) cb).afterHookedMethod(param);
} else if (HookBridge.instanceOf(cb, XposedInterface.AfterHookCallback.class)) {
((XposedInterface.AfterMethodHooker<T>) cb).after(param);
}
} catch (Throwable t) { } catch (Throwable t) {
XposedBridge.log(t); XposedBridge.log(t);

View File

@ -1,5 +1,7 @@
package org.lsposed.lspd.impl; package org.lsposed.lspd.impl;
import static de.robv.android.xposed.callbacks.XCallback.PRIORITY_DEFAULT;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.LoadedApk; import android.app.LoadedApk;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -49,6 +51,7 @@ import java.io.FileOutputStream;
import java.io.InputStream; 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.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;
@ -56,6 +59,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit; import de.robv.android.xposed.XposedInit;
import io.github.libxposed.XposedContext; import io.github.libxposed.XposedContext;
@ -754,76 +759,102 @@ public class LSPosedContext extends XposedContext {
return BuildConfig.VERSION_CODE; return BuildConfig.VERSION_CODE;
} }
// TODO private <T, U extends Executable> MethodUnhooker<T, U> doHook(U hookMethod, int priority, T callback) {
if (Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod);
} else if (hookMethod.getDeclaringClass().getClassLoader() == XposedBridge.class.getClassLoader()) {
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) {
throw new IllegalArgumentException("callback should not be null!");
}
if (HookBridge.hookMethod(hookMethod, XposedBridge.AdditionalHookInfo.class, priority, callback)) {
return new MethodUnhooker<>() {
@NonNull
@Override
public U getOrigin() {
return hookMethod;
}
@NonNull
@Override
public T getHooker() {
return callback;
}
@Override
public void unhook() {
HookBridge.unhookMethod(hookMethod, callback);
}
};
}
log("Cannot hook " + hookMethod);
return null;
}
@Override @Override
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, @NonNull BeforeMethodHooker<Method> hooker) { public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, @NonNull BeforeMethodHooker<Method> hooker) {
return null; return doHook(origin, PRIORITY_DEFAULT, hooker);
} }
// TODO
@Override @Override
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, @NonNull AfterMethodHooker<Method> hooker) { public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, @NonNull AfterMethodHooker<Method> hooker) {
return null; return doHook(origin, PRIORITY_DEFAULT, hooker);
} }
// TODO
@Override @Override
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, @NonNull MethodHooker<Method> hooker) { public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, @NonNull MethodHooker<Method> hooker) {
return null; return doHook(origin, PRIORITY_DEFAULT, hooker);
} }
// TODO
@Override @Override
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, int priority, @NonNull BeforeMethodHooker<Method> hooker) { public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, int priority, @NonNull BeforeMethodHooker<Method> hooker) {
return null; return doHook(origin, priority, hooker);
} }
// TODO
@Override @Override
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, int priority, @NonNull AfterMethodHooker<Method> hooker) { public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, int priority, @NonNull AfterMethodHooker<Method> hooker) {
return null; return doHook(origin, priority, hooker);
} }
// TODO
@Override @Override
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, int priority, @NonNull MethodHooker<Method> hooker) { public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, int priority, @NonNull MethodHooker<Method> hooker) {
return null; return doHook(origin, priority, hooker);
} }
// TODO
@Override @Override
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, @NonNull BeforeMethodHooker<Constructor<T>> hooker) { public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
return null; return doHook(origin, PRIORITY_DEFAULT, hooker);
} }
// TODO
@Override @Override
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, @NonNull AfterMethodHooker<Constructor<T>> hooker) { public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
return null; return doHook(origin, PRIORITY_DEFAULT, hooker);
} }
// TODO
@Override @Override
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull MethodHooker<Constructor<T>> hooker) { public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull MethodHooker<Constructor<T>> hooker) {
return null; return doHook(origin, PRIORITY_DEFAULT, hooker);
} }
// TODO // TODO
@Override @Override
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, int priority, @NonNull BeforeMethodHooker<Constructor<T>> hooker) { public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, int priority, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
return null; return doHook(origin, priority, hooker);
} }
// TODO
@Override @Override
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, int priority, @NonNull AfterMethodHooker<Constructor<T>> hooker) { public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, int priority, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
return null; return doHook(origin, priority, hooker);
} }
// TODO
@Override @Override
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull MethodHooker<Constructor<T>> hooker) { public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull MethodHooker<Constructor<T>> hooker) {
return null; return doHook(origin, priority, hooker);
} }
private static boolean deoptimize(@NonNull Executable method) { private static boolean deoptimize(@NonNull Executable method) {
@ -845,11 +876,10 @@ public class LSPosedContext extends XposedContext {
return deoptimize((Executable) constructor); return deoptimize((Executable) constructor);
} }
// TODO
@Nullable @Nullable
@Override @Override
public XposedUtils getUtils() { public XposedUtils getUtils() {
return null; return new LSPosedUtils(this);
} }
@Override @Override

View File

@ -0,0 +1,38 @@
package org.lsposed.lspd.impl;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.libxposed.XposedUtils;
public class LSPosedUtils implements XposedUtils {
private final LSPosedContext context;
LSPosedUtils(LSPosedContext context) {
this.context = context;
}
@NonNull
@Override
public <T> Class<T> classByName(@NonNull String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
throw new AbstractMethodError();
}
@Nullable
@Override
public <T> Class<T> classOrNullByName(@NonNull String className, @Nullable ClassLoader classLoader) {
throw new AbstractMethodError();
}
@NonNull
@Override
public <T> Class<T> classByBinaryName(@NonNull String binaryClassName, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
throw new AbstractMethodError();
}
@Nullable
@Override
public <T> Class<T> classOrNullByBinaryName(@NonNull String binaryClassName, @Nullable ClassLoader classLoader) {
throw new AbstractMethodError();
}
}

View File

@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
@ -25,7 +26,7 @@ public interface XposedInterface {
void throwAndSkip(@Nullable Throwable throwable); void throwAndSkip(@Nullable Throwable throwable);
@Nullable @Nullable
Object invokeOrigin(@Nullable Object thisObject, Object[] args); Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException;
<U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException; <U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException;
} }
@ -53,7 +54,7 @@ public interface XposedInterface {
void setThrowable(@Nullable Throwable throwable); void setThrowable(@Nullable Throwable throwable);
@Nullable @Nullable
Object invokeOrigin(@Nullable Object thisObject, Object[] args); Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException;
@Nullable @Nullable
<U> U getExtra(@NonNull String key); <U> U getExtra(@NonNull String key);