一、前言
从这篇開始咋们開始一个全新的静态方式逆向工具icodetools的实现过程。这个也是我自己第一次写的个人认为比較实用的小工具,特别是在静态方式逆向apk找关键点的时候。兴许会分为三篇来具体介绍这个工具实现:
第一篇:开凿篇,简介实现原理以及简单的初次方案实现简单的apk注入代码功能
第二篇:填坑篇。这一篇是在前一篇的基础上对工具的优化,能够应对市面上大部分的apk代码注入功能实现
第三篇:生产篇。这一篇是在前两篇的基础上利用这个工具来实际操刀怎样进行高速定位应用的关键方法功能
还记得那一年咋们 静态方式破解应用,为了更好的追踪代码位置。手动反编译成smali文件,然后加入smali日志代码,在回编译。查看日志信息来定位关键点。那种操作如今回忆是苦不堪言。操作非常复杂。
那么就尝试想想假设有一个工具能够在自己主动为每一个方法注入日志代码。这样就能够非常高速的定位到我们想要的方法,所以本文就来介绍一下这个工具的实现原理,我将其命名为icodetools。
二、实现方案
首先咋们来看一下具体实现原理吧,这个工具终于的形态应该是这种,就是输入一个apk,然后在为apk中每一个类的每一个方法加入一段打印此方法的堆栈信息日志。然后在又一次签名打包成新的apk。所以这个过程中我们最关键的地方就是怎样把日志代码插入到已经编译好的apk中。我们可能想到的有两个方案:
第一个方案:首先利用apktool进行反编译成smali文件,然后解析smali文件找到每一个方法的位置,加入指定smali日志代码。那么这里有一个非常大的问题,就是怎样分析smali文件。怎样定位到每一个方法?这个过程工作量就比較大了。所以这个方法就废弃了。
第二个方案:利用dex2jar获取到apk中的dex转化之后的jar文件。然后在解析jar文件获取到每一个class文件,解析class文件进行方法的信息获取。
这里会发现这个方法非常靠谱。由于我们解析class文件比解析smali文件方便多了。并且在这个过程中会发现有一个更大的惊喜,就是dex2jar这个工具是开源的。事实上内部实现原理就是解析dex文件格式,然后借助asm工具将其变成class文件的,这里又出现了一个非常重要的工具asm,这个工具以下会具体介绍。
说明:
1》这里非常感谢dex2jar的作者Claud大神的开源精神。这个工具的地址:https://github.com/pxb1988/dex2jar,是纯java代码,所以大家一定要先解读这个工具源代码,内部实现原理自己研读,本文不会具体介绍的。
2》这里有的同学会想到,能不能直接解析dex文件格式,然后在找到每一个类的每一个方法加入日志信息,尽管我们在前面有具体介绍dex文件格式:Dex文件格式具体解释,可是假设想在每一个类每一个方法中加入日志代码这个工作量感觉比操作smali还要复杂,所以直接将其转化成class文件进行操作就非常方便了,由于我们有asm工具。
三、方案实现
上面已经探讨了方案了,在上面提到了非常多次一个非常重要的工具就是asm,那么这个工具究竟是干嘛的呢?这个工具非常实用。他的表现之处在JavaWeb开发中的Spring框架就实用到。能够动态的解析class文件,然后能够操作这个类,比方加入类成员,方法等等操作。所以以下不多解释了,直接用一个简单的案例来看看他的强大之处。我们实现给一个类加入一个成员字段,方法,给每一个方法调用前加入一行代码。
第一、asm库基本使用
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
这里借助ClassReader来读取一个类,使用ClassVisitor来进行类信息操作,ClassWriter将操作完之后的类写入文件里
第二、加入类成员字段
上面看到了一个基本的类ClassVistor。他主要就是用来操作一个类:
这里看到的是继承了ClassAdapter。然后在构造方法中能够加入类成员信息包括字段。方法等,这里我们加入一个字段mJW。类型是String的。看看怎样加入的:
借助ClassVisitor的visitField方法就能够进行字段的加入了。然后我们执行程序之后,使用JD-GUI工具查看保存本地的class文件信息:
看到了吧,这里成功的定义了一个字段mJW。
第三、为类的全部方法前加入代码
这里我们须要借助另外一个类了MethodVisitor了,他是用来操作方法信息的:
在ClassAdapter的visitMethod回调方法中能够获取方法信息,然后进行操作:
在visitCode回调方法中利用MethodVisitor開始加入代码:
这里看到能够利用visitFieldInsn方法方法System类的静态变量out,然后在使用visitLdcInsn从常量池中获取值,最后在利用visitMethodInsn方法来调用PrintStream类的成员方法println来信息打印,执行程序之后继续借助JD-GUI来查看class文件:
看到了,类中全部的方法前面都被加了一行打印代码。
注意:有的同学发现了,上面那个加入代码怎么得到呢?难道真的要记住那么多api去加入吗?事实上不用那么复杂。Eclipse有一个插件Bytecode,能够把Java代码自己主动生成相似于上面的代码。咋们之后手动拷贝一下就好了!工具后面会说。
第四、为类加入成员方法
以下在来看最后一个操作吧,就是给类中加入一个成员方法,有了上面的操作之后我们应该知道。假设要操作类就要借助ClassVisitor类。要是操作方法就须要借助MethodVisitor类,那么这里为类加入成员方法,借助ClassVistor类来进行操作了,可是在操作之前咋们得先搞好一个工具Bytecode,这个是Eclipse插件。安装非常easy:
安装完毕之后,能够打开视图栏:
然后选择Java中的bytecode视图就能够了:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
以下咋们首先在一个类中编写好我们想要加入的方法代码,然后点击Bytecode视图卡片就能够看到相应的asm代码:
看到了吧。这样操作是不是如此简单,我们能够把这段asm代码直接复制到AsmUtils中:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
然后在ClassVisitor的构造方法中直接调用这种方法就能够了:
再次执行程序之后,使用JD-GUI查看类信息:
成功的加入了printStackTrace方法了。就是如此简单。借助Eclipse的强大工具Bytecode就能够了,以后都不要在自己去调用asm库的api去手动编写了。这个工具太好了得记住它。
四、工具案例实践
上面就介绍完了怎样操作一个类和方法,给类加入字段,方法,在每一个方法之前加入一行代码。
以下就得进入本文的主题了,怎样给apk应用中每一个类中的每一个方法加入一行打印日志信息。
在開始的时候我们有了方法,就是借助工具dex2jar源代码。他内部也是解析dex格式。然后利用asm将其信息变成一个class文件的,那么我们仅仅要在这个过程中得到ClassVisitor和MethodVisitor这两个对象,就能够尽情的干非常多事了。所以第一步你一定要先看懂dex2jar的源代码。这里我不会对源代码进行具体分析了,用过dex2jar工具的都知道。找入口非常easy,直接看他的一个d2j-dex2jar.bat脚本信息:
Dex2jarCmd就是工具的入口类:
然后分析代码,会发现处理的核心类是Dex2jar类:
这里会看到能够拿到ClassVisitor类对象的。那么我就能够加入打印堆栈信息的方法了:
这里须要注意的是打印消息用了Android的Log.d方法。然后在每一个方法调用之前在调用这个打印方法:
这里过滤依旧过滤了构造方法和静态代码块方法。
事实上我们就须要这么做就能够了,以下咋们就用一个简单的Apk来做一下实验,咋们把apk中的classes.dex文件解压出来。为了简单直接跑这个dex2jar工具,能够在Dex2jarCmd開始处构造一个简单的命令:
输入的dex文件为指定文件夹的,输出的jar文件也是指定文件夹的:
然后咋们就能够执行程序了。执行结束之后我们得到了一个classes.jar文件,能够使用JD-GUI进行查看:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
到这里我们就成功的。把apk中的classes.dex中的每一个类每一个方法加入了我们自己的打印消息的代码了,已经成功一大半了,以下为了验证效果。咋们还得把classes.jar转成dex,在弄到apk中执行看效果。这个过程就简单了,利用系统的dx命令,把classes.jar在变成classes.dex文件:
然后在把这个classes.dex文件塞到apk中,只是这时候得又一次签名了。能够借助jarsigner工具:
jarsigner -verbose -keystore cyy_game.keystore -storepass cyy1888 -signedjar signed.apk unsigned.apk cyy_game.keystore -digestalg SHA1 -sigalg MD5withRSA
然后咋们就能够安装apk。然后開始执行效果:
看到效果了吧。表示成功的不要不要的。
有点小兴奋,通过栈信息加上我们出发一个事件之后就能够定位这个事件的方法了。
对于我们在使用hook的时候寻找hook点非常重要。
五、技术总结
到这里我们本文的知识点就算结束了,可是整个工具开发并没有结束。以下就来总结一下本文涉及到的知识点:
1、学会了利用asm库来操作类,实现加入字段,方法等操作。
2、了解到了Eclipse的一个强大插件Bytecode,能够高速得到java代码相应的asm代码
3、了解了dex2jar的实现原理,内部也是借助于asm来进行操作的
事实上本文的这个自己主动注入代码工具绝大部分是借助了dex2jar这个工具的,我们仅仅是在适当的地方加入了我们想要的信息代码。在操作类的时候仅仅要有ClassVisitor对象就能够操作类,MethodVisitor对象就能够操作方法了。
而在dex2jar中正好有这两个对象,所以我们实际要操作的内容并不复杂。当我们使用通过改动之后的dex2jar得到了classes.jar之后,在用dx工具将其还原成classes.dex文件,在放到apk中进行重签名验证。
六、遗留的问题
假设到这里有的同学认为这个是挺简单的。那事实上就错了,由于開始的时候我已经说了后面另一个填坑篇文章,在那篇文章会具体说明在实际操作其它企业app会遇到哪些问题。我们须要继续优化这个工具,同一时候对这个工具最好能够做到一键化工作,所以我们须要解决这几个问题:
1、上面看到我们是为每一个类加入了一个打印栈信息的方法。那么就有一个非常大的问题,假设一个dex文件过大,包括的类非常多,那么就是添加了非常多方法,对于dx进行转化的时候会发现方法数超了。
2、在实际的apk操作过程中会发如今每一个方法中插入日志之后。当你启动app的时候那日志差点儿会被霸屏的。并且一个庞大的app内部调用的方法非常多,导致应用会出现无响应状态。所以咋们得弄个开关以及想在哪些方法中加入。
3、上面在看到在整个过程中,先把dex转成jar,然后再把jar转成dex。在放到apk中,在签名,整个过程我们都是手动操作的。显得非常的费劲,所以我们还得优化这个工作。做到真正意义上的一键化,输入一个apk,输入就是已经加入日志信息的apk就能够了。
所以后面一个填坑篇文章会有非常多工作要做的。当坑都填完了,咋们就得实际生产拿一些app进行实战操刀了。最后在来看一下我总结的一张原理图吧(能够点击查看高清大图):
严重说明:
===》本文介绍的是基础篇,能够看到我们是大致走通了整个流程。可是这个和终于的工具实现差的非常多,兴许还有非常多问题须要解决,所以重点事实上在下一篇中,那里会去解决在实际使用过程中遇到的各个问题。以及最重要的就是怎样把全部的步骤连贯起来,一键化开发完好。一定要记得看下一篇内容,那里才是主战场!
===》一定要自己去github上下载dex2jar的源代码,自己先调试走通程序。不然一切都是枉然,由于这个工具是纯Java编写的,代码不是非常多。所以难度不大。
七、总结
对于这个自己主动插入代码的工具我个人认为有需求採取这么干的,由于曾经遭受过那种手动注入代码的痛苦,可是从本文的工具能够看到。对于一些加固app是无能无力的。而工具的理念来源是美团的一个热修复框架Robust的。
关于源代码的话等后面都介绍完了。我把我写的部分公开。可是你还是得先去看看dex2jar的源代码。又開始了写这种文章。每次写完感觉自己身体被掏空了一样。多多点赞。打赏就在好只是了。
很多其它内容:点击这里
关注微信公众号,最新技术干货实时推送
扫一扫加小编微信
加入时注明:“编码漂亮”否则不予通过!
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" style="max-width: 100%; border: none; max-height: 100%;" />
加入时注明:“编码漂亮”否则不予通过!
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" style="max-width: 100%; border: none; max-height: 100%;" />