有时候咱们在Java开发过程当中可能有这样的需求:须要研究或者修改工程依赖的Jar包中的一些逻辑,查看代码运行中Jar包代码内部的取值状况(好比了解SDK与其服务器通讯的请求报文加密前的状况)。git
这个需求相似于Hook。github
可是每每这些依赖的Jar包中的代码已经被混淆过,删去了本地变量表和代码行号等debug信息,因此没法直接断点调试,其内部逻辑和运行状况也几乎没法触及,研究更难如下手。这时候,通常的办法有二:1.将Jar反解为Java源码,以module方式引入,即可自由修改调试;2.修改字节码或者打包后的smali代码,实现想要的逻辑后再从新打包。这两种方法中,前者每每十分繁杂,尤为在混淆后逻辑变得极其复杂,几乎不可能完成;后者也很麻烦,工序较多,修改为本也比较高。apache
Gradle编译插件hibeaver结合Java AOP编程中对于大名鼎鼎的ASM.jar的应用,和Android gradle 插件提供的最新的Transform API,在Apk编译环节中、class打包成dex以前,插入了中间环节,依据开发者的配置调用ASM API对项目所依赖的jar进行相应的修改,从而能够比较高效地实现上面的Hook需求。
源码地址:https://github.com/BryanSharp/hibeaver编程
(如今hiBeaver已经发布了1.2.7版本,支持轻量级AOP框架设计。)服务器
惟一须要注意的是,运用好这个插件须要有必定的Java汇编指令基础,并了解基本的ASM3的使用方法:后者仍是很简单的,而前者,关于Java汇编指令基础这块,对于事先不了解的同窗,接触起来有必定难度,可是学一学这个其实很是有益处,对于理解Java的运行有很大的帮助。
闲话少说,先看看如何快速实践一把!关键看疗效!网络
关于汇编指令的资料能够参阅本人的文章:大话+图说:Java汇编指令——只为让你懂app
咱们就先来尝试用这个Hook掉小米推送的SDK。框架
首先,在须要的工程的根项目gradle配置中加入如下内容:ide
如图所示,该插件上传到了jcenter中,只需引入classpath:工具
classpath 'com.bryansharp:HiBeaver:1.2.7'
这里须要注意的是,目前该插件仅支持Android gradle编译插件2.0及以上的版本。
而后,在你的App项目gradle配置底部或任意位置加入以下代码:
apply plugin: 'hiBeaver' hiBeaver { //turn this on to make it print help content, default value is true showHelp = true //this flag will decide whether the log of the modifying process be printed or not, default value is false keepQuiet = false //this is a kit feature of the plugin, set it true to see the time consume of this build watchTimeConsume = false //this is the most important part modifyMatchMaps = [:] }
而后,从新编译一下项目,会先去jitpack下载这个插件,开始编译后能够看到Android Studio的右下角的Gradle Console中,多输出了如下内容:
若是你看到了和我同样的内容,那说明初步配置成功。
能够看到,使用插件后会输出一段友好的帮助内容,仍是中英文的,告诉咱们能够直接拷贝做为初始配置,这个帮助输出也是能够关闭的。
下面咱们正式开始尝试Hook小米推送SDK,首先,找出其业务逻辑中的一个节点。
首先,引入小米推送,这个过程不赘述了,blablabla,引入成功!
众所周知,使用小米推送须要先在代码中调用以下:
MiPushClient.registerPush(this, APP_ID, APP_KEY);
这个代码应该会调起本地长链接的创建、注册服务器等流程。假如咱们出于学习的目的,想研究其中的流程,试举一例,先从查看其反编译的代码开始,找一个切入的节点,以下:
首先进入查看MiPushClient.registerPush这个方法:
在initialize的方法中,找到一段逻辑以下:
进入a方法,来到了这个类:com.xiaomi.mipush.sdk.u中,发现:
下面若是咱们想看看运行时前两个方法传入参数的值,就能够开始Hook了。该如何作呢?这个方法体内打Log输出全部的值吗?那样太麻烦了。咱们能够这样作:
首先在咱们项目的源码里新建一个静态方法,包含两个参数,以下图:
其后,咱们只要在a方法中加入一段代码,调用咱们的静态方法,并传入咱们想查看的两个参数便可。
这就有赖于咱们的hibeaver插件了,具体如何作呢?
咱们能够先看看以前的帮助内容:
里面有提到一个the most important par,最重要的部分。没错,这个插件的核心就在于配置这个类型为Map<String, List<Map<String, Object>>>的传入量。
首先咱们配置以下:
而后从新编译,发现输出log以下:
这样就输出这个u类的全部方法信息,用于后面进行配置。
再来看看刚刚的方法a:
是一个泛型方法,众所周知泛型只存在于编码阶段,编译后是没有泛型的,其实传入的参数的实际类型为org.apache.thrift.a,最终找到其方法描述应该为:
(Lorg/apache/thrift/a;Lcom/xiaomi/xmpush/thrift/a;ZLcom/xiaomi/xmpush/thrift/r;)V
进一步配置:
而后从新编译,console输出新增revist部分,以下:
最后,咱们增长以下代码,在其中植入咱们的代码,调用刚刚的静态方法,并把对应值传递过来:
终极配置:
以上代码就不作详细解释了,相信有基础的都能明白,而后编译查看输出:
下面咱们debug一下,看看是否能够成功在registerPush的运行流程中调用到咱们的方法:
上面能够看到,不管是debug仍是log输出均可以抓到想要的参数了。
由于小米推送是商业产品,这里不便于探索太多内容,可是经过hibeaver这个插件能够比较方便的进行相似的研究。
hibeaver所体现的技术,并无特别大的价值,仅仅做为工具来说比较方便易用,有助于学习研究Jar中的逻辑,和学习应用Java汇编码。除此以外,还有几个应用场景:1.修改引用SDK中的一些bug或者提升其效率;2.得到必要的SDK的一些关键调用时机,经过hook创建回调;3.欺骗SDK、关闭或减小SDK中不受控制的网络传输。不一而足,仍是颇有趣、颇有想象空间的。
目前存在的问题,以下,这个除了偶尔同步报错以外没有影响,编译正常:
还有,若是仅仅修改了gradle文件,不会触发更新,须要在代码上也进行任意修改方生效。
hibeaver彻底开源,你们能够自行查看其中代码,有大量的中文注释,对于学习gradle插件开发大有裨益。
github开源项目地址:https://github.com/BryanSharp/hibeaver