Revert YAHFA to the version where backup are memcpied

This commit is contained in:
LoveSy 2021-01-28 17:44:46 +08:00
parent 7f08ddcc21
commit b04d830fba
17 changed files with 398 additions and 599 deletions

View File

@ -17,21 +17,12 @@ jboolean Java_lab_galaxy_yahfa_HookMain_backupAndHookNative(JNIEnv *env, jclass
jobject target, jobject hook,
jobject backup);
void Java_lab_galaxy_yahfa_HookMain_ensureMethodCached(JNIEnv *env, jclass clazz,
jobject hook,
jobject backup);
#ifdef __cplusplus
}
#endif
void setNonCompilable(void *method);
void *getArtMethod(JNIEnv *env, jobject jmethod);
// TODO: move to common utils instead of in YAHFA's code
void *getEntryPoint(void* method);
#ifdef __cplusplus
}
#endif
// get original entrypoint from target ArtMethod
void *getOriginalEntryPointFromTargetMethod(void* method);
#endif // HOOK_MAIN_H
#endif // HOOK_MAIN_H

View File

@ -6,12 +6,17 @@
#define YAHFA_TAMPOLINE_H
extern int SDKVersion;
extern int OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
extern unsigned int hookCap; // capacity for trampolines
extern unsigned int hookCount; // current count of used trampolines
extern unsigned char trampoline[];
extern unsigned char trampolineForBackup[];
void* doInitHookCap(size_t cap);
void setupTrampoline(uint8_t offset);
void *genTrampoline(void *toMethod, void *entrypoint);
int doInitHookCap(unsigned int cap);
void setupTrampoline();
void *genTrampoline(void *hookMethod);
#define DEFAULT_CAP 1 //size of each trampoline area would be no more than 4k Bytes(one page)
#endif //YAHFA_TAMPOLINE_H

View File

@ -0,0 +1,252 @@
#include "jni.h"
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdbool.h>
#include "common.h"
#include "trampoline.h"
#include "HookMain.h"
int SDKVersion;
static int OFFSET_entry_point_from_interpreter_in_ArtMethod;
int OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
static int OFFSET_ArtMehod_in_Object;
static int OFFSET_access_flags_in_ArtMethod;
static size_t ArtMethodSize;
static uint32_t kAccCompileDontBother = 0x01000000;
static uint32_t kAccPublic = 0x0001; // class, field, method, ic
static uint32_t kAccPrivate = 0x0002; // field, method, ic
static uint32_t kAccProtected = 0x0004; // field, method, ic
static uint32_t kAccStatic = 0x0008; // field, method, ic
static jfieldID fieldArtMethod = NULL;
static inline uint32_t read32(void *addr) {
return *((uint32_t *) addr);
}
static inline void write32(void *addr, uint32_t value) {
*((uint32_t *) addr) = value;
}
static inline void *readAddr(void *addr) {
return *((void **) addr);
}
static inline void writeAddr(void *addr, void *value) {
*((void **) addr) = value;
}
void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVersion) {
SDKVersion = sdkVersion;
jclass classExecutable;
LOGI("init to SDK %d", sdkVersion);
switch (sdkVersion) {
case __ANDROID_API_R__:
classExecutable = (*env)->FindClass(env, "java/lang/reflect/Executable");
fieldArtMethod = (*env)->GetFieldID(env, classExecutable, "artMethod", "J");
case __ANDROID_API_Q__:
case __ANDROID_API_P__:
kAccCompileDontBother = 0x02000000;
OFFSET_ArtMehod_in_Object = 0;
OFFSET_access_flags_in_ArtMethod = 4;
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size;
ArtMethodSize = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 2;
break;
case __ANDROID_API_O_MR1__:
kAccCompileDontBother = 0x02000000;
case __ANDROID_API_O__:
OFFSET_ArtMehod_in_Object = 0;
OFFSET_access_flags_in_ArtMethod = 4;
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 2;
ArtMethodSize = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 3;
break;
case __ANDROID_API_N_MR1__:
case __ANDROID_API_N__:
OFFSET_ArtMehod_in_Object = 0;
OFFSET_access_flags_in_ArtMethod = 4; // sizeof(GcRoot<mirror::Class>) = 4
// ptr_sized_fields_ is rounded up to pointer_size in ArtMethod
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 3;
ArtMethodSize = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 4;
break;
case __ANDROID_API_M__:
OFFSET_ArtMehod_in_Object = 0;
OFFSET_entry_point_from_interpreter_in_ArtMethod = roundUpToPtrSize(4 * 7);
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
OFFSET_entry_point_from_interpreter_in_ArtMethod + pointer_size * 2;
ArtMethodSize = roundUpToPtrSize(4 * 7) + pointer_size * 3;
break;
case __ANDROID_API_L_MR1__:
OFFSET_ArtMehod_in_Object = 4 * 2;
OFFSET_entry_point_from_interpreter_in_ArtMethod = roundUpToPtrSize(
OFFSET_ArtMehod_in_Object + 4 * 7);
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
OFFSET_entry_point_from_interpreter_in_ArtMethod + pointer_size * 2;
ArtMethodSize = OFFSET_entry_point_from_interpreter_in_ArtMethod + pointer_size * 3;
break;
case __ANDROID_API_L__:
OFFSET_ArtMehod_in_Object = 4 * 2;
OFFSET_entry_point_from_interpreter_in_ArtMethod = OFFSET_ArtMehod_in_Object + 4 * 4;
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
OFFSET_entry_point_from_interpreter_in_ArtMethod + 8 * 2;
ArtMethodSize = OFFSET_ArtMehod_in_Object + 4 * 4 + 8 * 4 + 4 * 4;
break;
default:
LOGE("not compatible with SDK %d", sdkVersion);
break;
}
setupTrampoline();
}
void setNonCompilable(void *method) {
if (SDKVersion < __ANDROID_API_N__) {
return;
}
uint32_t access_flags = read32((char *) method + OFFSET_access_flags_in_ArtMethod);
LOGI("setNonCompilable: access flags is 0x%x", access_flags);
access_flags |= kAccCompileDontBother;
write32((char *) method + OFFSET_access_flags_in_ArtMethod, access_flags);
}
void setPrivate(void *method) {
uint32_t access_flags = read32((char *) method + OFFSET_access_flags_in_ArtMethod);
if (!(access_flags & kAccStatic)) {
LOGI("setPrivate: access flags is 0x%x", access_flags);
access_flags |= kAccPrivate;
access_flags &= ~kAccProtected;
access_flags &= ~kAccPublic;
write32((char *) method + OFFSET_access_flags_in_ArtMethod, access_flags);
}
}
static int doBackupAndHook(JNIEnv *env, void *targetMethod, void *hookMethod, void *backupMethod) {
if (hookCount >= hookCap) {
LOGI("not enough capacity. Allocating...");
if (doInitHookCap(DEFAULT_CAP)) {
LOGE("cannot hook method");
return 1;
}
LOGI("Allocating done");
}
LOGI("target method is at %p, hook method is at %p, backup method is at %p",
targetMethod, hookMethod, backupMethod);
// set kAccCompileDontBother for a method we do not want the compiler to compile
// so that we don't need to worry about hotness_count_
if (SDKVersion >= __ANDROID_API_N__) {
setNonCompilable(targetMethod);
setNonCompilable(hookMethod);
}
if (backupMethod) {// do method backup
// have to copy the whole target ArtMethod here
// if the target method calls other methods which are to be resolved
// then ToDexPC would be invoked for the caller(origin method)
// in which case ToDexPC would use the entrypoint as a base for mapping pc to dex offset
// so any changes to the target method's entrypoint would result in a wrong dex offset
// and artQuickResolutionTrampoline would fail for methods called by the origin method
memcpy(backupMethod, targetMethod, ArtMethodSize);
setPrivate(backupMethod);
}
// replace entry point
void *newEntrypoint = genTrampoline(hookMethod);
LOGI("origin ep is %p, new ep is %p",
readAddr((char *) targetMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod),
newEntrypoint
);
if (newEntrypoint) {
writeAddr((char *) targetMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod,
newEntrypoint);
} else {
LOGE("failed to allocate space for trampoline of target method");
return 1;
}
if (OFFSET_entry_point_from_interpreter_in_ArtMethod != 0) {
writeAddr((char *) targetMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod,
readAddr((char *) hookMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod));
}
// set the target method to native so that Android O wouldn't invoke it with interpreter
if (SDKVersion >= __ANDROID_API_O__) {
// setNativeFlag(targetMethod, true);
}
LOGI("hook and backup done");
hookCount += 1;
return 0;
}
void *getArtMethod(JNIEnv *env, jobject jmethod) {
void *artMethod = NULL;
if (jmethod == NULL) {
return artMethod;
}
if (SDKVersion == __ANDROID_API_R__) {
artMethod = (void *) (*env)->GetLongField(env, jmethod, fieldArtMethod);
} else {
artMethod = (void *) (*env)->FromReflectedMethod(env, jmethod);
}
LOGI("ArtMethod: %p", artMethod);
return artMethod;
}
jobject Java_lab_galaxy_yahfa_HookMain_findMethodNative(JNIEnv *env, jclass clazz,
jclass targetClass, jstring methodName,
jstring methodSig) {
const char *c_methodName = (*env)->GetStringUTFChars(env, methodName, NULL);
const char *c_methodSig = (*env)->GetStringUTFChars(env, methodSig, NULL);
jobject ret = NULL;
//Try both GetMethodID and GetStaticMethodID -- Whatever works :)
jmethodID method = (*env)->GetMethodID(env, targetClass, c_methodName, c_methodSig);
if (!(*env)->ExceptionCheck(env)) {
ret = (*env)->ToReflectedMethod(env, targetClass, method, JNI_FALSE);
} else {
(*env)->ExceptionClear(env);
method = (*env)->GetStaticMethodID(env, targetClass, c_methodName, c_methodSig);
if (!(*env)->ExceptionCheck(env)) {
ret = (*env)->ToReflectedMethod(env, targetClass, method, JNI_TRUE);
} else {
(*env)->ExceptionClear(env);
}
}
(*env)->ReleaseStringUTFChars(env, methodName, c_methodName);
(*env)->ReleaseStringUTFChars(env, methodSig, c_methodSig);
return ret;
}
jboolean Java_lab_galaxy_yahfa_HookMain_backupAndHookNative(JNIEnv *env, jclass clazz,
jobject target, jobject hook,
jobject backup) {
if (!doBackupAndHook(env,
getArtMethod(env, target),
getArtMethod(env, hook),
getArtMethod(env, backup)
)) {
(*env)->NewGlobalRef(env,
hook); // keep a global ref so that the hook method would not be GCed
if (backup) (*env)->NewGlobalRef(env, backup);
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}

View File

@ -1,257 +0,0 @@
#include "jni.h"
#include <sys/mman.h>
#include <cstdlib>
#include <unordered_map>
#include "common.h"
#include "HookMain.h"
extern "C" {
#include "trampoline.h"
}
int SDKVersion;
namespace {
uint32_t OFFSET_entry_point_from_interpreter_in_ArtMethod;
uint32_t OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
uint32_t OFFSET_ArtMehod_in_Object;
uint32_t OFFSET_access_flags_in_ArtMethod;
uint32_t kAccCompileDontBother = 0x01000000;
uint32_t kAccNative = 0x0100;
uint32_t kAccFastInterpreterToInterpreterInvoke = 0x40000000;
jfieldID fieldArtMethod = nullptr;
std::unordered_map<void *, void *> replaced_entrypoint;
inline uint32_t read32(void *addr) {
return *((uint32_t *) addr);
}
inline void write32(void *addr, uint32_t value) {
*((uint32_t *) addr) = value;
}
inline void *readAddr(void *addr) {
return *((void **) addr);
}
inline void writeAddr(void *addr, void *value) {
*((void **) addr) = value;
}
}
extern "C" void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVersion) {
SDKVersion = sdkVersion;
jclass classExecutable;
LOGI("init to SDK %d", sdkVersion);
switch (sdkVersion) {
case __ANDROID_API_R__:
classExecutable = env->FindClass("java/lang/reflect/Executable");
fieldArtMethod = env->GetFieldID(classExecutable, "artMethod", "J");
case __ANDROID_API_Q__:
case __ANDROID_API_P__:
kAccCompileDontBother = 0x02000000;
OFFSET_ArtMehod_in_Object = 0;
OFFSET_access_flags_in_ArtMethod = 4;
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size;
break;
case __ANDROID_API_O_MR1__:
kAccCompileDontBother = 0x02000000;
case __ANDROID_API_O__:
OFFSET_ArtMehod_in_Object = 0;
OFFSET_access_flags_in_ArtMethod = 4;
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 2;
break;
case __ANDROID_API_N_MR1__:
case __ANDROID_API_N__:
OFFSET_ArtMehod_in_Object = 0;
OFFSET_access_flags_in_ArtMethod = 4; // sizeof(GcRoot<mirror::Class>) = 4
// ptr_sized_fields_ is rounded up to pointer_size in ArtMethod
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 3;
break;
case __ANDROID_API_M__:
OFFSET_ArtMehod_in_Object = 0;
OFFSET_entry_point_from_interpreter_in_ArtMethod = roundUpToPtrSize(4 * 7);
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
OFFSET_entry_point_from_interpreter_in_ArtMethod + pointer_size * 2;
break;
case __ANDROID_API_L_MR1__:
OFFSET_ArtMehod_in_Object = 4 * 2;
OFFSET_entry_point_from_interpreter_in_ArtMethod = roundUpToPtrSize(
OFFSET_ArtMehod_in_Object + 4 * 7);
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
OFFSET_entry_point_from_interpreter_in_ArtMethod + pointer_size * 2;
break;
case __ANDROID_API_L__:
OFFSET_ArtMehod_in_Object = 4 * 2;
OFFSET_entry_point_from_interpreter_in_ArtMethod = OFFSET_ArtMehod_in_Object + 4 * 4;
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
OFFSET_entry_point_from_interpreter_in_ArtMethod + 8 * 2;
break;
default:
LOGE("not compatible with SDK %d", sdkVersion);
break;
}
setupTrampoline(OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
}
static uint32_t getFlags(void *method_) {
char *method = (char *) method_;
uint32_t access_flags = read32(method + OFFSET_access_flags_in_ArtMethod);
return access_flags;
}
static void setFlags(void *method_, uint32_t access_flags) {
char *method = (char *) method_;
write32(method + OFFSET_access_flags_in_ArtMethod, access_flags);
}
void setNonCompilable(void *method) {
if (SDKVersion < __ANDROID_API_N__) {
return;
}
uint32_t access_flags = getFlags(method);
uint32_t old_flags = access_flags;
access_flags |= kAccCompileDontBother;
setFlags(method, access_flags);
LOGI("setNonCompilable: change access flags from 0x%x to 0x%x", old_flags, access_flags);
}
void *getEntryPoint(void *method) {
return readAddr((char *) method + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
}
static int replaceMethod(void *fromMethod, void *toMethod, int isBackup) {
// replace entry point
void *newEntrypoint = nullptr;
void *fromEntrypoint =
(char *) fromMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
if (isBackup) {
void *originEntrypoint = readAddr(
(char *) toMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
// entry point hardcoded
newEntrypoint = genTrampoline(toMethod, originEntrypoint);
} else {
// entry point from ArtMethod struct
newEntrypoint = genTrampoline(toMethod, nullptr);
}
replaced_entrypoint[fromMethod] = readAddr(fromEntrypoint);
LOGI("replace entry point from %p to %p", readAddr(fromEntrypoint), newEntrypoint);
if (newEntrypoint) {
writeAddr(fromEntrypoint, newEntrypoint);
} else {
LOGE("failed to allocate space for trampoline of target method");
return 1;
}
// For pre Android M devices, should be not used by EdXposed.
if (OFFSET_entry_point_from_interpreter_in_ArtMethod != 0) {
void *interpEntrypoint = readAddr(
(char *) toMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod);
writeAddr(fromEntrypoint, interpEntrypoint);
}
return 0;
}
static int doBackupAndHook(void *targetMethod, void *hookMethod, void *backupMethod) {
LOGI("target method is at %p, hook method is at %p, backup method is at %p",
targetMethod, hookMethod, backupMethod);
int res = 0;
// set kAccCompileDontBother for a method we do not want the compiler to compile
// so that we don't need to worry about hotness_count_
if (SDKVersion >= __ANDROID_API_N__) {
setNonCompilable(targetMethod);
// setNonCompilable(hookMethod);
if (backupMethod) setNonCompilable(backupMethod);
}
if (backupMethod) {// do method backup
// we use the same way as hooking target method
// hook backup method and redirect back to the original target method
// the only difference is that the entry point is now hardcoded
// instead of reading from ArtMethod struct since it's overwritten
res += replaceMethod(backupMethod, targetMethod, 1);
}
res += replaceMethod(targetMethod, hookMethod, 0);
LOGI("hook and backup done");
return res;
}
void *getArtMethod(JNIEnv *env, jobject jmethod) {
void *artMethod = nullptr;
if (jmethod == nullptr) {
return artMethod;
}
if (SDKVersion == __ANDROID_API_R__) {
artMethod = (void *) env->GetLongField(jmethod, fieldArtMethod);
} else {
artMethod = (void *) env->FromReflectedMethod(jmethod);
}
LOGI("HookMain: getArtMethod: %p", artMethod);
return artMethod;
}
extern "C" jobject Java_lab_galaxy_yahfa_HookMain_findMethodNative(JNIEnv *env, jclass clazz,
jclass targetClass,
jstring methodName,
jstring methodSig) {
const char *c_methodName = env->GetStringUTFChars(methodName, nullptr);
const char *c_methodSig = env->GetStringUTFChars(methodSig, nullptr);
jobject ret = nullptr;
//Try both GetMethodID and GetStaticMethodID -- Whatever works :)
jmethodID method = env->GetMethodID(targetClass, c_methodName, c_methodSig);
if (!env->ExceptionCheck()) {
ret = env->ToReflectedMethod(targetClass, method, JNI_FALSE);
} else {
env->ExceptionClear();
method = env->GetStaticMethodID(targetClass, c_methodName, c_methodSig);
if (!env->ExceptionCheck()) {
ret = env->ToReflectedMethod(targetClass, method, JNI_TRUE);
} else {
env->ExceptionClear();
}
}
env->ReleaseStringUTFChars(methodName, c_methodName);
env->ReleaseStringUTFChars(methodSig, c_methodSig);
return ret;
}
extern "C" jboolean Java_lab_galaxy_yahfa_HookMain_backupAndHookNative(JNIEnv *env, jclass clazz,
jobject target, jobject hook,
jobject backup) {
if (!doBackupAndHook(getArtMethod(env, target),
getArtMethod(env, hook),
getArtMethod(env, backup)
)) {
env->NewGlobalRef(hook); // keep a global ref so that the hook method would not be GCed
if (backup) env->NewGlobalRef(backup);
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
void *getOriginalEntryPointFromTargetMethod(void *method) {
return replaced_entrypoint[method];
}

View File

@ -14,17 +14,15 @@
#include "common.h"
#include "trampoline.h"
#define MAX(a, b) ((a) > (b) ? (a) : (b))
static unsigned char *trampolineCode; // place where trampolines are saved
static unsigned int trampolineSize; // trampoline size required for each hook
unsigned int hookCap = 0;
unsigned int hookCount = 0;
// trampoline:
// 1. set eax/rdi/r0/x0 to the hook ArtMethod addr
// 1. set eax/r0/x0 to the hook ArtMethod addr
// 2. jump into its entry point
// trampoline for backup:
// 1. set eax/rdi/r0/x0 to the target ArtMethod addr
// 2. ret to the hardcoded original entry point
#if defined(__i386__)
// b8 78 56 34 12 ; mov eax, 0x12345678 (addr of the hook method)
// ff 70 20 ; push DWORD PTR [eax + 0x20]
@ -35,15 +33,6 @@ unsigned char trampoline[] = {
0xc3
};
// b8 78 56 34 12 ; mov eax, 0x12345678 (addr of the target method)
// 68 78 56 34 12 ; push 0x12345678 (original entry point of the target method)
// c3 ; ret
unsigned char trampolineForBackup[] = {
0xb8, 0x78, 0x56, 0x34, 0x12,
0x68, 0x78, 0x56, 0x34, 0x12,
0xc3
};
#elif defined(__x86_64__)
// 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678
// ff 77 20 ; push QWORD PTR [rdi + 0x20]
@ -54,17 +43,6 @@ unsigned char trampoline[] = {
0xc3
};
// 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678
// 57 ; push rdi (original entry point of the target method)
// 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678 (addr of the target method)
// c3 ; ret
unsigned char trampolineForBackup[] = {
0x48, 0xbf, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
0x57,
0x48, 0xbf, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
0xc3
};
#elif defined(__arm__)
// 00 00 9F E5 ; ldr r0, [pc, #0]
// 20 F0 90 E5 ; ldr pc, [r0, 0x20]
@ -75,21 +53,6 @@ unsigned char trampoline[] = {
0x78, 0x56, 0x34, 0x12
};
// 0c 00 9F E5 ; ldr r0, [pc, #12]
// 01 00 2d e9 ; push {r0}
// 00 00 9F E5 ; ldr r0, [pc, #0]
// 00 80 bd e8 ; pop {pc}
// 78 56 34 12 ; 0x12345678 (addr of the hook method)
// 78 56 34 12 ; 0x12345678 (original entry point of the target method)
unsigned char trampolineForBackup[] = {
0x0c, 0x00, 0x9f, 0xe5,
0x01, 0x00, 0x2d, 0xe9,
0x00, 0x00, 0x9f, 0xe5,
0x00, 0x80, 0xbd, 0xe8,
0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12
};
#elif defined(__aarch64__)
// 60 00 00 58 ; ldr x0, 12
// 10 00 40 F8 ; ldr x16, [x0, #0x00]
@ -103,113 +66,75 @@ unsigned char trampoline[] = {
0x78, 0x56, 0x34, 0x12,
0x89, 0x67, 0x45, 0x23
};
// 60 00 00 58 ; ldr x0, 12
// 90 00 00 58 ; ldr x16, 16
// 00 02 1f d6 ; br x16
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (original entry point of the target method)
unsigned char trampolineForBackup[] = {
0x60, 0x00, 0x00, 0x58,
0x90, 0x00, 0x00, 0x58,
0x00, 0x02, 0x1f, 0xd6,
0x78, 0x56, 0x34, 0x12,
0x89, 0x67, 0x45, 0x23,
0x78, 0x56, 0x34, 0x12,
0x89, 0x67, 0x45, 0x23
};
#endif
static unsigned int trampolineSize = roundUpToPtrSize(sizeof(trampoline));
static inline void FlushCache(void *addr, size_t size) {
__builtin___clear_cache((char *) addr, (char *) ((uintptr_t) addr + size));
}
void *genTrampoline(void *toMethod, void *entrypoint) {
size_t size = entrypoint == NULL ? sizeof(trampoline) : sizeof(trampolineForBackup);
void *genTrampoline(void *hookMethod) {
void *targetAddr;
// TODO: make use of thread_local to avoid frequent memory allocate
char *targetAddr = doInitHookCap(size);
if (targetAddr == NULL) return NULL;
if (entrypoint != NULL) {
memcpy(targetAddr, trampolineForBackup, size);
} else {
memcpy(targetAddr, trampoline, size);
}
targetAddr = trampolineCode + trampolineSize * hookCount;
memcpy(targetAddr, trampoline,
sizeof(trampoline)); // do not use trampolineSize since it's a rounded size
// replace with the actual ArtMethod addr
#if defined(__i386__)
if(entrypoint) {
memcpy(targetAddr + 1, &toMethod, pointer_size);
memcpy(targetAddr + 6, &entrypoint, pointer_size);
}
else {
memcpy(targetAddr + 1, &toMethod, pointer_size);
}
memcpy(targetAddr+1, &hookMethod, pointer_size);
#elif defined(__x86_64__)
if(entrypoint) {
memcpy(targetAddr + 2, &entrypoint, pointer_size);
memcpy(targetAddr + 13, &toMethod, pointer_size);
}
else {
memcpy(targetAddr + 2, &toMethod, pointer_size);
}
memcpy((char*)targetAddr + 2, &hookMethod, pointer_size);
#elif defined(__arm__)
if(entrypoint) {
memcpy(targetAddr + 20, &entrypoint, pointer_size);
memcpy(targetAddr + 16, &toMethod, pointer_size);
}
else {
memcpy(targetAddr + 8, &toMethod, pointer_size);
}
memcpy(targetAddr+8, &hookMethod, pointer_size);
#elif defined(__aarch64__)
if (entrypoint) {
memcpy(targetAddr + 20, &entrypoint, pointer_size);
memcpy(targetAddr + 12, &toMethod, pointer_size);
} else {
memcpy(targetAddr + 12, &toMethod, pointer_size);
}
memcpy(targetAddr + 12, &hookMethod, pointer_size);
#else
#error Unsupported architecture
#endif
FlushCache(targetAddr, size);
FlushCache(targetAddr, sizeof(trampoline));
return targetAddr;
}
void setupTrampoline(uint8_t offset) {
void setupTrampoline() {
#if defined(__i386__)
trampoline[7] = offset;
trampoline[7] = (unsigned char)OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
#elif defined(__x86_64__)
trampoline[12] = offset;
trampoline[12] = (unsigned char)OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
#elif defined(__arm__)
trampoline[4] = offset;
trampoline[4] = (unsigned char)OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
#elif defined(__aarch64__)
trampoline[5] |= offset << 4;
trampoline[6] |= offset >> 4;
trampoline[5] |=
((unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod) << 4;
trampoline[6] |=
((unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod) >> 4;
#else
#error Unsupported architecture
#endif
}
void *doInitHookCap(size_t size) {
if (size == 0) {
LOGE("invalid capacity: %zx", size);
return NULL;
int doInitHookCap(unsigned int cap) {
if (cap == 0) {
LOGE("invalid capacity: %d", cap);
return 1;
}
unsigned char *buf = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
if (hookCap) {
LOGI("allocating new space for trampoline code");
}
unsigned int allSize = trampolineSize * cap;
unsigned char *buf = mmap(NULL, allSize, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (buf == MAP_FAILED) {
LOGE("mmap failed, errno = %s", strerror(errno));
return NULL;
return 1;
}
return buf;
hookCap = cap;
hookCount = 0;
trampolineCode = buf;
return 0;
}

View File

@ -7,17 +7,17 @@ namespace art {
namespace jit {
CREATE_MEM_HOOK_STUB_ENTRIES("_ZN3art3jit12JitCodeCache37GetSavedEntryPointOfPreCompiledMethodEPNS_9ArtMethodE",
const void*, GetSavedEntryPointOfPreCompiledMethod, (void *thiz,
void *art_method), {
if (UNLIKELY(edxp::isHooked(art_method))) {
LOGD("Found hooked method %p (%s), return entrypoint as jit entrypoint", art_method,
art::art_method::PrettyMethod(art_method).c_str());
return getEntryPoint(art_method);
}
return backup(thiz, art_method);
});
// CREATE_MEM_HOOK_STUB_ENTRIES("_ZN3art3jit12JitCodeCache37GetSavedEntryPointOfPreCompiledMethodEPNS_9ArtMethodE",
// const void*, GetSavedEntryPointOfPreCompiledMethod, (void *thiz,
// void *art_method), {
// if (UNLIKELY(edxp::isHooked(art_method))) {
// LOGD("Found hooked method %p (%s), return entrypoint as jit entrypoint", art_method,
// art::art_method::PrettyMethod(art_method).c_str());
// return getEntryPoint(art_method);
// }
// return backup(thiz, art_method);
// });
//
static void HookJitCacheCode(void *handle, HookFunType hook_func) {
const int api_level = edxp::GetAndroidApiLevel();
// For android R, the invisibly initialization makes static methods initializes multiple
@ -25,7 +25,7 @@ namespace art {
// our hooked entry point won't be overwritten.
// This is for SandHook and YAHFA
if (api_level >= __ANDROID_API_R__) {
edxp::HookSyms(handle, hook_func, GetSavedEntryPointOfPreCompiledMethod);
// edxp::HookSyms(handle, hook_func, GetSavedEntryPointOfPreCompiledMethod);
}
}

View File

@ -64,7 +64,7 @@ namespace art {
// RETRIEVE_FIELD_SYMBOL(mutator_lock_, "_ZN3art5Locks13mutator_lock_E");
// LOGE("mutator_lock_: %p", mutator_lock_);
edxp::HookSyms(handle, hook_func, IsInSamePackage);
// edxp::HookSyms(handle, hook_func, IsInSamePackage);
// HOOK_FUNC(ClassForName,
// "_ZN3artL18Class_classForNameEP7_JNIEnvP7_jclassP8_jstringhP8_jobject");

View File

@ -0,0 +1,28 @@
//
// Created by loves on 1/28/2021.
//
#ifndef EDXPOSED_REFLECTION_H
#define EDXPOSED_REFLECTION_H
#include "base/object.h"
namespace art {
CREATE_HOOK_STUB_ENTRIES(
"_ZN3art12VerifyAccessENS_6ObjPtrINS_6mirror6ObjectEEENS0_INS1_5ClassEEEjS5_",
bool, VerifyAccess,
(void * obj, void * declaring_class, uint32_t access_flags, void * calling_class), {
auto calling_desc = art::mirror::Class(calling_class).GetDescriptor();
if (UNLIKELY(calling_desc.find("de/robv/android/xposed/LspHooker") !=
std::string::npos)) {
return true;
}
return backup(obj, declaring_class, access_flags, calling_class);
});
static void PermissiveAccessByReflection(void *handle, HookFunType hook_func) {
edxp::HookSym(handle, hook_func, VerifyAccess);
}
}
#endif //EDXPOSED_REFLECTION_H

View File

@ -24,20 +24,6 @@ namespace edxp {
return Java_lab_galaxy_yahfa_HookMain_backupAndHookNative(env, clazz, target, hook, backup);
}
static void Yahfa_setMethodNonCompilable(JNI_START, jobject member) {
if (!member) {
LOGE("setNonCompilableNative: member is null");
return;
}
void *art_method = getArtMethod(env, member);
if (!art_method) {
LOGE("setNonCompilableNative: art_method is null");
return;
}
setNonCompilable(art_method);
}
static void Yahfa_recordHooked(JNI_START, jobject member) {
edxp::recordHooked(getArtMethod(env, member));
}
@ -52,7 +38,6 @@ namespace edxp {
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Member;"),
NATIVE_METHOD(Yahfa, backupAndHookNative,
"(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"),
//NATIVE_METHOD(Yahfa, setMethodNonCompilable, "(Ljava/lang/reflect/Member;)V"),
NATIVE_METHOD(Yahfa, recordHooked, "(Ljava/lang/reflect/Member;)V"),
NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Member;)Z"),
};

View File

@ -21,6 +21,7 @@
#include "art/runtime/jit/jit_code_cache.h"
#include "art/runtime/art_method.h"
#include "art/runtime/instrumentation.h"
#include "art/runtime/reflection.h"
std::vector<soinfo_t> linker_get_solist(); // Dobby but not in .h
@ -86,6 +87,7 @@ namespace edxp {
// art::oat_file_manager::DisableOnlyUseSystemOatFiles(art_handle, hook_func);
// art::jit::HookJitCacheCode(art_handle, hook_func);
art::instrumentation::DisableUpdateHookedMethodsCode(art_handle, hook_func);
art::PermissiveAccessByReflection(art_handle, hook_func);
art_hooks_installed = true;
LOGI("ART hooks installed");

View File

@ -12,8 +12,6 @@ public class Yahfa {
public static native void init(int sdkVersion);
//public static native void setMethodNonCompilable(Member member);
public static native void recordHooked(Member member);
public static native boolean isHooked(Member member);

View File

@ -10,87 +10,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class HookMain {
private static final Set<String> hookItemWhiteList = new HashSet<String>();
public static void addHookItemWhiteList(String className) {
hookItemWhiteList.add(className);
}
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(Member target, Method hook) {
backupAndHook(target, hook, null);
}
public static void backupAndHook(Member target, Method hook, Method backup) {
Utils.logD(String.format("target=%s, hook=%s, backup=%s", target, hook, backup));
if (target == null) {
@ -103,13 +23,13 @@ public class HookMain {
if (!Modifier.isStatic(hook.getModifiers())) {
throw new IllegalArgumentException("Hook must be a static method: " + hook);
}
checkCompatibleMethods(target, hook, "Original", "Hook");
checkCompatibleMethods(target, hook, "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");
checkCompatibleMethods(target, backup, "Backup");
}
// make sure GC completed before hook
int lastGcType = Heap.waitForGcToComplete();
@ -118,28 +38,15 @@ public class HookMain {
Runtime.getRuntime().gc();
}
if (!Yahfa.backupAndHookNative(target, hook, backup)) {
if(!Yahfa.backupAndHookNative(target, hook, backup)){
throw new RuntimeException("Failed to hook " + target + " with " + hook);
} else {
Yahfa.recordHooked(target);
Yahfa.recordHooked(backup);
// Yahfa.recordHooked(backup);
}
}
public static Member 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 Yahfa.findMethodNative(cls, methodName, methodSig);
}
private static void checkCompatibleMethods(Object original, Method replacement, String originalName, String replacementName) {
private static void checkCompatibleMethods(Object original, Method replacement, String replacementName) {
ArrayList<Class<?>> originalParams;
if (original instanceof Method) {
originalParams = new ArrayList<>(Arrays.asList(((Method) original).getParameterTypes()));
@ -165,7 +72,7 @@ public class HookMain {
if (original instanceof Method
&& !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) {
throw new IllegalArgumentException("Incompatible return types. " + originalName + ": " + ((Method) original).getReturnType() + ", " + replacementName + ": " + replacement.getReturnType());
throw new IllegalArgumentException("Incompatible return types. " + "Original" + ": " + ((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());
@ -173,12 +80,12 @@ public class HookMain {
}
if (originalParams.size() != replacementParams.size()) {
throw new IllegalArgumentException("Number of arguments don't match. " + originalName + ": " + originalParams.size() + ", " + replacementName + ": " + replacementParams.size());
throw new IllegalArgumentException("Number of arguments don't match. " + "Original" + ": " + 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));
throw new IllegalArgumentException("Incompatible argument #" + i + ": " + "Original" + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i));
}
}
}

View File

@ -32,8 +32,6 @@ public abstract class BaseRouter implements Router {
protected volatile AtomicBoolean bootstrapHooked = new AtomicBoolean(false);
protected static boolean useXposedApi = false;
public void initResourcesHook() {
XposedBridge.initXResources();
}
@ -81,49 +79,27 @@ public abstract class BaseRouter implements Router {
public void startBootstrapHook(boolean isSystem) {
Utils.logD("startBootstrapHook starts: isSystem = " + isSystem);
ClassLoader classLoader = BaseRouter.class.getClassLoader();
if (useXposedApi) {
if (isSystem) {
XposedHelpers.findAndHookMethod(SystemMainHooker.className, classLoader,
SystemMainHooker.methodName, new SystemMain());
}
XposedHelpers.findAndHookMethod(HandleBindAppHooker.className, classLoader,
HandleBindAppHooker.methodName,
"android.app.ActivityThread$AppBindData",
new HandleBindApp());
XposedHelpers.findAndHookConstructor(LoadedApkConstructorHooker.className, classLoader,
ActivityThread.class, ApplicationInfo.class, CompatibilityInfo.class,
ClassLoader.class, boolean.class, boolean.class, boolean.class,
new LoadedApkCstr());
} else {
if (isSystem) {
HookMain.doHookDefault(
BaseRouter.class.getClassLoader(),
classLoader,
SysBootstrapHookInfo.class.getName());
} else {
HookMain.doHookDefault(
BaseRouter.class.getClassLoader(),
classLoader,
AppBootstrapHookInfo.class.getName());
}
if (isSystem) {
XposedHelpers.findAndHookMethod(SystemMainHooker.className, classLoader,
SystemMainHooker.methodName, new SystemMain());
}
XposedHelpers.findAndHookMethod(HandleBindAppHooker.className, classLoader,
HandleBindAppHooker.methodName,
"android.app.ActivityThread$AppBindData",
new HandleBindApp());
XposedHelpers.findAndHookConstructor(LoadedApkConstructorHooker.className, classLoader,
ActivityThread.class, ApplicationInfo.class, CompatibilityInfo.class,
ClassLoader.class, boolean.class, boolean.class, boolean.class,
new LoadedApkCstr());
}
public void startSystemServerHook() {
ClassLoader classLoader = BaseRouter.class.getClassLoader();
if (useXposedApi) {
StartBootstrapServices sbsHooker = new StartBootstrapServices();
Object[] paramTypesAndCallback = Versions.hasR() ?
new Object[]{"com.android.server.utils.TimingsTraceAndSlog", sbsHooker} :
new Object[]{sbsHooker};
XposedHelpers.findAndHookMethod(StartBootstrapServicesHooker.className,
SystemMain.systemServerCL,
StartBootstrapServicesHooker.methodName, paramTypesAndCallback);
} else {
HookMain.doHookDefault(
classLoader,
SystemMain.systemServerCL,
SysInnerHookInfo.class.getName());
}
StartBootstrapServices sbsHooker = new StartBootstrapServices();
Object[] paramTypesAndCallback = Versions.hasR() ?
new Object[]{"com.android.server.utils.TimingsTraceAndSlog", sbsHooker} :
new Object[]{sbsHooker};
XposedHelpers.findAndHookMethod(StartBootstrapServicesHooker.className,
SystemMain.systemServerCL,
StartBootstrapServicesHooker.methodName, paramTypesAndCallback);
}
}

View File

@ -16,7 +16,6 @@ import de.robv.android.xposed.XposedBridge;
public class SandHookRouter extends BaseRouter {
public SandHookRouter() {
useXposedApi = true;
}
private static boolean useSandHook = false;

View File

@ -8,8 +8,6 @@ import com.elderdrivers.riru.edxp.yahfa.dexmaker.DynamicBridge;
public class YahfaRouter extends BaseRouter {
YahfaRouter() {
// TODO: disable for better performance
useXposedApi = true;
}
public void onEnterChildProcess() {

View File

@ -1,15 +1,12 @@
package com.elderdrivers.riru.edxp.yahfa.dexmaker;
import com.elderdrivers.riru.edxp.config.ConfigManager;
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.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import de.robv.android.xposed.LspHooker;
@ -17,7 +14,7 @@ import de.robv.android.xposed.XposedBridge;
public final class DynamicBridge {
private static final HashMap<Member, LspHooker> hookedInfo = new HashMap<>();
private static final ConcurrentHashMap<Member, LspHooker> hookedInfo = new ConcurrentHashMap<>();
private static final HookerDexMaker dexMaker = new HookerDexMaker();
private static final AtomicBoolean dexPathInited = new AtomicBoolean(false);
@ -74,7 +71,10 @@ public final class DynamicBridge {
if (hooker == null) {
throw new IllegalStateException("method not hooked, cannot call original method.");
}
return hooker.callBackup(thisObject, args);
if (args == null) {
args = new Object[0];
}
return hooker.getBackup().invoke(thisObject, args);
}
}

View File

@ -18,39 +18,12 @@ public class LspHooker {
this.backup = backup;
}
public Object callBackup(Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException {
if (args == null) {
args = new Object[0];
}
if (Modifier.isStatic(method.getModifiers())) {
return backup.invoke(null, args);
} else {
Object[] newArgs = new Object[args.length + 1];
newArgs[0] = thisObject;
System.arraycopy(args, 0, newArgs, 1, args.length);
return backup.invoke(null, newArgs);
}
public Method getBackup() {
return backup;
}
@SuppressWarnings({"unused", "RedundantSuppression"})
public Object handleHookedMethod(Object[] args) throws Throwable {
if (disableHooks) {
try {
return backup.invoke(null, args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
final int callbacksLength = callbacksSnapshot.length;
if (callbacksLength == 0) {
try {
return backup.invoke(null, args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
param.method = method;
@ -64,6 +37,23 @@ public class LspHooker {
System.arraycopy(args, 1, param.args, 0, args.length - 1);
}
if (disableHooks) {
try {
return backup.invoke(param.thisObject, param.args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
final int callbacksLength = callbacksSnapshot.length;
if (callbacksLength == 0) {
try {
return backup.invoke(param.thisObject, param.args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
// call "before method" callbacks
int beforeIdx = 0;
do {
@ -88,7 +78,7 @@ public class LspHooker {
// call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(callBackup(param.thisObject, param.args));
param.setResult(backup.invoke(param.thisObject, param.args));
} catch (InvocationTargetException e) {
param.setThrowable(e.getCause());
}