在前面已经介绍完了 自动给apk中注入日志代码工具icodetools原理了,在那里咱们曾经说过其实离真正的可以使用价值有点距离,本篇就对这个工具进行一些优化,让其真正意义上开始能工做量产。当时在前面一篇文章中说到遗留的三个主要问题:java
第一个问题:对每一个类中都添加一个静态打印方法堆栈信息的方法,这样会致使有些应用的dex过大,方法数超了问题android
第二个问题:在从输入一个apk到给每一个类中的每一个方法添加日志代码而后在签名输出最终的apk,这个过程其实不少步,可是咱们以前都是手动的去进行操做,很是麻烦,因此这里还得解决一键化问题git
第三个问题:在实际演练中会发现一些大型的app调用的方法特别多,致使咋们打印的日志信息过多霸屏,很难定位到咱们真正想要的那个方法,并且打印日志调用次数过多会致使应用出现无响应状态。因此这里得作一个开关和过滤规则github
只有解决了这三个问题咋们才算是真正意义上的自动化工具,能够经历各类app的考验。api
咱们可使用在前一篇讲解原理的文章中的步骤来进行,用一个比较庞大的企业应用作案例,结果在使用dx将添加日志代码以后的jar文件转化成dex文件的时候出现报错了:数组
看到这个咱们就猜到了,咱们添加日志代码以后的jar中方法数超过了,缘由其实很简单,由于咱们以前的添加日志操做是在每一个类中添加一个静态打印日志的方法,那么若是对于一个dex文件中有不少类,那么就添加了不少相同的打印方法,方法数超了是有可能的。那么如何解决这个问题呢?其实方案有两个:微信
第一个方案:由于方法数超了,每一个类都被添加了一个打印堆栈日志的方法,咋们能够不用这个方法,把这个方法的代码直接拷贝到原来类中的每一个方法前。可是这样会带来一个问题,若是一个类中的方法不少,那么就会增长很是多的一样代码。最后使用dx转化的时候会发现也是报错的。因此该方案不可行。app
第二个方案:由于方法数超了,因此那个打印堆栈的方法确定不能要了,可是又不能把代码都塞到每一个方法的前面,那么正常的编码习惯是能够把这个打印方法抽取出来放到一个工具类中。从这里能够看到这个方案是靠谱的。一个应用中只有这么一个工具类,并且这个工具类包含了打印堆栈信息的方法,那么整体来看方法数是没多大变化的,只是多了一个工具类。框架
有了方案,咋们就得实现了,可是这个实现仍是有点曲折的,由于从方案2来看,咱们须要给dex中添加一个工具类了,可是咱们在前一篇文章中了解到,能够经过ClassVisitor类操做dex中的每一个类信息,经过MethodVisitor类操做dex中每一个类的每一个方法,可是没有途径能够添加一个类的。因此咋们得另想办法了。工具
咱们如今可以往dex中塞入一个类有两个方案:
第一个方案:很是清楚dex文件格式以后,能够去手动的添加类信息到dex中。可是这个方案我只是敢想想,实践的话我就算了,很简单由于我怕麻烦!
第二个方案:能够利用jar工具,在咱们把利用dex2jar把dex添加代码变成jar文件以后,咱们能够把jar文件解压,而后再把咱们须要插入的类放到这个解压目录下,最后再用jar命令生成jar文件。最终在使用dx命令生成dex文件。这个方案有点复杂,可是靠谱好操做呀。如今看着有点复杂,可是下面会详细介绍一个一键化工具,到时候都不用你来操做,何谈复杂了。可是惋惜的是,这个方案有一个缺点,就是解压过程当中,Windows平台是不区分文件名的大小写的,可是若是原来jar中的包名中有两个类名是大小写的,那么解压到本地的时候会出错的。好比一个包里面有A.class和a.class文件,解压到本地前者会被后者覆盖,并且这个方案有点繁琐了。
第三个方案:能够直接把编译以后的classes文件塞到jar文件中。有了这个方案,实现比较简单了。
注意:
一、这里咱们本身定义的类文件必定要注意,首先这个类的包名必定要具备本身的惟一性,千万不可与原来jar中的类重名了,要想作到彻底惟一是不可能了。可是咱们能够弄一个奇葩的包名和类名就能够了。这里我用的名称是:
cn.wjdiankong.jw.utils.JWUtils这个名称了。现阶段应该不会有重复。
代码比较简单,咋们直接来看看便可:
这里直接借助ZipEntry类进行添加一个文件到jar文件中便可,可是在添加的时候必定要注意ZipEntry的名称必须是类的全路径名称,咱们是从工具命令外部传入获取到的类名:
下面咋们咱们在每一个类的每一个方法以前调用这么一行代码便可:JWUtils.printStackTrace(“jw”);,而这段代码对应的asm代码为:
上面操做完成以后,就能够运行一下程序了,前提是你得先准备一个须要插入的类JWUtils
首先默认状况下咱们必须得准备一个cn.wjdiankong.jw.utils.JWUtils类,并且类中有一个打印堆栈的方法:
public static void printStackTrace(String tag){….},而后编译获取到JWUtils.class文件放到指定工具根目录下便可。当成功的把JWUtils.class文件塞入到jar文件中以后就能够直接使用dx命令进行转化成dex了,这里有可能会遇到这个错误:
这个错误缘由是由于我用JDK1.8编译了JWUtils.java文件,而dx工具不兼容这个JDK版本,因此可使用1.8如下的版本编译JWUtils.java文件便可。
成功以后,在把dex文件放到apk中,在从新签名以后的apk,可使用jadx打开查看:
看到了,在apk中咱们已经成功的把咱们的JWUtils类插入进去了,而后咱们在随便打开一个其余类看看:
在类的每一个方法以前也成功调用了JWUtils.printStatckTrace方法进行打印日志信息了。
那么到这里其实咱们就解决了第一个问题了,并且从这里能够看到之后若是咱们想本身在打印什么消息能够本身实现JWUtils类,而后实现printStatckTrace方法便可,可是须要注意的是类名和包名以及方法的签名都必须一致,否则会报错的。
解决了第一个问题以后,咋们就能够来解决第二个问题了,第二个问题实际上是为了工具更好的可以被使用。由于咱们在前面操做能够看到从输入一个源apk到最终输出一个添加日志代码的apk有不少步骤,可是这些操做很是繁琐。因此这里咱们就须要把每一步作到代码化,让使用该工具的人无感知。因此一键化主要从下面几步进行完善:
第1、解压apk文件获取其全部的dex文件
这个代码简单不解释了,可是这里须要注意的是,不要解压apk中全部的文件,那样不必也很浪费时间,这里只须要解压dex文件和签名目录,由于签名目录在下一步须要用到:
第2、删除原始签名文件
这一步是为了后面签名工做准备,若是这里不删除签名文件的话,后面再进行二次签名会发现有的apk有冲突
看到了吧,这里若是不删除原始签名,重签名以后的apk会有两个签名文件,原本Android中是容许有多个签名文件的,可是这些签名文件信息必须保持一致也就是须要用一样的私钥进行签名,可是如今明显不是,因此在安装apk会报错的。从这里就能够看到咋们为了后面重签名方便,就在这里把原始签名文件删除便可。
这里为了方便就直接借助aapt命令进行删除apk中的签名文件了,命令很简单:
aapt remove src.apk META-INF/XXX.RSA META-INF/XXX.SF MANIFEST.MF
由于aapt不支持直接删除目录操做,因此这里须要借助第一步中的解压META-INF目录,获得应用的全部签名文件,而后在这里组装签名文件名便可。
第3、添加日志到dex文件中
这个依然用咱们以前介绍的改造以后的dex2jar的接口,把dex转化成jar文件,而且在每一个类每一个方法中添加日志代码,这里须要注意的是有的apk中可能有多个dex文件,因此须要处理第一步中获取到解压以后的全部dex文件。
这里咱们为了更好的后续拓展,因此只须要外部工具提供JWUtils.java文件,内部咱们自动使用javac将其编译成JWUtils.class文件了,
注意:
一、这里使用javac命令编译的时候,指定了类文件格式是UTF-8的,因此若是想本身定义JWUtils类的话,须要注意这个文件的格式为UTF-8,否则在编译的时候可能会报错。
二、由于JWUtils.java中用到了Android的系统api,因此编译的时候须要携带系统的android.jar文件一块儿进行编译工做。
这一步主要是咱们把上面编译好的JWUtils.class文件直接塞入到jar文件中便可。
这里须要注意由于有的应用可能包含多个dex文件,因此咋们在给dex添加JWUtils类的时候,只须要在主dex中添加便可,不可重复添加。而后就借助dx.jar这个工具进行转化了。这个jar包在AndroidSDK目录下有。构造好dx命令以后直接调用main方法便可。
这里须要把咱们添加代码以后的dex文件插入到源apk中,依然借助aapt命令便可,由于aapt命令不支持文件覆盖功能,因此咋们得先删除原始的dex文件,而后在添加新的dex文件。
这一步就简单了,直接使用jarsigner工具命令进行apk签名便可。签名以后的文件为signed.apk。这里的签名文件信息写死了,不支持外部传入的功能。
到这里咋们就把全部的步骤用代码进行整合了,而后咋们须要把这个工具导出一个可执行的jar文件,可是咱们还得想一想为了让这个工具更好的拓展,咱们可让外部传入一些参数作成动态化功能,这里接受了外部传入的5个参数信息:
这里接受的5个参数以下:
1》工具运行的当前目录
2》须要处理的源apk文件路径
3》aapt命令的路径
4》打印信息的tag,默认是:jw
5》打印信息的类名,默认是:cn.wjdiankong.jw.utils.JWUtils
6》打印信息的方法名:默认是:printStackTrace
而后咋们还须要判断aapt命令路径是否有效,以及是否配置了JAVA_HOME环境变量,由于后面几步都会依赖aapt工具和java的一些工具。因此这两个内容是必要的。
有了这些接受参数,咱们在外部就好扩展了,好比咱们能够本身实现打印消息的类,类名和包名随意定义,也能够随意定义打印日志的方法名,可是方法的签名不可变也就是必须是这种格式:public static void XXX(String tag)。其实这个签名也是能够修改的,可是我以为没那个必要了。有这些拓展应该足够用了。后续看用户反馈能够在进行详细修改。有了这个可执行jar包,咋们就能够简单的写一个批处理icodetools.bat:
cd %~dp0
set aapt_path=D:\Android_tools\AndroidSdk\build-tools\23.0.1\aapt.exe
java -jar libs\icode_tools.jar %~dp0 src.apk %aapt_path% jw cn.wjdiankong.jw.utils.JWUtils printStackTrace
adb install -r signed.apk
pause..
首先进入当前目录,而后设置aapt_path环境变量,接着就要调用咋们导出来的icode_tools.jar包了,看到这里输入了上面对应的6个参数信息。这里能够修改的,好比原始apk路径,aapt路径,打印消息的tag等。最后为了方便直接在一键化安装apk。固然这一步可能会失败,不过失败了能够本身想办法安装便可,签名以后的apk是signed.apk,未签名的apk是unsigned.apk,能够本身签名的。
而后就是须要额外的jar包,由于在编译类文件的时候须要引用到系统api,因此这里要用到android.jar文件,放在当前目录的libs下面,同时icode_tools.jar也是在这个目录下,最后就是还须要一个打印消息的工具类JWUtils.java了,因此最终咋们工具的目录是这样的结构:
这里的签名文件信息在工具中写死了,因此这里不支持修改,若是想本身重签名可使用工具运行完以后在当前目录下有一个unsigned.apk文件进行操做便可。
有了这个工具,咋们确定想火烧眉毛的尝试一下,如今咱们只须要双击icodetools_1.0.bat批处理便可坐等结果了:
看到了,全部步骤一鼓作气,多么智能一键化,不再用那么费劲了。运行完命令以后的目录有了签名和未签名的apk文件以下:
后续还能够本身操做这两个文件。想怎么玩就怎么玩。
工具如今已经有了,可是咱们上面都是用了简单的案例apk进行操做的,这个明显不符合现实,咱们为了检验此工具的牛逼性,必须用一些大型app来作实验。由于真的勇士勇于面对淋漓的鲜血!下面就用一个如今很火的直播软件来进行操做,有人说为什么不用微信,别着急微信后面再用。
看到了,这里由于应用包含了多个dex,并且每一个dex文件较大,因此在处理的过程当中会比较耗时的,须要慢慢的等待
等操做结束以后,咋们直接运行应用,输出log信息:
日志刷刷的,太辣眼睛了彻底把握晃晕了,因此这个就是咱们此次须要解决的第三个问题了。如何让这些日志信息不霸屏,在指定地方打印咱们想要的结果。
从上面能够看到咱们完成了全部的一键化操做,可是惋惜的是被那些日志霸屏了,彻底懵逼的状态。因此这里我还得解决一个问题,就是给这个日志加一些过滤规则,可以很好的控制日志,让他受我控制。这个问题其实和上面的工具没多大关系了,由于在前面咱们知道,那个打印方法已经被弄出来放到了JWUtils这个类中,而这个类是工具须要编译而后插入到dex中的,因此咋们就能够直接修改JWUtils中的打印日志的方法便可。
下面就是我写的JWUtils类,内部已经有了一些打印信息的过滤规则,主要包括:控制日志的总开关,须要打印日志的方法名,返回类型,参数类型,类名等规则。这个规则是一个字符串内容:
-s 1 -m JW -r int -p [int,java.lang.String] -c JWUtils
-s:表示日志总开关
-m:表示须要打印日志的方法名称
-r:表示方法返回的类型
-p:表示方法的参数类型,多个类型直接用分号隔开
-c:表示须要添加日志的类名
而后我把这行字符串内容保存到/data/local/tmp/log.txt文件中,为何要保存到这里呢?有的同窗想保存到SD卡中,可是假若有的应用没有声明SD读写权限,那怎么办?咱们最终的JWUtils类是被插入到应用中的。因此就想到了系统有一个不须要权限有没有沙盒权限限制的目录:/data/local/tmp/。
下面简要的说一下这个过滤规则吧:
首先咱们能够在一个方法中获取当前方法的堆栈信息,因此就能够获取到当前方法名和类名了:
StackTraceElement[] stackElements = new Throwable().getStackTrace();
由于咱们想要获取到被插入打印代码的方法信息,因此这里只要获取数组的第二个元素便可,第一个元素实际上是JWUtils.printStackTrace方法信息了。有了这个元素以后直接调用它的两个方法就能够获取到当前方法名称和类名称了,这个也就能够作到方法名和类型的过滤规则了。
而对于上面只能简单的获取到指定的方法名和类型,却获取不到对应方法的签名信息,好比参数类型,返回值类型等。因此这里得费点事,就是须要经过上面获取到的类名而后用Class.forName方法获取到对应类的Class对象,而后在获取该类全部的方法信息:
而后用一个全局数组进行保存,在结合上面的方法名去遍历这个数组,就能够获取到指定方法名对应的方法信息了:
这样就能够作到方法的返回值类型,参数类型的过滤规则了,可是有同窗会发现这里有一个问题,假如一个类中有重构方法,也就是方法名相同的可是方法签名不同,这里由于只是经过检测方法名来获取到method的信息的。因此对于同一个类中重构方法是没有过滤效果的。
最后就是一个日志总开关了,因此最终的过滤规则以下:
有了这个规则以后,咋们再次操做一下,把这个JWUtils类放到icode_tools目录中,而后再次跑一下icode_tools批处理,安装便可,这时候咱们先设置一个过滤规则,直接使用命令就进行操做了:
我开始的时候把日志关闭,而后在打开,在关闭看一下效果,咱们使用echo命令写入一条规则关闭日志:
看到了吧,这里咱们经过总开关能够控制日志输出了,下面再来一条实际的过滤规则,就是经过包名,方法名,返回类型等规则操做一下:-s 1 -m a -c com.meelive.ingkee.v1.core.a.a
这里咱们经过方法名和类型进行过滤限制,打印以后的结果都是指定类名的方法日志了。
上面操做的是某直播软件,为了有说服力,我得用微信在尝试一下哈:
看到了,此刻咱们加上规则以后,打开微信顿时以为爽多了,日志很少,慢慢操做查看具体方法,从微信的日志看,这个版本已经开始使用热修复框架Tinker了。后面得赶快出一篇分析Tinker框架的文章了。
最后在来看一下QQ的日志:
注意:此过滤规则能够本身定义的,由于全部的打印消息逻辑都在JWUtils类中,上面说到这个类是开放出来的,也就是能够本身随便定义这个类的信息!
好了,到这里咱们已经解决了在前一篇文章中遇到的三个问题,也是填完了工具的坑,下面来总结一下这三个问题:
第一个问题:方法数超了问题,由于咱们给每一个类都添加了一个打印堆栈信息的方法,因此若是一个dex中包含不少类的话,那么就会额外增长不少方法,在使用dx工具进行转化的时候出现了方法数超的问题。
解决方案:能够把打印堆栈信息的方法抽取到一个工具类中,而后把这个类插入到dex中,这里采用的方案是经过dex2jar工具转化dex成jar文件,而后在编译须要插入的类文件,把编译以后的类文件直接塞入到jar文件中,最后在原先的dex基础上每一个类中的每一个方法只须要调用JWUtils.printStackTrace方法便可。
第二个问题:一键化完善工做,这个是由于咱们在前面文章中操做的时候发现从抽离apk中的dex文件到最终重签名apk文件有不少步骤,可是都是人工操做的,很是繁琐,因此能够把这些步骤进行整合一步到位。
解决方案:先从apk中解压出dex文件和签名文件==》利用aapt命令删除apk中的签名文件==》添加代码到dex中==》编译工具类获得class文件,塞入到jar文件中==》使用dx命令转化成dex文件==》使用aapt命令覆盖apk中旧的dex文件==》使用jarsigner对apk从新签名。
而后把工程导出一个可执行的jar包,这里为了后续扩展,就提供了执行的入口参数:
1》工具运行的当前目录
2》须要处理的源apk文件路径
3》aapt命令的路径
4》打印信息的tag,默认是:jw
5》打印信息的类名,默认是:cn.wjdiankong.jw.utils.JWUtils
6》打印信息的方法名:默认是:printStackTrace
而后咱们还须要提供一个批处理icodetools.bat文件,主要执行的命令是:
java -jar libs\icode_tools.jar %~dp0 src.apk %aapt_path% jw cn.wjdiankong.jw.utils.JWUtils printStackTrace
第三个问题:优化打印日志信息规则,这个是由于当咱们使用一键化工具生成apk安装运行以后发现打印日志太多致使霸屏,并且应用自己还会出现了ANR问题。因此得想个办法控制打印日志输出。
解决方案:加一些日志输出过滤规则,首先咋们得有一个日志总开关,而后是能够指定须要打印方法所属的类名,方法名,以及方法的返回类型,参数类型等过滤规则。
须要注意的是,这个过滤规则都是工具使用者能够本身实现的,就在JWUtils类中,代码能够自行修改,若是不想修改,默认的我已经加了这些规则:
-s 1 -m JW -r int -p [int,java.lang.String] -c JWUtils
-s:表示日志总开关
-m:表示须要打印日志的方法名称
-r:表示方法返回的类型
-p:表示方法的参数类型,多个类型直接用分号隔开
-c:表示须要添加日志的类名
咱们能够经过echo命令给/data/local/tmp/log.txt文件输入规则来控制日志输出。
本文中咱们能够学习到一些知识点和经验值:
一、了解到一种往dex文件中插入一个类文件的方法
先把dex转成jar文件,而后解压jar文件,复制对应的类文件到解压目录下,而后在使用jar命令进行class文件从新打包成jar文件便可。最后在使用dx命令转成dex文件。
二、了解到如何在一个方法中获得该方法的签名信息。
经过堆栈信息获取到该方法的名称和所在的类名,而后在使用Class.forName方法经过类名获得类对象,而后在使用反射获取到该类对应的全部方法签名信息,而后经过以前的方法名进行检索获取到对应方法的签名信息。
三、javac,jarsiginer,aapt命令的常见用法。
一、当前目录的须要操做的apk文件名称默认是src.apk文件,若是想修改apk名称,能够手动的修改icodetools.bat中的apk文件名
二、在icodetools.bat中能够指定当前日志的tag,默认值是jw
三、当前目录下还有一个JWUtils.java这个java文件,这个类中有一些打印方法,能够根据本身的需求定义一些方法,可是定义的方法必须有要求:
1》必须是static类型
2》方法只容许有一个参数是String类型的,而这个参数就是打印的日志tag
3》方法名称能够随意指定,可是必须在icodetools.bat中保持一致
因此最终的方法模板为: public static XXX YYY(String tag)
这个类的名称能够变更,但必须和icodetools.bat中保持一致
四、当前目录下的libs目录中是工具依赖的jar包,不能够随便修改
五、当前目录下的JWUtils.java文件名和包名都不可变更
六、cyy_game.keystore签名文件名不可进行修改
七、若是想本身再次签名,可使用unsigned.apk文件操做,signed.apk是使用了cyy_game.keystore文件签名
八、在icodetools.bat中须要手动设置aapt命令的路径
九、工具运行前必须配置JAVA_HOME环境变量
十、现阶段只支持JDK1.7以及如下版本编译器,不支持1.8以及以上的
注意:工具目录下有两个脚本,一个是icodetools_1.0.bat,一个是icodetools_2.0.bat,这两个工具主要是由于为了兼容更多的apk,默认最好先采用icodetools_1.0.bat工具进行尝试,失败了能够在使用icodetools_2.0.bat工具,若是都失败了那就要反馈问题给我了!
第一个问题:Uncaught translation error: com.android.dx.cf.code.SimException
这样的错误是由于工具版本不兼容问题,能够尝试使用另一个版本操做。若是icodetools_1.0.bat操做有问题具使用icodetools_2.0.bat工具操做,若是icodetools_2.0.bat工具操做有问题就是用icodetools_1.0.bat工具进行操做。若是两个工具都有问题那就是真的有问题了,记得给我反馈!
第二个问题:拷贝文件失败:java.io.FileNotFoundException: JWUtils.class
这个错误主要是由于工具内部会采用javac编译JWUtils类文件,这里应该是编译失败了,大部分缘由是由于JWUtils这个类文件的编码格式和语法格式致使的,因此解决版本,能够本身使用javac命令进行编译看看具体是哪里编译出了问题。
第三个问题:bad class file magic (cafebabe) or version (0034.0000)
这个问题是由于javac这个命令是1.7以上版本的,也就是JDK版本。可是此工具如今只支持JDK1.8版本如下的。因此这里须要设置JDK版本。
第四个问题:成功注入代码安装以后发现无日志信息
这个问题可能有两个缘由:第一个是须要检查日志的tag是否正确,主要经过查看icodetools.bat文件中的执行命令,第二个缘由是由于默认状况下开始日志开关是关闭的,因此咱们还得手动打开,首先得去/data/local/tmp目录下,而后使用echo “-s 1” >log.txt,打开日志便可。
固然还有其余问题,因此我但愿你们在使用的过程当中遇到问题以及一些优化建议均可以提给我,我会尽快修复!
工具下载地址:https://github.com/fourbrother/icodetools
声明:有人认为有了这个工具火烧眉毛的手痒想立马下载尝试,可是我想说这尚未结束,由于后面一篇文章才是重点,任何一个工具都须要发挥其做用才是个好工具,因此下一篇文章就会带你们用这个工具来破解一些app!
后期优化:现阶段此工具支持Windows系统,后续会增长Mac和Linux系统,现阶段只支持apk根目录下的dex文件,不支持其余目录下的dex文件处理,因此对于有些apk此工具处理过程当中会出现错误!
解决了这三个问题以后,咋们的工具才算是比较完整的可以用于生产的工具了,可是由于是本人业余时间编写的,因此我相信这个工具确定还有一些漏洞,以及须要优化改善的地方,因此我先将此工具的初版本给出,很是欢迎你们一块儿使用,若是在使用的过程当中发现有一些问题,必定要记得给我留言,我会当即修复和改善,我相信一个好的工具是靠你们一块儿贡献的。问题反馈能够在我微信私聊我或者是在微信公众号留言都是能够的,我会第一时间回复,先拜谢各位使用者了。写了这篇文章以后并无结束,由于后面还有一篇文章会详细介绍这个工具的实际使用,如何用它来解决咱们的实际问题,好比寻找hook点。