320 lines
13 KiB
C
320 lines
13 KiB
C
#include "jni.h"
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "common.h"
|
|
#include "env.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_dex_method_index_in_ArtMethod;
|
|
static int OFFSET_dex_cache_resolved_methods_in_ArtMethod;
|
|
static int OFFSET_array_in_PointerArray;
|
|
static int OFFSET_ArtMehod_in_Object;
|
|
static int OFFSET_access_flags_in_ArtMethod;
|
|
static size_t ArtMethodSize;
|
|
static int kAccNative = 0x0100;
|
|
static int kAccCompileDontBother = 0x01000000;
|
|
|
|
static inline uint16_t read16(void *addr) {
|
|
return *((uint16_t *) addr);
|
|
}
|
|
|
|
static inline uint32_t read32(void *addr) {
|
|
return *((uint32_t *) addr);
|
|
}
|
|
|
|
static inline uint64_t read64(void *addr) {
|
|
return *((uint64_t *) addr);
|
|
}
|
|
|
|
void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVersion) {
|
|
int i;
|
|
SDKVersion = sdkVersion;
|
|
LOGI("init to SDK %d", sdkVersion);
|
|
switch (sdkVersion) {
|
|
case ANDROID_P:
|
|
kAccCompileDontBother = 0x02000000;
|
|
OFFSET_ArtMehod_in_Object = 0;
|
|
OFFSET_access_flags_in_ArtMethod = 4;
|
|
OFFSET_dex_method_index_in_ArtMethod = 4 * 3;
|
|
OFFSET_array_in_PointerArray = 0;
|
|
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_O2:
|
|
kAccCompileDontBother = 0x02000000;
|
|
case ANDROID_O:
|
|
OFFSET_ArtMehod_in_Object = 0;
|
|
OFFSET_access_flags_in_ArtMethod = 4;
|
|
OFFSET_dex_method_index_in_ArtMethod = 4 * 3;
|
|
OFFSET_dex_cache_resolved_methods_in_ArtMethod = roundUpToPtrSize(4 * 4 + 2 * 2);
|
|
OFFSET_array_in_PointerArray = 0;
|
|
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_N2:
|
|
case ANDROID_N:
|
|
OFFSET_ArtMehod_in_Object = 0;
|
|
OFFSET_access_flags_in_ArtMethod = 4; // sizeof(GcRoot<mirror::Class>) = 4
|
|
OFFSET_dex_method_index_in_ArtMethod = 4 * 3;
|
|
OFFSET_dex_cache_resolved_methods_in_ArtMethod = roundUpToPtrSize(4 * 4 + 2 * 2);
|
|
OFFSET_array_in_PointerArray = 0;
|
|
|
|
// 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_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;
|
|
OFFSET_dex_method_index_in_ArtMethod = 4 * 5;
|
|
OFFSET_dex_cache_resolved_methods_in_ArtMethod = 4;
|
|
OFFSET_array_in_PointerArray = 4 * 3;
|
|
ArtMethodSize = roundUpToPtrSize(4 * 7) + pointer_size * 3;
|
|
break;
|
|
case ANDROID_L2:
|
|
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;
|
|
OFFSET_dex_method_index_in_ArtMethod = OFFSET_ArtMehod_in_Object + 4 * 5;
|
|
OFFSET_dex_cache_resolved_methods_in_ArtMethod = OFFSET_ArtMehod_in_Object + 4;
|
|
OFFSET_array_in_PointerArray = 12;
|
|
ArtMethodSize = OFFSET_entry_point_from_interpreter_in_ArtMethod + pointer_size * 3;
|
|
break;
|
|
case ANDROID_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;
|
|
OFFSET_dex_method_index_in_ArtMethod =
|
|
OFFSET_ArtMehod_in_Object + 4 * 4 + 8 * 4 + 4 * 2;
|
|
OFFSET_dex_cache_resolved_methods_in_ArtMethod = OFFSET_ArtMehod_in_Object + 4;
|
|
OFFSET_array_in_PointerArray = 12;
|
|
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_N) {
|
|
return;
|
|
}
|
|
int access_flags = read32((char *) method + OFFSET_access_flags_in_ArtMethod);
|
|
LOGI("setNonCompilable: access flags is 0x%x", access_flags);
|
|
access_flags |= kAccCompileDontBother;
|
|
memcpy(
|
|
(char *) method + OFFSET_access_flags_in_ArtMethod,
|
|
&access_flags,
|
|
4
|
|
);
|
|
}
|
|
|
|
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_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);
|
|
}
|
|
|
|
// 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) {
|
|
memcpy((char *) targetMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod,
|
|
&newEntrypoint,
|
|
pointer_size);
|
|
} else {
|
|
LOGE("failed to allocate space for trampoline of target method");
|
|
return 1;
|
|
}
|
|
|
|
if (OFFSET_entry_point_from_interpreter_in_ArtMethod != 0) {
|
|
memcpy((char *) targetMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod,
|
|
(char *) hookMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod,
|
|
pointer_size);
|
|
}
|
|
|
|
// set the target method to native so that Android O wouldn't invoke it with interpreter
|
|
if (SDKVersion >= ANDROID_O) {
|
|
int access_flags = read32((char *) targetMethod + OFFSET_access_flags_in_ArtMethod);
|
|
access_flags |= kAccNative;
|
|
memcpy(
|
|
(char *) targetMethod + OFFSET_access_flags_in_ArtMethod,
|
|
&access_flags,
|
|
4
|
|
);
|
|
LOGI("access flags is 0x%x", access_flags);
|
|
}
|
|
|
|
LOGI("hook and backup done");
|
|
hookCount += 1;
|
|
return 0;
|
|
}
|
|
|
|
static void ensureMethodCached(void *hookMethod, void *backupMethod,
|
|
void *hookClassResolvedMethods) {
|
|
void *dexCacheResolvedMethods;
|
|
// then we get the dex method index of the static backup method
|
|
int methodIndex = read32(
|
|
(void *) ((char *) backupMethod + OFFSET_dex_method_index_in_ArtMethod));
|
|
|
|
if (methodIndex >= 512) {
|
|
LOGW("methodIndex = %d", methodIndex);
|
|
}
|
|
|
|
// update the cached method manually
|
|
// first we find the array of cached methods
|
|
dexCacheResolvedMethods = hookClassResolvedMethods;
|
|
|
|
if (!dexCacheResolvedMethods) {
|
|
LOGE("dexCacheResolvedMethods is null");
|
|
return;
|
|
}
|
|
|
|
// finally the addr of backup method is put at the corresponding location in cached methods array
|
|
if (SDKVersion >= ANDROID_O2) {
|
|
// array of MethodDexCacheType is used as dexCacheResolvedMethods in Android 8.1
|
|
// struct:
|
|
// struct NativeDexCachePair<T> = { T*, size_t idx }
|
|
// MethodDexCachePair = NativeDexCachePair<ArtMethod> = { ArtMethod*, size_t idx }
|
|
// MethodDexCacheType = std::atomic<MethodDexCachePair>
|
|
memcpy((char *) dexCacheResolvedMethods + OFFSET_array_in_PointerArray +
|
|
pointer_size * 2 * methodIndex,
|
|
(&backupMethod),
|
|
pointer_size
|
|
);
|
|
memcpy((char *) dexCacheResolvedMethods + OFFSET_array_in_PointerArray +
|
|
pointer_size * 2 * methodIndex + pointer_size,
|
|
&methodIndex,
|
|
pointer_size
|
|
);
|
|
} else {
|
|
memcpy((char *) dexCacheResolvedMethods + OFFSET_array_in_PointerArray +
|
|
pointer_size * methodIndex,
|
|
(&backupMethod),
|
|
pointer_size);
|
|
}
|
|
}
|
|
|
|
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,
|
|
(void *) (*env)->FromReflectedMethod(env, target),
|
|
(void *) (*env)->FromReflectedMethod(env, hook),
|
|
backup == NULL ? NULL : (void *) (*env)->FromReflectedMethod(env, backup)
|
|
)) {
|
|
(*env)->NewGlobalRef(env,
|
|
hook); // keep a global ref so that the hook method would not be GCed
|
|
return JNI_TRUE;
|
|
} else {
|
|
return JNI_FALSE;
|
|
}
|
|
}
|
|
|
|
void Java_lab_galaxy_yahfa_HookMain_ensureMethodCached(JNIEnv *env, jclass clazz,
|
|
jobject hook,
|
|
jobject backup) {
|
|
ensureMethodCached((void *) (*env)->FromReflectedMethod(env, hook),
|
|
backup == NULL ? NULL : (void *) (*env)->FromReflectedMethod(env, backup),
|
|
getResolvedMethodsAddr(env, hook));
|
|
}
|
|
|
|
static void *getResolvedMethodsAddr(JNIEnv *env, jobject hook) {
|
|
// get backup class
|
|
jclass methodClass = (*env)->FindClass(env, "java/lang/reflect/Method");
|
|
jmethodID getClassMid = (*env)->GetMethodID(env, methodClass, "getDeclaringClass",
|
|
"()Ljava/lang/Class;");
|
|
jclass backupClass = (*env)->CallObjectMethod(env, hook, getClassMid);
|
|
// get dexCache of backup class
|
|
jclass classClass = (*env)->FindClass(env, "java/lang/Class");
|
|
jfieldID dexCacheFid = (*env)->GetFieldID(env, classClass, "dexCache", "Ljava/lang/Object;");
|
|
jobject dexCacheObj = (*env)->GetObjectField(env, backupClass, dexCacheFid);
|
|
// get resolvedMethods address
|
|
jclass dexCacheClass = (*env)->GetObjectClass(env, dexCacheObj);
|
|
if (SDKVersion >= ANDROID_N) {
|
|
jfieldID resolvedMethodsFid = (*env)->GetFieldID(env, dexCacheClass, "resolvedMethods",
|
|
"J");
|
|
return (void *) (*env)->GetLongField(env, dexCacheObj, resolvedMethodsFid);
|
|
} else if (SDKVersion >= ANDROID_L) {
|
|
LOGE("this should has been done in java world: %d", SDKVersion);
|
|
return 0;
|
|
} else {
|
|
LOGE("not compatible with SDK %d", SDKVersion);
|
|
return 0;
|
|
}
|
|
}
|