Revert YAHFA to the version where backup are memcpied
This commit is contained in:
parent
7f08ddcc21
commit
b04d830fba
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import de.robv.android.xposed.XposedBridge;
|
|||
public class SandHookRouter extends BaseRouter {
|
||||
|
||||
public SandHookRouter() {
|
||||
useXposedApi = true;
|
||||
}
|
||||
|
||||
private static boolean useSandHook = false;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue