美文网首页
ART hook 框架 - YAHFA 源码分析

ART hook 框架 - YAHFA 源码分析

作者: 虾饺的开发手记 | 来源:发表于2021-02-21 18:01 被阅读0次

    一些参考资料

    YAHFA 作者写了两篇文章,可以作为参考:
    YAHFA--ART环境下的Hook框架
    在Android N上对Java方法做hook遇到的坑

    backupAndHookNative

    backupAndHookNative 是 YAHFA 执行 hook 操作的主入口:

    private static native boolean backupAndHookNative(Object target, Method hook, Method backup);
    
    // HookMain.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(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;
        }
    }
    
    • getArtMethod 通过 method 对象,拿到对应的 ArtMethod。
    • doBackupAndHook 执行入口点的替换:
      1. 调用 target 的时候,直接执行的是 hook
      2. 调用 backup 的时候实际执行的是 target

    doBackupAndHook 方法执行后,就完成了对 target 方法的 hook。getArtMethod 和 doBackupAndHook 下文再分别叙述。

    getArtMethod

    // HookMain.c
    static 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;
    
    }
    
    • Android 11 以下,jmethodID 就是 ArtMethod 的地址,所以直接 FromReflectedMethod 就可以得到 ArtMethod
    • Android 11 引入了 index id,FromReflectedMethod 返回的可能是一个 index。这里转为拿 Executable(Java) 的 artMethod 字段的值:
    public abstract class Executable extends AccessibleObject
        implements Member, GenericDeclaration {
    
        // ...
    
        /**
         * The ArtMethod associated with this Executable, required for dispatching due to entrypoints
         * Classloader is held live by the declaring class.
         */
        @SuppressWarnings("unused") // set by runtime
        private long artMethod;
    }
    
    public final class Method extends Executable  { ... }
    

    doBackupAndHook

    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;
    }
    
    • setNonCompilable 用于给 ArtMethod 的 access_flag 设置 kAccCompileDontBother标志。这样可以禁止 ART 对 target method 进行 JIT 编译,不然 JIT 的时候会发现我们把方法替换了。
    • replaceMethod 把第一个参数对应的方法“替换”成第二个参数。(第三个参数是 isBackup)。参考下文

    replaceMethod

    replaceMethod 把 from 的入口点,替换成 to

    static int replaceMethod(void *fromMethod, void *toMethod, int isBackup) {
        LOGI("replace method from %p to %p", fromMethod, toMethod);
    
        // replace entry point
        void *newEntrypoint = NULL;
        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, NULL);
        }
    
        LOGI("replace entry point from %p to %p",
             readAddr((char *) fromMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod),
             newEntrypoint
        );
        if (newEntrypoint) {
            writeAddr((char *) fromMethod + 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) {
            void *interpEntrypoint = readAddr((char *) toMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod);
            writeAddr((char *) fromMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod,
                    interpEntrypoint);
        }
    
        // set the target method to native so that Android O wouldn't invoke it with interpreter
        if(SDKVersion >= __ANDROID_API_O__) {
            uint32_t access_flags = getFlags(fromMethod);
            uint32_t old_flags = access_flags;
            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;
            }
            // MakeInitializedClassesVisiblyInitialized is called explicitly
            // entry of jni methods would not be set to jni trampoline after hooked
    //        if (SDKVersion <= __ANDROID_API_Q__) {
                // We don't set kAccNative on R+ because they will try to load from real native method pointer instead of entry_point_from_quick_compiled_code_.
                // Ref: https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:art/runtime/art_method.h;l=844;bpv=1;bpt=1
                access_flags |= kAccNative;
    //        }
            setFlags(fromMethod, access_flags);
            LOGI("change access flags from 0x%x to 0x%x", old_flags, access_flags);
        }
    
        return 0;
    
    }
    
    • genTrampoline 生成一段跳板代码作为入口点
      • 如果是 backup 方法,入口点硬编码为 target 的入口点
      • 如果是 hook 的话,入口点将在运行时从 hook 的 ArtMethod 结构中获取
      • 详情参考下文
    • 替换 from 的 entry_point_from_quick_compiled_code
    • 替换 from 的 entry_point_from_interpreter(小于等于 Android 6.0)

    由于我们会把 target method 的 entry point 替换成了 trampoline,这里先将 target method 的 entry point 硬编码到 backup method 对应的 trampoline,就相当于保存了 target 的 entry point。

    另一方面,hook method 的 entry point 不会被替换,所以为调用 hook method 生成的 trampoline 可以动态地从它的 ArtMethod 读取 entry point。

    genTrampleline

    void *genTrampoline(void *toMethod, void *entrypoint) {
        size_t trampolineSize = entrypoint != NULL ? sizeof(trampolineForBackup) : sizeof(trampoline);
    
        // check available space for new trampoline
        if(currentTrampolineOff+trampolineSize > trampolineSpaceEnd) {
            currentTrampolineOff = allocTrampolineSpace();
            if (currentTrampolineOff == NULL) {
                return NULL;
            } else {
                trampolineSpaceEnd = currentTrampolineOff + TRAMPOLINE_SPACE_SIZE;
            }
        }
    
        unsigned char *targetAddr = currentTrampolineOff;
    
        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__)
        if(entrypoint) {
            memcpy(targetAddr + 1, &toMethod, pointer_size);
            memcpy(targetAddr + 6, &entrypoint, pointer_size);
        }
        else {
            memcpy(targetAddr + 5, &toMethod, pointer_size);
        }
    
    #elif defined(__x86_64__)
        if(entrypoint) {
            memcpy(targetAddr + 2, &entrypoint, pointer_size);
            memcpy(targetAddr + 13, &toMethod, pointer_size);
        }
        else {
            memcpy(targetAddr + 6, &toMethod, pointer_size);
        }
    
    #elif defined(__arm__)
        if(entrypoint) {
            memcpy(targetAddr + 20, &entrypoint, pointer_size);
            memcpy(targetAddr + 16, &toMethod, pointer_size);
        }
        else {
            memcpy(targetAddr + 12, &toMethod, pointer_size);
        }
    
    #elif defined(__aarch64__)
        if(entrypoint) {
            memcpy(targetAddr + 20, &entrypoint, pointer_size);
            memcpy(targetAddr + 12, &toMethod, pointer_size);
        }
        else {
            memcpy(targetAddr + 16, &toMethod, pointer_size);
        }
    #else
    #error Unsupported architecture
    #endif
    
        // skip 4 bytes of code_size_
        if(entrypoint == NULL) {
            targetAddr += 4;
        }
    
        // keep each trampoline aligned
        currentTrampolineOff += roundUpToPtrSize(trampolineSize);
    
        return targetAddr;
    }
    

    trampolineForBackup

    对于 aarch64,trampolineForBackup 如下:

    // 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
    };
    

    对于 backup,执行完下面两行代码后,0x2345678912345678 分别变成了 toMethod 和 entrypoint 的地址

    memcpy(targetAddr + 20, &entrypoint, pointer_size);
    memcpy(targetAddr + 12, &toMethod, pointer_size);
    

    ldr 在这里使用的是 PC-relative 寻址,第一个指令加载 &toMethod 到 x0,第二个加载 &entrypoint 到 x16,然后跳转到 x16 的

    当我们调用 backup 的时候,虚拟机准备好方法的执行环境,然后跳转到 backup 的 entry point,也就是这一段 trampoline:

    1. 调用一个方法时,x0 寄存器存放 callee 的 ArtMethod。ldr x0, 12把 x0 替换回 target method
    2. ldr x16, 16把 target method 的 entry point 加载到 x16,跟着跳转到该地址去执行

    这样一来,就相当于直接调用 target method。

    trampoline

    // 60 00 00 58 ; ldr x0, 12
    // 10 00 40 F8 ; ldr x16, [x0, #0x00]
    // 00 02 1f d6 ; br x16
    // 78 56 34 12
    // 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
    unsigned char trampoline[] = {
            0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
            0x60, 0x00, 0x00, 0x58,
            0x10, 0x00, 0x40, 0xf8,
            0x00, 0x02, 0x1f, 0xd6,
            0x78, 0x56, 0x34, 0x12,
            0x89, 0x67, 0x45, 0x23
    };
    

    对于 hook method,随后 trampoline 后面的 0x2345678912345678 会变成 toMethod 的地址:

    memcpy(targetAddr + 16, &toMethod, pointer_size);
    

    替换 entry point 后,调用 target method 会执行到这一段 trampoline:

    1. ldr x0, 12 加载 &toMethod 到 x0,也就是把 x0 从 target 换成 hook method
    2. ldr x16, [x0, #0x00] 加载 toMethod 的 entry point 到 x16,跟着跳转到这个地址。

    慢着,这里加载 ArtMethod 的第一个 double word 作为目的地址,但 ArtMethod 的第一个字段是 declaring_class_。按道理,这里应该加载 hook method 的 entry point 才对。也就是说,这里的 offset 不应该是 0。

    再看看代码,可以发现在初始化的时候我们调用了 setupTrampoline。offset 即是在这里设置的:

    void Java_lab_galaxy_yahfa_HookMain_init(JNIEnv *env, jclass clazz, jint sdkVersion) {
        // ...
    
        setupTrampoline(OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
    }
    
    void setupTrampoline(uint8_t offset) {
    #if defined(__i386__)
        trampoline[11] = offset;
    #elif defined(__x86_64__)
        trampoline[16] = offset;
    #elif defined(__arm__)
        trampoline[8] = offset;
    #elif defined(__aarch64__)
        trampoline[9] |= offset << 4;
        trampoline[10] |= offset >> 4;
    #else
    #error Unsupported architecture
    #endif
    }
    

    相关文章

      网友评论

          本文标题:ART hook 框架 - YAHFA 源码分析

          本文链接:https://www.haomeiwen.com/subject/zimqfltx.html