diff --git a/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithVirtualMethod_tac.java b/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithVirtualMethod_tac.java index f024ae1..b5e93c7 100644 --- a/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithVirtualMethod_tac.java +++ b/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithVirtualMethod_tac.java @@ -21,7 +21,13 @@ public static String hook(Object thiz, String a, String b, String c, String d) { } public static String backup(Object thiz, String a, String b, String c, String d) { - Log.w(TAG, "ClassWithVirtualMethod.tac() should not be here"); + try { + Log.w(TAG, "ClassWithVirtualMethod.tac() should not be here"); + } + catch (Exception e) { + + } return ""; } } + diff --git a/library/src/main/jni/HookMain.c b/library/src/main/jni/HookMain.c index 9d0268e..8d98ab5 100644 --- a/library/src/main/jni/HookMain.c +++ b/library/src/main/jni/HookMain.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "common.h" #include "trampoline.h" @@ -11,11 +12,11 @@ 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 int kAccNative = 0x0100; static int kAccCompileDontBother = 0x01000000; static int kAccFastInterpreterToInterpreterInvoke = 0x40000000; + static inline uint32_t read32(void *addr) { return *((uint32_t *) addr); } @@ -41,7 +42,6 @@ void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVers //OFFSET_dex_method_index_in_ArtMethod = 4*3; 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; @@ -50,7 +50,6 @@ void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVers 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__: @@ -59,15 +58,12 @@ void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVers // 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; @@ -75,14 +71,12 @@ void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVers 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); @@ -97,12 +91,14 @@ static void setNonCompilable(void *method) { return; } int access_flags = read32((char *) method + OFFSET_access_flags_in_ArtMethod); - LOGI("setNonCompilable: access flags is 0x%x", access_flags); + int old_flags = access_flags; access_flags |= kAccCompileDontBother; write32((char *) method + OFFSET_access_flags_in_ArtMethod, access_flags); + LOGI("setNonCompilable: change access flags from 0x%x to 0x%x", old_flags, access_flags); + } -static int doHook(void *targetMethod, void *hookMethod) { +static int replaceMethod(void *fromMethod, void *toMethod, int isBackup) { if (hookCount >= hookCap) { LOGI("not enough capacity. Allocating..."); if (doInitHookCap(DEFAULT_CAP)) { @@ -112,16 +108,24 @@ static int doHook(void *targetMethod, void *hookMethod) { LOGI("Allocating done"); } - LOGI("target method is at %p, hook method is at %p", targetMethod, hookMethod); + LOGI("replace method from %p to %p", fromMethod, toMethod); // 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), + void *newEntrypoint = NULL; + if(isBackup) { + void *originEntrypoint = readAddr((char *) toMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod); + newEntrypoint = genTrampoline(toMethod, originEntrypoint); + } + else { + newEntrypoint = genTrampoline(toMethod, NULL); + } + + LOGI("replace entry point from %p to %p", + readAddr((char *) fromMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod), newEntrypoint ); if (newEntrypoint) { - memcpy((char *) targetMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod, + memcpy((char *) fromMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod, &newEntrypoint, pointer_size); } else { @@ -130,24 +134,24 @@ static int doHook(void *targetMethod, void *hookMethod) { } 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, + memcpy((char *) fromMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod, + (char *) toMethod + 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_API_O__) { - int access_flags = read32((char *) targetMethod + OFFSET_access_flags_in_ArtMethod); + int access_flags = read32((char *) fromMethod + OFFSET_access_flags_in_ArtMethod); + int old_flags = access_flags; access_flags |= kAccNative; if (SDKVersion >= __ANDROID_API_Q__) { // On API 29 whether to use the fast path or not is cached in the ART method structure access_flags &= ~kAccFastInterpreterToInterpreterInvoke; } - write32((char *) targetMethod + OFFSET_access_flags_in_ArtMethod, access_flags); - LOGI("access flags is 0x%x", access_flags); + write32((char *) fromMethod + OFFSET_access_flags_in_ArtMethod, access_flags); + LOGI("change access flags from 0x%x to 0x%x", old_flags, access_flags); } - LOGI("hook done"); hookCount += 1; return 0; @@ -163,24 +167,17 @@ static int doBackupAndHook(void *targetMethod, void *hookMethod, void *backupMet // so that we don't need to worry about hotness_count_ if (SDKVersion >= __ANDROID_API_N__) { setNonCompilable(targetMethod); - setNonCompilable(hookMethod); +// setNonCompilable(hookMethod); if(backupMethod) setNonCompilable(backupMethod); } 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 - void *backupMethod_raw = malloc(ArtMethodSize); - memcpy(backupMethod_raw, targetMethod, ArtMethodSize); - - res += doHook(backupMethod, backupMethod_raw); + // we use the same way as hooking target method + // hook backup method and redirect back to the original target method + res += replaceMethod(backupMethod, targetMethod, 1); } - res += doHook(targetMethod, hookMethod); + res += replaceMethod(targetMethod, hookMethod, 0); LOGI("hook and backup done"); return res; diff --git a/library/src/main/jni/trampoline.c b/library/src/main/jni/trampoline.c index 3bac696..4207385 100644 --- a/library/src/main/jni/trampoline.c +++ b/library/src/main/jni/trampoline.c @@ -8,6 +8,8 @@ #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 @@ -15,8 +17,13 @@ unsigned int hookCap = 0; unsigned int hookCount = 0; // trampoline: -// 1. set eax/r0/x0 to the hook ArtMethod addr +// 1. set eax/rdi/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 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] @@ -27,6 +34,15 @@ 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] @@ -37,6 +53,17 @@ 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] @@ -47,6 +74,21 @@ 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] @@ -60,28 +102,73 @@ 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 unsigned int trampolineSize = roundUpToPtrSize(MAX(sizeof(trampoline), sizeof(trampolineForBackup))); -void *genTrampoline(void *hookMethod) { - void *targetAddr; +void *genTrampoline(void *toMethod, void *entrypoint) { + unsigned char *targetAddr = trampolineCode + trampolineSize * hookCount; - targetAddr = trampolineCode + trampolineSize * hookCount; - memcpy(targetAddr, trampoline, - sizeof(trampoline)); // do not use trampolineSize since it's a rounded size + if(entrypoint != NULL) { + memcpy(targetAddr, trampolineForBackup, sizeof(trampolineForBackup)); + } + else { + memcpy(targetAddr, trampoline, + sizeof(trampoline)); // do not use trampolineSize since it's a rounded size + } // replace with the actual ArtMethod addr #if defined(__i386__) - memcpy(targetAddr + 1, &hookMethod, pointer_size); + if(entrypoint) { + memcpy(targetAddr + 1, &toMethod, pointer_size); + memcpy(targetAddr + 6, &entrypoint, pointer_size); + } + else { + memcpy(targetAddr + 1, &toMethod, pointer_size); + } #elif defined(__x86_64__) - memcpy((char*)targetAddr + 2, &hookMethod, pointer_size); + if(entrypoint) { + memcpy(targetAddr + 2, &entrypoint, pointer_size); + memcpy(targetAddr + 13, &toMethod, pointer_size); + } + else { + memcpy(targetAddr + 2, &toMethod, pointer_size); + } #elif defined(__arm__) - memcpy(targetAddr+8, &hookMethod, pointer_size); + if(entrypoint) { + memcpy(targetAddr + 20, &entrypoint, pointer_size); + memcpy(targetAddr + 16, &toMethod, pointer_size); + } + else { + memcpy(targetAddr + 8, &toMethod, pointer_size); + } #elif defined(__aarch64__) - memcpy(targetAddr + 12, &hookMethod, pointer_size); + if(entrypoint) { + memcpy(targetAddr + 20, &entrypoint, pointer_size); + memcpy(targetAddr + 12, &toMethod, pointer_size); + } + else { + memcpy(targetAddr + 12, &toMethod, pointer_size); + } #else #error Unsupported architecture diff --git a/library/src/main/jni/trampoline.h b/library/src/main/jni/trampoline.h index 94b7032..a012139 100644 --- a/library/src/main/jni/trampoline.h +++ b/library/src/main/jni/trampoline.h @@ -17,7 +17,7 @@ int doInitHookCap(unsigned int cap); void setupTrampoline(); -void *genTrampoline(void *hookMethod); +void *genTrampoline(void *toMethod, void *entrypoint); #define DEFAULT_CAP 100 //size of each trampoline area would be no more than 4k Bytes(one page)