allhookinone

作者: sakuradream | 来源:发表于2016-05-06 18:02 被阅读421次
    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之前需要准备的信息

    1. 需要知道被hook方法的名字以及被hook方法定义所在的类
    2. 通过classname.class.getDeclaredMethod("methodname",args[i].class)方法获取被hook方法的Method对象;
    3. 得到被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,所以整体的代码逻辑是

    1. 将jstring类型的变量都转换成char *类型,存入到HookInfo结构体中。这一步会对HookInfo结构体中的classDesc,methodName,methodSig,isStaticMethod进行赋值
    2. 获取android虚拟机运行过程中加载的so文件,以此来判断android虚拟机是art模式还是dalvik模式。这里假定androidVM此时是ART运行时,往下继续分析代码。
    3. 从HookInfo结构体中可以获取到被hook方法的相关信息,包括classDesc(被hook方法定义所在的类),methodName(被hook方法的名字),methodSig(被hook方法的参数和返回值),isStaticMethod(被hook方法是否是静态方法)。
    4. 获取当前程序对应的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中。
    5. 获取到被hook方法的methodID之后,通过reinterpret_cast<ArtMethod *>(methid)获取被hook方法,存储在ArtMethod*类型的变量artmeth中
    6. 获取ArtMethod类型的对象之后,通过GetEntryPointFromCompiledCode()获取被hook方法原本的本地机器指令入口,然后将这个入口与我们hook之后指向的入口做比较,如果此时被hook方法指定的本地机器指令入口不是我们hook之后指向的入口,则继续走下边的流程;如果两个入口相同,则说明该方法已经被hook,程序运行结束。
    7. 被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
    
    

    从这段汇编可以看出,在运行汇编之前,寄存器中的内容分布是第二种

    这段汇编代码的逻辑是

    1. 保存环境,以便运行完成之后返回,执行下一条指令。
    2. 保存r1,r2,r3寄存器中的内容
    3. r0中的值不变,在两种存储规则中都是存储的method pointer
    4. 从r9中获取thread pointer,存储到r1中
    5. r2中存储的是参数指针,
    6. r3中存储原来的栈定sp
    7. 调用artQuickToDispatcher方法。
    8. 运行结束,返回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。

    相关文章

      网友评论

        本文标题:allhookinone

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