从零开始打造一个,用gradle配置即可执行的Hook库

2,527 阅读8分钟

背景

有一天,老板突然找到小B说,隐私合规需要我们获取权限前,需要明确授权来意,这个你来跟一下吧!小B此时就可愁了,因为项目权限那么多,每个自己手动加上授权来意提示的话,可能会漏掉很多,工作量也大,这可咋办呀!老B看到小B这么愁眉苦脸,连忙说:“可以用ASM进行插桩呀!hook想要的方法”,小B听了,兴奋的去百度了一下,但是发现asm学习成本又高,短期又不可能搞完,这可咋办呀!明明我只想搞hook一个方法交差来着!!老B:”没事,所以本文就来了!”

本文须知

这里只是提供一个设计思路,不会涉及到太多细节,需要读者了解相关的知识,如果不清楚只想使用的话,也是有的 github.com/TestPlanB/S… 欢迎点星星或者pr噢!

当前技术背景

目前可以利用字节码进行hook的框架有很多,比如ASM,AspectJ,javassit等等,都是可以在编译时插入相关的字节码,进行方法的插桩,从而达到一个hook的目的,但是这些工具好归好,但是都有一个小问题,就是需要上手,部分hook框架上手门槛高,也有自己独特的用法,短时间内可能很难使得开发人员上手。所以对hook库进行一个二次封装,也是很多公司在做的一个事情。方法有很多种,作者基于自己的理解,认为配置式的hook才是最简单的,毕竟,Android就有gradle进行各种的项目工程配置,那么我们为什么就不能通过gradle进行配置的Hook呢?基于上面的猜想,就有了本文!友情提示:阅读本文最好对asm跟transform机制有所了解

底层选择

为了更加通用和高效,本次采用asm作为底层,进行二次封装,毕竟android官方的link还有比较出色的aspectj都是基于asm进行底层修改的,那我们这次也同样使用,好了就开干!

目标流程图

image.png

Transform

为了让不太了解的ASM的也能够阅读本文,所以也会介绍部分ASM相关的信息,详细了解还需要大家去官网阅读噢!这里先介绍Transform机制。 Transform是android 进行编译时,在class 文件生成 dex文件时,给我们开发者预留的一个小口,可以理解在这个阶段,我们可以修改已生成的class等文件,编织入自己额外的字节码,从而达到无需修改项目本身的源代码就可以行为修改的机制!如果大家有留意的话,这个机制就是gradle 在build阶段中,会存在一个transformClassesWithXXForXX的task,举例子:

image.png transformClassesWithSpiderPluginForDebug,就是在这里进行的transform修改。 当然,一个项目会存在多个transform,如图所示

image.png 就像流水线一样,我们的transform处理完就会交给下一个transform,共同修改生成的字节码的行为。大家可以先简单理解为这是一个任务,提供了接口给外部修改生成字节码的机会,具体我们可以google相关的资料,也可以看下最后例子项目的处理

ASM

ASM是一个字节码修改框架,他就在我们上文提到的Transform里面做了文章。关于ASM的介绍我们简单来几下,有个大概的认知就好,就像我们访问一个方法/属性一样,jvm肯定是先加载类,然后在执行方法或者属性的方法,ASM的运行机制就如图一样

image.png

封装开始

目标

我们的目标是建立一个基于gradle配置即可运行的hook库,先从使用角度考虑,如果我想hook一个类是LogUtils,中的test方法的话,需要哪些参数呢?快动一下你聪明的小脑袋,emmm,比如类的名称需要吧!方法名称!还有捏!只靠这两个明显还不够,因为我们还存在着各种重载不是嘛,那怎么表示一个特定方法呢!没错,还有函数签名对吧!毕竟编译器底层就是靠着函数签名去识别某个方法的呀,还有嘛?找到这个方法后,我们是在方法前/方法本身/方法后 进行自定义修改呢?所以就还需要一个类似于模式一样的东西吧!这里就称为hook模式好了,还有嘛?找到这个方法,我们还需要自己自定义的操作吧!就定义为hook操作吧。 总结起来,我们需要hook模式,类的名称,方法名称,函数签名,hook操作就可以完成一次hook某个方法的需求了对吧,就比如以下代码所示

比如hook LogUtils类的test方法,签名是()V,替换为调用LogTest类的一个静态方法test

hookMethod hookMode.Default(hook模式),  "com/example/spider/LogUtils"(类的名称), "test"(方法名称), "()V"(函数签名), { MethodVisitor mv ->

    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/spider/LogTest", "test", "()V", false)

}(hook操作)

实现

为了使只要通过上面的代码就能实现hook操作,我们需要定义: asm相关的:自定义的classvisitor,methodvisitor gradle:extension参数,比如上面的“hookMethod”,用来标记我们需要哪部分进行hook操作 transform:标准transform写法。

gradle 定义extension

我们按照上面思路,是不是需要定义一个类,包含hook模式,类的名称,方法名称,函数签名,hook操作,才能将参数传给transform,从而执行自己的ASM操作。 所以就需要定义extension参数: 我们可以在定义plugin的时候,在apply阶段通过project.extensions.create,创建一个自己的配置格式参数,比如Hook.class里面就有我们的参数

project.extensions.create("hook"(标识名称), Hook.class)

使用的话就可以在任意gradle文件使用

hook{
  参数1 对象值
  参数2 对象值
}

这样的话,我们只需要在Transform阶段收集到配置信息传给ASM即可!。

Transform阶段收集信息:

gradle声明的信息我们都可以通过project.xx(标识名称)获取


比如
hook.methodHooker = project.hook,就拿到了一个属于Hook类的hook对象
后续通过hook.hook模式就可以拿到属性是hook模式的参数了

自定义的classvisitor

我们transform阶段会遍历所有的类,但是我们只需要对特定的类进行修改对不对,所以在这里,我们需要针对只需对gradle配置的类,比如例子中的LogUtils进行处理即可,而不需要动刀其他的类! transform进行时,调用classvisitor就会调用其visit方法,我们在这里识别出我们需要hook的类即可对不对,加入我们需要hook的东西都在 hook.hookMethodList里面,我们只需要遍历一遍,找到需要的类,然后打上一个标记


@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
    super.visit(version, access, name, signature, superName, interfaces);
   for(遍历hookMethodList里面){
    if 如果配置的类 == name{
        标记就为true
    }
    }


}

调用visit方法后,就代表了这个类被访问过了,就会调用其visitmethod方法,如果标记有效,我们就采用自定义的method visitor进行方法的修改,否则就还是原本的method visitor

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
    MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);

    if (标记不为true) {
        return mv;
    }
    进行我们自定义的method visitor操作
 
    }
    return mv;
}

自定义method visitor

如果class是我们需要hook的class,就会走到了自定义的method visitor,这里是ASM的定义

@Override
public void visitCode() {
    if hook模式是方法前{
     hook 行为执行
     }

    super.visitCode();
}

@Override
public void visitMethodInsn(int opcode, String owner, String methodName, String descriptor, boolean isInterface) {
    
if hook模式是方法本身{
     hook 行为执行
     }
    super.visitMethodInsn(opcode, owner, methodName, descriptor, isInterface);


}

@Override
public void visitInsn(int opcode) {
    if (opcode == Opcodes.ATHROW || (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
     if hook模式是方法后{
     hook 行为执行
     }
    }

    super.visitInsn(opcode);
}

自定义hook操作

在配置阶段,一个hook操作就可以抽象为Closure,如果用groovy语法就是Closure,如果是Kotlin就是一个函数,代表要进行的操作。 在Transform阶段我们就可以织入自定义的closure,等满足条件就触发。幸运的是,ASM本身就提供了一个为AndroidStudio,准备的插件,叫“ASM Bytecode viewer”,通过这个插件,我们可以直接生成想要的插入代码所对应的ASM编码,如图:

image.png 通过closure所传递的methodvisitor,我们就可以执行配置的hook操作了。值得注意一点是,Spider(github.com/TestPlanB/S… 不重新定义hook规则,而是在ASM基础上,封装比较容易编译错误的点,比如Transform编写,visitor类的编写等等,便于实现我们自己的hook规格,而脱离框架本身,这点是需要运用Spider的开发者需要注意的点!

总结

因为ASM体系有很多细节,文章是没办法列举出所有细节,所以只能表露一个设计思路,具体的用法大家可以移步github.com/TestPlanB/S… 上面也是Spider的设计思路,具体用法也可以看Readme噢!