allhookinone

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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 8086汇编 本笔记是笔者观看小甲鱼老师(鱼C论坛)《零基础入门学习汇编语言》系列视频的笔记,在此感谢他和像他一样...
    Gibbs基阅读 37,022评论 8 114
  • 王爽汇编全书知识点大纲 第一章 基础知识 机器语言 汇编语言的产生 汇编语言的组成 存储器 cpu对存储器的读写 ...
    2c3ba901516f阅读 2,400评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • ai安阅读 156评论 0 0
  • 今天去学校给初中竞赛的学生监考,没开考前我们在一起等待,我无聊的看向周围,看到一位美女老师和同事说话,我盯着她看,...