从零开始使用gradle配置即可执行的Hook库详解

目录
  • 背景
    • 本文须知
    • 当前技术背景
    • 底层选择
    • 目标流程图
  • Transform
  • ASM
  • 封装开始
    • 目标
    • 实现
      • gradle 定义extension
      • Transform阶段收集信息:
      • 自定义的classvisitor
      • 自定义method visitor
      • 自定义hook操作
  • 总结

背景

有一天,老板突然找到小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进行底层修改的,那我们这次也同样使用,好了就开干!

目标流程图

Transform

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

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

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

ASM

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

封装开始

目标

我们的目标是建立一个基于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编码,如图:

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

总结

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

以上就是从零开始使用gradle配置即可执行的Hook库详解的详细内容,更多关于gradle配置可执行Hook库的资料请关注我们其它相关文章!

(0)

相关推荐

  • android的got表HOOK实现代码

    概述 对于android的so文件的hook根据ELF文件特性分为:Got表hook.Sym表hook和inline hook等. 全局符号表(GOT表)hook,它是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数. Androd so注入和函数Hook(基于got表)的步骤: 1.ptrace附加目标pid进程; 2.在目标pid进程中,查找内存空间(用于存放被注入的so文件的路径和so中被调用的函

  • Android开发之Gradle 进阶Tasks深入了解

    目录 前言 定义Task register与create的区别 查找Task 配置Task 将参数传递给Task构造函数 Task添加依赖 Task排序 Task添加说明 跳过Task 使用onlyIf 使用 StopExecutionException 禁用与启用Task Task超时 Task支持增量编译 Task的输入输出 自定义task类型 声明输入输出的好处 推断task依赖关系 输入和输出验证 并行task 增量编译原理解析 一些高端操作 将@OutputDirectory链接到@I

  • Android三方依赖冲突Gradle中exclude的使用

    目录 一.场景 二.如何查看项目中的三方依赖? 三.使用exclude解决依赖冲突的问题 四.总结 一.场景 Android项目的开发过程中,我们项目中的gradle的dependencies闭包中会引入很多三方依赖库,引入的库越多,越容易产生库之间的依赖冲突. 列举冲突的场景: 1.同一个依赖库引入多个版本: 2.重复引入了同一个依赖库: 编译报错信息一般为:Program type already present 二.如何查看项目中的三方依赖? 方案一: Gradle task工具查看 按照

  • Android性能优化之plt hook与native线程监控详解

    目录 背景 native 线程创建 PLT PLT Hook xhook bhook plt hook总结 背景 我们在android超级优化-线程监控与线程统一可以知道,我们能够通过asm插桩的方式,进行了线程的监控与线程的统一,通过一系列的黑科技,我们能够将项目中的线程控制在一个非常可观的水平,但是这个只局限在java层线程的控制,如果我们项目中存在着native库,或者存在着很多其他so库,那么native层的线程我们就没办法通过ASM或者其他字节码手段去监控了,但是并不是就没有办法,还有

  • Android Studio调试Gradle插件详情

    前言: 使用ASM在编译期修改字节码来完成各种功能(统计方法耗时.全埋点统计...),就需要开发Gradle插件.开发插件中遇到问题排查使用日志输出效率太低了,能断点调试的话就更好了.其实已经有很多文章写了调试插件的方法,但是自己照着他们的步骤下来还是遇到一些问题,把这些问题记录下来方便大家少走弯路. Android Studio版本是3.5.2,Gradle版本是3.5.1 1.点击[Run]->[Edit Configurations...]: 2.然后点击左上角的[+],选择[Remote

  • Android下hook点击事件的示例

    Hook是一种思想,也就是将原来的事件,替换到我们自己的事件,方便我们做一些切入处理.目的是不修改原来的代码,同时也避免遗漏的N多类里面处理. 最近需要在现有的app中设置统计埋点.去业务代码里埋的话似乎耦合度太高.所以决定使用hook的方法对事件进行埋点处理. 这里先记一下对点击事件hook的基本流程. 1.先建一个代理类实现View.OnClickListener,用来做点击后的后续处理. import android.view.View; /** * 实现点击监听 */ public cl

  • 从零开始使用gradle配置即可执行的Hook库详解

    目录 背景 本文须知 当前技术背景 底层选择 目标流程图 Transform ASM 封装开始 目标 实现 gradle 定义extension Transform阶段收集信息: 自定义的classvisitor 自定义method visitor 自定义hook操作 总结 背景 有一天,老板突然找到小B说,隐私合规需要我们获取权限前,需要明确授权来意,这个你来跟一下吧!小B此时就可愁了,因为项目权限那么多,每个自己手动加上授权来意提示的话,可能会漏掉很多,工作量也大,这可咋办呀!老B看到小B这

  • 配置Ant执行Jmeter脚本过程详解

    1.将 Jmeter 下 extras 目录中 ant-jmeter-1.1.1.jar 包拷贝至 ant 安装目录下的lib目录中,否则会报错 ant-jmeter-1.1.1 不存在 2.创建 testcases 文件夹用来存放 Jmeter 脚本文件,创建 test_report 文件夹用来存放测试报告文件 3.testcases 创建 build.xml 文件(从 Jmeter 下的 extras 中将 build.xml 复制到该目录下),所有的 Jmeter 脚本文件都放在此目录下,

  • MySQL 8.0.20 Window10免安装版配置及Navicat管理教程图文详解

    1.MySQL8.0.20下载及解压 下载链接https://dev.mysql.com/downloads/mysql/ 2.新建配置文件my.ini放在D:\mysql-8.0.20-winx64目录下 [client] # 设置mysql客户端默认字符集 default-character-set=utf8 [mysqld] # 设置3306端口 port = 3306 # 设置mysql的安装目录 basedir=D:\\mysql-8.0.20-winx64 # 设置 mysql数据库

  • VSCODE配置Markdown及Markdown基础语法详解

    一,VSCODE配置Markdown 打开左侧的extensions,或者使用 Ctrl+Shift+X,输入Markdown (1)Markdown all in one :是一个组合包,把最常用的Markdown优化都可以安装好. (2)Markdown preview GitHub styling :Github使用的Markdown渲染样式,使用这个样式,在本地就能预览Markdown文件最终在Github Pages中显示的效果. 二,Markdown基础语法 1,VSCODE中新建一

  • ASM的tree api对匿名线程的hook操作详解

    目录 背景 ASM介绍 class文件 fields methods InsnList Signature 实战部分 解决“匿名”Thread 最后 背景 看完本章,你将会学习到用ASM的tree api进行对匿名线程的hook操作,同时也能够了解到asm相关的操作和背景知识介绍!对于ASM插桩来说,可能很多人都不陌生了,但是大多数可能都停留在core api上,对于现在市面上的一些插桩库,其实很多都用tree api进行编写了,因为tree api的简单与明了的特性,也越来越成为许多开源库的选

  • Gradle 依赖切换源码实践示例详解

    目录 引言 1.一般的修改办法 2.通过 Gradle 脚本动态修改依赖 2.1 配置文件和工作流程抽象 2.2 为项目动态添加子工程 2.3 使用子工程替换依赖 2.4 注意事项 总结 引言 最近,因为开发的时候经改动依赖的库,所以,我想对 Gradle 脚本做一个调整,用来动态地将依赖替换为源码.这里以 android-mvvm-and-architecture 这个工程为例.该工程以依赖的形式引用了我的另一个工程 AndroidUtils.在之前,当我需要对 AndroidUtils 这个

  • Spring配置使用之Bean生命周期详解

    基本概念 Spring 中的 Bean 的生命周期,指的是 Bean 从创建到销毁的过程. 下面来探究下几个有关 Bean 生命周期配置的属性. lazy-init lazy-init 表示延迟加载 Bean,默认在 Spring IoC 容器初始化时会实例化所有在配置文件定义的 Bean,若启用了 lazy-init 则在调用 Bean 时才会去创建 Bean. 定义 Bean: public class Animals { public Animals(){ System.out.print

  • php文件包含目录配置open_basedir的使用与性能详解

    1.open_basedir介绍 open_basedir 将php所能打开的文件限制在指定的目录树中,包括文件本身.当程序要使用例如fopen()或file_get_contents()打开一个文件时,这个文件的位置将会被检查.当文件在指定的目录树之外,程序将拒绝打开. 本指令不受安全模式打开或关闭的影响. 2.open_basedir设置方法 1.在php.ini 加入 open_basedir="指定目录" 2.在程序中使用 ini_set('open_basedir', '指定

  • MyBatis 执行动态 SQL语句详解

    大家基本上都知道如何使用 MyBatis 执行任意 SQL,使用方法很简单,例如在一个 XXMapper.xml 中: <select id="executeSql" resultType="map"> ${_parameter} </select> 你可以如下调用: sqlSession.selectList("executeSql", "select * from sysuser where enabled

  • CentOS 7 安装并配置 MySQL 5.6的步骤详解

    Linux使用MySQL Yum存储库上安装MySQL 5.6,适用于Oracle Linux,Red Hat Enterprise Linux和CentOS系统. 一.全新安装MySQL 1.添加MySQL Yum存储库 将MySQL Yum存储库添加到系统的存储库列表中.这是一次性操作,可以通过安装MySQL提供的RPM来执行.跟着这些步骤: 1.1.到MySQL官网下载MySQL Yum存储库(https://dev.mysql.com/downloads/repo/yum/). 1.2.

随机推荐