struct HookInfo{
    char * classDesc;//被hook方法声明所在的类.
    char * methodName;//被hook方法名字
    char * methodSig;//被hook方法的参数和返回值例如,如果被hook方法是`private String getString(int a,String b)`则此时对应的methodSig为`(ILjava/lang/String;)Ljava/lang/String;`
    
    //for dalvik jvm
    bool isStaticMethod;//
    void * originalMethod;
    void * returnType;
    void * paramTypes;
    //for art jvm
    const void * nativecode;
    const void * entrypoint;
}
java hook思路
在hook之前需要准备的信息
- 需要知道被hook方法的名字以及被hook方法定义所在的类
 - 通过classname.class.getDeclaredMethod("methodname",args[i].class)方法获取被hook方法的Method对象;
 - 得到被hook方法的Method对象之后,可以通过Method类提供的方法获取更多的被hook方法的信息。具体是clasdes(被Hook方法的声明所在的类,通过method.getDeclaringClass().getName()方法获得),methodname(通过method.getName()方法获得),methodsig(methodsig中存储的是被hook方法的参数和返回值).
 
hook流程
在进行hook时,主要思路是修改被hook方法对应的ArtMethod对象中的EntryPointFromCompiledCode()即被hook方法的本地机器指令入口。修改该入口,使得该入口指向我们编写的函数,在执行完成之后跳转到被hook函数,从而完成整个hook过程。
由于java代码在生成本地机器指令时,是通过dex2oat程序完成的,而native层代码生成本地机器指令时则是通过gcc生成,这两套工具生成的机器指令规则不同,所以在相互调用时,需要去调整寄存器中的内容。所以在修改被hook方法时,需要对寄存器中内容进行调整。
两种工具生成的本地机器指令对应的寄存器的内容如下所示
r0=method pointer                                                  |r0=method pointer
r1=thread pointer                                                  |r1=arg1
r2=args array                                                      |r2=arg2
r3=old sp                                                          |r3=arg3
[sp]=entrypoint                                                    |r9=thread pointer
        1                                                         |[sp]=method pointer
                                                                   |[sp+4]=addr of thiz
                                                                   |[sp+8]=addr of arg1
                                                                   |[sp+12]=addr of arg2
                                                                   |[sp+16]=addr of arg3 
                                                                                2
具体代码分析
代码入口点hookMethodNative(String clsdes,String methodname,String methodsig,boolean isstatic)
对应的native层方法是java_com_example_allhookinone_HookUtils_hookMethodNative(JNIEnv *env,jobject thiz,jstring cls,jstring methodname,jstring methodsig,jboolean isstatic)方法
java语言中的String对应到native层是jstring,所以整体的代码逻辑是
- 将jstring类型的变量都转换成char *类型,存入到HookInfo结构体中。这一步会对HookInfo结构体中的classDesc,methodName,methodSig,isStaticMethod进行赋值
 - 获取android虚拟机运行过程中加载的so文件,以此来判断android虚拟机是art模式还是dalvik模式。这里假定androidVM此时是ART运行时,往下继续分析代码。
 - 从HookInfo结构体中可以获取到被hook方法的相关信息,包括classDesc(被hook方法定义所在的类),methodName(被hook方法的名字),methodSig(被hook方法的参数和返回值),isStaticMethod(被hook方法是否是静态方法)。
 - 获取当前程序对应的JNIEnv。然后通过JNIEnv中提供的JNI方法得到被hook方法对应的ArtMethod对象。具体的获取方法是
4.1 通过env->FindClass(classDesc)方法获取被hook方法所在的类,存储在jclass类型的变量claxx中。
4.2 判断被hook方法是否是静态方法(通过第一步中获取的isStaticMethod判断),如果是静态方法则通过env->GetStaticMethodID(claxx,methodName,methodSig)获取被hook方法的MethodID,存储在jmethodID类型的变量methid中;如果是非静态方法,则通过env->GetMethodID(claxx,methodName,methodSig)获取被hook方法的MethodID,同样存储在jmethodID类型的变量methid中。 - 获取到被hook方法的methodID之后,通过
reinterpret_cast<ArtMethod *>(methid)获取被hook方法,存储在ArtMethod*类型的变量artmeth中 - 获取ArtMethod类型的对象之后,通过
GetEntryPointFromCompiledCode()获取被hook方法原本的本地机器指令入口,然后将这个入口与我们hook之后指向的入口做比较,如果此时被hook方法指定的本地机器指令入口不是我们hook之后指向的入口,则继续走下边的流程;如果两个入口相同,则说明该方法已经被hook,程序运行结束。 - 被hook方法对应的artmethod对象中指定的本地机器指令入口不是hook之后指向的入口
7.1 获取此时被hook方法对应的artmethod对象中指定的本地机器指令入口,存储在新定义的变量entrypoint中
7.2 将entrypoint赋值给HookInfo结构体中的entrypoint变量
7.3 将当前被hook方法对应的artmethod对象中指定的NativeMethod赋值给HookInfo结构体中的nativecode
7.4重新设置被hook方法对应的artmethod对象中的EntryPointFromCompiledCode,将这个值设置为art_quick_dispatcher。
7.5重新设置artmethod对象中的nativemethod。 
修改完成之后,当执行被hook方法时,由于已经将被hook方法的本地机器指令入口设置为art_quick_dispatcher,所以执行到被hook方法时会去运行art_quick_dispatcher。
art_quick_dispatcher是由汇编代码实现的。如下所示。
 ENTRY art_quick_dispatcher
    push    {r4, r5, lr}           @ sp - 12
    mov      r0, r0                @ pass r0 to method
    str   r1, [sp, #(12 + 4)]
    str      r2, [sp, #(12 + 8)]
    str      r3, [sp, #(12 + 12)]
    mov   r1, r9                   @ pass r1 to thread
    add      r2, sp, #(12 + 4)     @ pass r2 to args array
    add   r3, sp, #12              @ pass r3 to old SP
    blx      artQuickToDispatcher   @ (Method* method, Thread*, u4 **, u4 **)
    pop      {r4, r5, pc}          @ return on success, r0 and r1 hold the result
END art_quick_dispatcher
从这段汇编可以看出,在运行汇编之前,寄存器中的内容分布是第二种
这段汇编代码的逻辑是
- 保存环境,以便运行完成之后返回,执行下一条指令。
 - 保存r1,r2,r3寄存器中的内容
 - r0中的值不变,在两种存储规则中都是存储的method pointer
 - 从r9中获取thread pointer,存储到r1中
 - r2中存储的是参数指针,
 - r3中存储原来的栈定sp
 - 调用artQuickToDispatcher方法。
 - 运行结束,返回pc,从pc中可以获取下一条指令
 
artQuickToDispatcher方法
artQuickToDispatcher方法主要完成的工作包括两点
- 运行我们添加的before hook method,
 - 调整寄存器中的内容,将寄存器中的内容从第一种修改成第二种之后调用被hook方法原本的本地机器指令
 
artQuickToDispatcher方法的代码如下所示
extern "C" uint64_t artQuickToDispatcher(ArtMethod* method, Thread *self, u4 **args, u4 **old_sp){
    HookInfo *info = (HookInfo *)method->GetNativeMethod();
    LOGI("[+] entry ArtHandler %s->%s", info->classDesc, info->methodName);
    // If it not is static method, then args[0] was pointing to this
    if(!info->isStaticMethod){
        Object *thiz = reinterpret_cast<Object *>(args[0]);
        if(thiz != NULL){
            char *bytes = get_chars_from_utf16(thiz->GetClass()->GetName());
            LOGI("[+] thiz class is %s", bytes);
            delete bytes;
        }
    }
//从这个函数开始到这里为止的这一段代码相当于before hook方法
    const void *entrypoint = info->entrypoint;//info中存储的是被hook方法原本的本地机器指令入口
    method->SetNativeMethod(info->nativecode); //restore nativecode for JNI method
    uint64_t res = art_quick_call_entrypoint(method, self, args, old_sp, entrypoint);//调整寄存器内容,从第一种调整成第二种,调整之后会通过blx跳转到被hook方法原本的本地机器指令区执行
    JValue* result = (JValue* )&res;
    if(result != NULL){
        Object *obj = result->l;
        char *raw_class_name = get_chars_from_utf16(obj->GetClass()->GetName());
        if(strcmp(raw_class_name, "java.lang.String") == 0){
            char *raw_string_value = get_chars_from_utf16((String *)obj);
            LOGI("result-class %s, result-value \"%s\"", raw_class_name, raw_string_value);
            free(raw_string_value);
        }else{
            LOGI("result-class %s", raw_class_name);
        }
        free(raw_class_name);
    }
    // entrypoid may be replaced by trampoline, only once.
//  if(method->IsStatic() && !method->IsConstructor()){
    entrypoint = method->GetEntryPointFromCompiledCode();
    if(entrypoint != (const void *)art_quick_dispatcher){
        LOGW("[*] entrypoint was replaced. %s->%s", info->classDesc, info->methodName);
        method->SetEntryPointFromCompiledCode((const void *)art_quick_dispatcher);
        info->entrypoint = entrypoint;
        info->nativecode = method->GetNativeMethod();
    }
    method->SetNativeMethod((const void *)info);
//  }
    return res;
}
具体的代码分析见注释
art_quick_call_entrypoint是由汇编代码实现的。如下所示。
 ENTRY art_quick_call_entrypoint
    push    {r4, r5, lr}           @ sp - 12
    sub     sp, #(40 + 20)         @ sp - 40 - 20
    str     r0, [sp, #(40 + 0)]    @ var_40_0 = method_pointer
    str     r1, [sp, #(40 + 4)]    @ var_40_4 = thread_pointer
    str     r2, [sp, #(40 + 8)]    @ var_40_8 = args_array
    str     r3, [sp, #(40 + 12)]   @ var_40_12 = old_sp
    mov     r0, sp
    mov     r1, r3
    ldr     r2, =40
    blx     memcpy                 @ memcpy(dest, src, size_of_byte)
    ldr     r0, [sp, #(40 + 0)]    @ restore method to r0
    ldr     r1, [sp, #(40 + 4)]
    mov     r9, r1                 @ restore thread to r9
    ldr     r5, [sp, #(40 + 8)]    @ pass r5 to args_array
    ldr     r1, [r5]               @ restore arg1
    ldr     r2, [r5, #4]           @ restore arg2
    ldr     r3, [r5, #8]           @ restore arg3
    ldr     r5, [sp, #(40 + 20 + 12)] @ pass ip to entrypoint
    blx     r5
    add     sp, #(40 + 20)
    pop     {r4, r5, pc}           @ return on success, r0 and r1 hold the result
 END art_quick_call_entrypoint
在运行这段汇编之前,寄存器中的内容分布是第一种
这段汇编代码的逻辑是
1.保存环境,以便运行完成之后返回,执行下一条指令。
2.保存r0,r1,r2,r3寄存器中的内容
3.调用memcpy申请一个空间。memcpy的参数是dest,src,size_of_byte,所以在调用memcpy之前,首先要将函数的参数存储到r0,r1,r2中。
4.重新给r0,r1,r2,r9等寄存器赋值,使得寄存器按照第二种方式。
5.调用entrypoint。