最近看了一篇Android加壳相关的文章:http://blog.csdn.net/jiangwei0910410003/article/details/48415225
尝试根据文章的步骤来实现Android加壳的功能,在发现文章实现的效果不大理想后,本人进行了一定的调整与改进
实现效果
实现效果如下:
reinforceTest是我们的加壳Android工程,我们把需要加壳的apk放置在其workspace目录下
接着在reinforceTest工程下运行gradle的task:buildReinforceApk
在workspace目录下,我们可以找到加壳后的output.apk
加壳后的apk可正常运行:
通过dex2jar以及jd-gui,我们可以看到dex中的壳代码,但是看不到源apk的代码:
原理简介
加壳的原理大致如下图所示:
壳apk(即reinforceTest工程)的主要作用用是提供壳classes.dex,用于在启动app时解析出源classes.dex,并引导程序执行classes.dex的代码
引导程序执行真正classes.dex文件的步骤如下:
引导的Application如下:
1 | public class ReinforceApplication extends Application { |
reinforceTest工程具体的加壳步骤如下所示:
1 | task buildReinforceApk(dependsOn: 'assembleDebug') << { |
这里的Gradle Task依赖了assembleDebug Task,用于获取最新的壳apk
清理目录就不多说了,解压apk使用的是apktool工具,把源apk与壳apk反编译出来:
1 | private void decodeApk() { |
接着是修改源apk的AndroidManifest.xml文件,为啥要修改呢?首先因为源apk上有我们需要的资源文件,所以我们肯定是把加壳后的dex放入源apk中,而不是放入壳apk中。由上面的引导步骤图我们得知,我们解壳时需要先执行壳apk的Application。如果加壳后的dex放入源apk中,我们的解壳Application由于没有在源apk的AndroidManifest中注册,因此无法率先执行。所以,我们需要修改源apk的AndroidManifest,把Application的name改为壳apk的Application,同时添加一项meta-data,用于记录源apk的Application的名字,让我们在待会替换回真正的Application时知道它的名字:
1 | private void modifyManifest() { |
接着就是加壳的步骤了,这里主要调用了用Java写的加壳工具:
1 | private void reinforce() { |
工具Java代码如下:
1 | public class DexTools { |
具体的加壳原理可以参考文章顶部的链接,其中有提及,在此不做赘述
加壳后的dex如下所示:
加壳完后把新的dex覆盖旧的源apk的dex,由于文件进行了改动,因此apk在重新打包后需要重新签名:
1 | private void rebuildAndSign() { |
存在问题
目前这种加壳方式在测试过程中发现仍存在一些问题:
- 不支持AppCompatActivity:当源apk使用AppCompatActivity时,会出现资源找不到的错误,具体原因未知,以后再做研究
- ContentProvider不清楚是否支持:根据执行顺序,在APK中最先执行的几个方法应该为:Application.attachBaseContext -> ContentProvider.onCreate -> Application.onCreate。这里我们选择在Application.attachBaseContext以及Application.onCreate进行解壳以及引导程序执行的操作,不清楚是否会对ContentProvider造成影响,打算以后再做测试