YAHFA--ART环境下的Hook框架(代码详解)

来源:互联网 发布:51单片机指令集 编辑:程序博客网 时间:2024/06/06 01:17

上文转载YAHFA作者的原文熟悉了YAHFA的原理,但是由于比较笼统,所以本文针对代码细节,从流程上更加简明的介绍一下YAHFA的做了什么怎么起效


开篇前我们区分下方法的命名,防止文中被绕晕:

originMethod:文中指将要hook的方法,即target。

hookMethod:我们自定义的方法,用来替换originMethod,当调用originMethod时,执行我们的hookMethod。

backupMethod:用来备份originMethod的方法,这样一来hookMethod可以通过调用backupMethod来简介调用了originMethod。


根据大家对hook的了解,hook一般都是对目标方法originMethod进行修改替换,换成hookMethod,从而在程序执行到originMethod的时候,变成了执行hookMethod,从而完成了hook,但是有时候我们想在执行了我们自己的hookMethod之后,继续执行原来的originMethod方法,例如我们hook文件读写,为了修改文件路径,所以用我们的hookMethod修改完文件路径后,继续通过originMethod打开新的文件路径,所以这就需要一个数据区或者指针保存原来的originMethod,所以backupMethod来完成这个使命,这样hookMethod执行完之后,调用backupMethod就等于调用了originMethod。

一、YAHFA的主要代码实现

YAHFA的代码实现主要包含以下几部分:
1.把originMethod备份到backupMethod,此部分属于整体替换,即将backupMethod整体被originMethod覆盖掉。
2.originMethod的entry_point_from_jni_指向hookMethod的首地址

3.originMethod的entry_point_from_quick_compiled_code_指向一段汇编指令,汇编指令负责从originMethod的entry_point_from_quick_compiled_code_跳转到hookMethod的entry_point_from_quick_compiled_code_,


本部分只讲做了什么改变,下部分再讲为什么这么做。


1.把originMethod备份到backupMethod

主要代码如下:
void *dexCacheResolvedMethods = (void *)readAddr((void *)((char *)backupMethod+OFFSET_dex_cache_resolved_methods_in_ArtMethod));int methodIndex = read32((void *)((char *)backupMethod+OFFSET_dex_method_index_in_ArtMethod));        //first update the cached method manuallymemcpy((char *)dexCacheResolvedMethods+OFFSET_array_in_PointerArray+pointer_size*methodIndex,               (&backupMethod),               pointer_size);        //then replace the placeholder with original methodmemcpy((void *)((char *)backupMethod+OFFSET_ArtMehod_in_Object),                (void *)((char *)originMethod+OFFSET_ArtMehod_in_Object),                ArtMethodSize-OFFSET_ArtMehod_in_Object);

首先要保证backupMethod方法已经解析完成。在备份之前,手工更新dex_cache_resolved_methods_数组对应项,确保hookMethod在调用backupMethod时无需再进行方法解析。什么意思呢,就是如果backupMethod还没有装载到dex_cache_resolved_methods_,此时即使我们用backupMethod替换掉了originMethod,到时候调用backupMethod的时候,还会重新装载backupMethod,所以我们修改过的backupMethod会被覆盖掉而丢失备份的originMethod,因此需要先装载backupMethod,然后再进行覆盖。


2.originMethod的entry_point_from_jni_指向hookMethod的首地址

//save the hook method at entry_point_from_jni_    memcpy((char *)originMethod+OFFSET_entry_point_from_jni_in_ArtMethod,           &hookMethod,           pointer_size);
这段代码直接通过originMethod的首地址偏移,找到entry_point_from_jni_的内存地址,然后把hookMethod的首地址赋给了entry_point_from_jni_所在内存上。为什么这样做在接下来第二部分讲。


3.originMethod的entry_point_from_quick_compiled_code_指向一段汇编指令

汇编指令的定义在entrypoints_x86.S文件中:

BEGIN_ENTRY hook_new_entry_21    mov 32(%eax), %eax    push 40(%eax)    retEND_ENTRY hook_new_entry_21
然后在HookMain.c中被引用,定义了一个hook_new_entry,指向这段汇编指令:

        case 21:            LOGI("init to SDK %d", sdkVersion);            OFFSET_dex_cache_resolved_methods_in_ArtMethod = 12;            OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod = 40;            OFFSET_entry_point_from_jni_in_ArtMethod = 32;            OFFSET_entry_point_from_interpreter_in_ArtMethod = 24;            OFFSET_dex_method_index_in_ArtMethod = 64;            OFFSET_array_in_PointerArray = 12;            OFFSET_ArtMehod_in_Object = 8;            pointer_size = sizeof(void *);            ArtMethodSize = 72;            hook_new_entry = (void *)hook_new_entry_21;            break;
接着在HookMain.c中用这段汇编指令地址替换掉originMethod的entry_point_from_quick_compiled_code_:
    //update the entrypoints    memcpy((char *)originMethod+OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod,           &hook_new_entry,           pointer_size);
这样一来,originMethod被调用的时候,就会执行这段汇编指令。


二、YAHFA的工作原理

做了以上的操作,不禁会问为什么这样做就可以实现hook?本部分讲解上述修改的机制。

首先引用YAHFA作者的原文的解释,讲解一下ArtMethod是怎么调用的。

例如,我们有如下Java代码:

Log.e("tag", "msg");

编译为dalvik字节码,对应如下:

invoke-static {v0, v1}, Landroid/util/Log;.e:(Ljava/lang/String;Ljava/lang/String;)I // method@0000

而经过dex2oat,将其编译为机器码,则得到如下内容:

mov    0x10(%esp),%ecx ; 设置第1个参数mov    0x14(%esp),%edx ; 设置第2个参数mov    (%esp),%eax ; 栈顶保存了当前方法ArtMethod结构体的地址mov    0x4(%eax),%eax ; 获取当前方法的dex_cache_resolved_methods_(偏移为4)mov    0xc(%eax),%eax ; 获取dex_cache_resolved_methods_中的第一项,即method index为0的方法Log.e,call   *0x24(%eax) ; 调用Log.e的entry_point_from_quick_compiled_code_(偏移为36)

上述汇编指令中,先通过mov将参数"tag"和参数"msg"分别存储在%ecx%edx上,然后查找ArtMethod的dex_cache_resolved_methods_,这个dex_cache_resolved_methods_可以理解为方法表,先找到方法表dex_cache_resolved_methods_的地址,再进一步解析Log.e方法在entry_point_from_quick_compiled_code_中的址,然后调用时,直接从entry_point_from_quick_compiled_code_查找到Log.e方法执行,完成调用。



YAHFA后执行时,首先贴一张流程图,一般情况下,当originMethod被调用的时候(上述例子Log.e),系统会结合originMethod的参数,从entry_point_from_quick_compiled_code_地址为入口,执行originMethod方法。

由于我们1.3中将originMethod的entry_point_from_quick_compiled_code_入口的地址改为了一段汇编指令,所以,当执行originMethod方法时,会先执行entry_point_from_quick_compiled_code_的汇编指令:
BEGIN_ENTRY hook_new_entry_21    mov 32(%eax), %eax      push 40(%eax)       ret  END_ENTRY hook_new_entry_21

首先mov 32(%eax), %eax将当前originMethod的entry_point_from_jni_处的地址替换掉当前方法基地址%eax,由于我们1.2做了处理将originMethod的entry_point_from_jni_处替换为了hookMethod的首地址,所以做完mov 32(%eax), %eax操作后,寄存器%eax的内容从originMethod基地址替换为了hookMethod的首地址。
然后push 40(%eax)操作将hookMethod结构的entry_point_from_quick_compiled_code_地址压栈,然后通过ret返回。

这样执行originMethod的entry_point_from_quick_compiled_code_时,就通过这三行指令,结合之前做的处理,跳转到了hookMethod中的entry_point_from_quick_compiled_code_地址进行执行,实现了hook。
由于originMethod保存在了backupMethod中,所以在hookMethod中调用backupMethod就会跳转到originMethod的entry_point_from_quick_compiled_code_进行执行,这样就完成了文章开头hook的目的。

原创粉丝点击