Implement invoke special and new instance special

This commit is contained in:
LoveSy 2023-01-24 19:58:57 +08:00 committed by LoveSy
parent 54483ab89b
commit 4444825e3e
4 changed files with 169 additions and 25 deletions

View File

@ -229,12 +229,6 @@ public abstract class XC_MethodHook extends XCallback {
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 Object invokeOrigin() throws InvocationTargetException, IllegalAccessException {

View File

@ -968,14 +968,71 @@ public class LSPosedContext extends XposedContext {
@Nullable
@Override
public Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException {
public Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
return HookBridge.invokeOriginalMethod(method, thisObject, args);
}
private static char getTypeShorty(Class<?> type) {
if (type == int.class) {
return 'I';
} else if (type == long.class) {
return 'J';
} else if (type == float.class) {
return 'F';
} else if (type == double.class) {
return 'D';
} else if (type == boolean.class) {
return 'Z';
} else if (type == byte.class) {
return 'B';
} else if (type == char.class) {
return 'C';
} else if (type == short.class) {
return 'S';
} else if (type == void.class) {
return 'V';
} else {
return 'L';
}
}
private static char[] getExecutableShorty(Executable executable) {
var parameterTypes = executable.getParameterTypes();
var shorty = new char[parameterTypes.length + 1];
shorty[0] = getTypeShorty(executable instanceof Method ? ((Method) executable).getReturnType() : void.class);
for (int i = 1; i < shorty.length; i++) {
shorty[i] = getTypeShorty(parameterTypes[i - 1]);
}
return shorty;
}
@Nullable
@Override
public <T> T newInstanceOrigin(@NonNull Constructor<T> constructor, Object[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
return HookBridge.invokeOriginalConstructor(constructor, constructor.getDeclaringClass(), args);
public Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException("Cannot invoke special on static method: " + method);
}
return HookBridge.invokeSpecialMethod(method, getExecutableShorty(method), method.getDeclaringClass(), thisObject, args);
}
@NonNull
@Override
public <T> T newInstanceOrigin(@NonNull Constructor<T> constructor, Object... args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
var obj = HookBridge.allocateObject(constructor.getDeclaringClass());
HookBridge.invokeOriginalMethod(constructor, obj, args);
return obj;
}
@NonNull
@Override
public <T, U extends T> U newInstanceSpecial(@NonNull Constructor<T> constructor, @NonNull Class<U> subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException {
var superClass = constructor.getDeclaringClass();
if (!superClass.isAssignableFrom(subClass)) {
throw new IllegalArgumentException(subClass + " is not inherited from " + superClass);
}
var obj = HookBridge.allocateObject(subClass);
HookBridge.invokeSpecialMethod(constructor, getExecutableShorty(constructor), superClass, obj, args);
return obj;
}
@Override

View File

@ -13,9 +13,11 @@ public class HookBridge {
public static native boolean deoptimizeMethod(Executable method);
public static native <T> T allocateObject(Class<T> clazz) throws InstantiationException;
public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
public static native <T> T invokeOriginalConstructor(Constructor<T> method, Class<T> clazz, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
public static native <T> Object invokeSpecialMethod(Executable method, char[] shorty, Class<T> clazz, Object thisObject, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
@FastNative
public static native boolean instanceOf(Object obj, Class<?> clazz);

View File

@ -143,23 +143,113 @@ LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeOriginalMethod, jobject hookMet
return env->CallObjectMethod(to_call, invoke, thiz, args);
}
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeOriginalConstructor, jobject hookConstructor,
jclass cls, jobjectArray args) {
auto target = env->FromReflectedMethod(hookConstructor);
HookItem * hook_item = nullptr;
{
std::shared_lock lk(hooked_lock);
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
hook_item = found->second.get();
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, allocateObject, jclass cls) {
return env->AllocObject(clazz);
}
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeSpecialMethod, jobject method, jcharArray shorty,
jclass cls, jobject thiz, jobjectArray args) {
static auto* const get_int = env->GetMethodID(env->FindClass("java/lang/Integer"), "intValue", "()I");
static auto* const get_double = env->GetMethodID(env->FindClass("java/lang/Double"), "doubleValue", "()D");
static auto* const get_long = env->GetMethodID(env->FindClass("java/lang/Long"), "longValue", "()J");
static auto* const get_float = env->GetMethodID(env->FindClass("java/lang/Float"), "floatValue", "()F");
static auto* const get_short = env->GetMethodID(env->FindClass("java/lang/Short"), "shortValue", "()S");
static auto* const get_byte = env->GetMethodID(env->FindClass("java/lang/Byte"), "byteValue", "()B");
static auto* const get_char = env->GetMethodID(env->FindClass("java/lang/Character"), "charValue", "()C");
static auto* const get_boolean = env->GetMethodID(env->FindClass("java/lang/Boolean"), "booleanValue", "()Z");
static auto* const set_int = env->GetStaticMethodID(env->FindClass("java/lang/Integer"), "valueOf", "(I)Ljava/lang/Integer;");
static auto* const set_double = env->GetStaticMethodID(env->FindClass("java/lang/Double"), "valueOf", "(D)Ljava/lang/Double;");
static auto* const set_long = env->GetStaticMethodID(env->FindClass("java/lang/Long"), "valueOf", "(J)Ljava/lang/Long;");
static auto* const set_float = env->GetStaticMethodID(env->FindClass("java/lang/Float"), "valueOf", "(F)Ljava/lang/Float;");
static auto* const set_short = env->GetStaticMethodID(env->FindClass("java/lang/Short"), "valueOf", "(S)Ljava/lang/Short;");
static auto* const set_byte = env->GetStaticMethodID(env->FindClass("java/lang/Byte"), "valueOf", "(B)Ljava/lang/Byte;");
static auto* const set_char = env->GetStaticMethodID(env->FindClass("java/lang/Character"), "valueOf", "(C)Ljava/lang/Character;");
static auto* const set_boolean = env->GetStaticMethodID(env->FindClass("java/lang/Boolean"), "valueOf", "(Z)Ljava/lang/Boolean;");
auto target = env->FromReflectedMethod(method);
auto param_len = env->GetArrayLength(shorty) - 1;
if (env->GetArrayLength(args) != param_len) {
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), "args.length != parameters.length");
return nullptr;
}
if (thiz == nullptr) {
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), "this == null");
return nullptr;
}
std::vector<jvalue> a(param_len);
auto *const shorty_char = env->GetCharArrayElements(shorty, nullptr);
for (jint i = 0; i != param_len; ++i) {
jobject element = nullptr;
switch(shorty_char[i + 1]) {
case 'I':
a[i].i = env->CallIntMethod(element = env->GetObjectArrayElement(args, i), get_int);
break;
case 'D':
a[i].d = env->CallDoubleMethod(element = env->GetObjectArrayElement(args, i), get_double);
break;
case 'J':
a[i].j = env->CallLongMethod(element = env->GetObjectArrayElement(args, i), get_long);
break;
case 'F':
a[i].f = env->CallFloatMethod(element = env->GetObjectArrayElement(args, i), get_float);
break;
case 'S':
a[i].s = env->CallShortMethod(element = env->GetObjectArrayElement(args, i), get_short);
break;
case 'B':
a[i].b = env->CallByteMethod(element = env->GetObjectArrayElement(args, i), get_byte);
break;
case 'C':
a[i].c = env->CallCharMethod(element = env->GetObjectArrayElement(args, i), get_char);
break;
case 'Z':
a[i].z = env->CallBooleanMethod(element = env->GetObjectArrayElement(args, i), get_boolean);
break;
default:
case 'L':
a[i].l = env->GetObjectArrayElement(args, i);
element = nullptr;
break;
}
if (element) env->DeleteLocalRef(element);
if (env->ExceptionCheck()) return nullptr;
}
jobject to_call = hookConstructor;
if (hook_item) {
std::unique_lock lk(backup_lock);
if (hook_item->backup) to_call = hook_item->backup;
jobject value = nullptr;
switch(shorty_char[0]) {
case 'I':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_int, env->CallNonvirtualIntMethodA(thiz, cls, target, a.data()));
break;
case 'D':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_double, env->CallNonvirtualDoubleMethodA(thiz, cls, target, a.data()));
break;
case 'J':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_long, env->CallNonvirtualLongMethodA(thiz, cls, target, a.data()));
break;
case 'F':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_float, env->CallNonvirtualFloatMethodA(thiz, cls, target, a.data()));
break;
case 'S':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_short, env->CallNonvirtualShortMethodA(thiz, cls, target, a.data()));
break;
case 'B':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_byte, env->CallNonvirtualByteMethodA(thiz, cls, target, a.data()));
break;
case 'C':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_char, env->CallNonvirtualCharMethodA(thiz, cls, target, a.data()));
break;
case 'Z':
value = env->CallStaticObjectMethod(jclass{nullptr}, set_boolean, env->CallNonvirtualBooleanMethodA(thiz, cls, target, a.data()));
break;
case 'L':
value = env->CallNonvirtualObjectMethodA(thiz, cls, target, a.data());
break;
default:
case 'V':
env->CallNonvirtualVoidMethodA(thiz, cls, target, a.data());
break;
}
auto thiz = env->AllocObject(cls);
return env->CallObjectMethod(to_call, invoke, thiz, args);
env->ReleaseCharArrayElements(shorty, shorty_char, JNI_ABORT);
return value;
}
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, instanceOf, jobject object, jclass expected_class) {
@ -199,7 +289,8 @@ static JNINativeMethod gMethods[] = {
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, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
LSP_NATIVE_METHOD(HookBridge, invokeOriginalConstructor, "(Ljava/lang/reflect/Constructor;Ljava/lang/Class;[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, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"),
LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/reflect/Executable;)[Ljava/lang/Object;"),