1 | 好久没写博客了,要深刻检讨下! |
前言:
在Android中没有经过加密的Apk给人的感觉就是在裸奔,通过apktool,dex2jar,AndroidKill等各式各样的反编译工具就可以轻松的获取其smail代码,如这个叫SourceProject的helloworld程序被apktool反编译后,对于懂smail语法的逆向工程师来说就一览无余了。破解与反破解是相对的,所以我们尽可能的给自己的Apk多穿点衣服。
原理解析
首先我们先来看下Apk加壳的步骤:
- 源Apk:需要加壳的Apk
- 加密的Apk:源Apk经过加密算法加密后的Apk
- 加壳程序Apk:是有解密源Apk和动态加载启动源Apk的外壳
首先我们拿到需要加壳的源Apk,通过加密算法加密源Apk然后与加壳Apk的dex文件组合成新的Dex文件,然后将加壳程序Apk的Dex文件替换成新的Dex,生成新的Apk重新签名。
我们先来看下Dex文件的结构:
- Magic
Magic数是为了方便虚拟机识别目标文件是否是合格的Dex文件,在Dex文件中magic的值固定值 - checksum
文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 - signature
使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。 - file_size
当前Dex 文件的大小 。
所以我们在将Dex与加密算法加密后的Apk合并生成新的Dex后需要修改新Dex文件的这三个值,为了方便从新Dex中获得加密的Apk,我们需要知道加密的Apk的大小,为了方便以后获得,我们将其大小放置在新Dex的后四位,新生成的Dex文件结构:
生成新Dex后,将加壳程序Apk的Dex文件替换,重新签名后加壳的Apk即完成了。如果觉得步骤理清了,我们来看下其具体的实现。
具体实现:
这过程一共要创建三个项目。
首先我们先创建一个需要加密的Apk项目,结构非常简单只有几个Log的打印,结构
SourceApplication.java
1 | package com.jju.yuxin.sourceproject; |
MainActivity.java
1 | package com.jju.yuxin.sourceproject; |
SubActivity.java
1 | package com.jju.yuxin.sourceproject; |
然后将其打包生成Apk。
第二个项目是一个JAVA项目用于将源Apk加密,并合并加壳程序Dex与加密后的源Apk。在贴出这个代码时我们先不用管加壳程序Dex从何而来,假设已经有了这样一个文件。我们来看下具体实现:
1 | package com.forceapk; |
我们可以看到程序比较简单和我们之前描述的一样,核心部分就在文件拼接部分和修改三个头信息的部分。
1 | //依次将解壳DEX,加密后的源APK,加密后的源APK大小,拼接出新的Dex |
1 | //修改DEX file size文件头 |
我们再来看下第三个项目:
加壳程序,这个程序主要负责将在JAVA项目中加密的源Apk获取及解密,以及动态加载源Apk。项目结构
我们看下代码:
1 | package com.jju.yuxin.reforceapk; |
这个文件比较长我们来依次分析:
1 | //第一次加载 |
通过判断用于存放解密后的源Apk文件是否存在来判断是否是第一次加载。第一次加载时通过readDexFileFromApk()来获取当前Apk的Dex文件
1 | /** |
然后通过this.splitPayLoadFromDex();将当前Dex分解,从中获取源Apk并将其解密,以及源Apk的so库
1 | private void splitPayLoadFromDex(byte[] shelldexdata) throws IOException { |
然后通过动态加载机制将加壳程序的ClassLoader替换成他的子ClassLoader这样确保既能加载自己的Class又能加载源Apk的Class
核心代码,如果这段代码不是很懂,你可能需要去了解Java反射,Android中Classloader的双亲委托模型,以及动态加载机制1
2
3DexClassLoader dLoader = new DexClassLoader(srcApkFilePath, odexPath,
libPath, (ClassLoader) RefInvoke.getFieldOjbect(
"android.app.LoadedApk", wr.get(), "mClassLoader"));
然后在Application的onCreate()中替换LoadApk以及ActivityThread中的Application,希望更清楚的明白这点需要了解下Android中Activity以及Application的启动流程,其中在加壳程序的清单文件中我们配置了源Apk的相关信息以便能找到他们
1 | <application |
实现操作流程:
先将源Apk的项目生成Apk,放置到JAVA项目中
将加壳程序也生成Apk,通过直接将apk后缀名改成zip的方式获取到classes.dex,(最好复制一份,这个后面还要用)。将classes.dex改名成shelldex.dex放置到JAVA项目中
运行JAVA项目,将生成新的Dex文件classes.dex,将新生成的classes.dex替换加壳Apk的classes.dex(通过解压软件直接拖放进去替换即可)
最后cd到apktool的目录下,使用apktool中的jarsigner对应用重新签名即可,重新签名指令
1 | jarsigner -verbose -keystore 签名文件路径 -storepass 密码 -keypass 密码 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar 签名后生成Apk路径 需要签名Apk路径 签名文件别名 |
这三个项目的Github地址(项目地址)
常见错误:
ClassNotFoundException:
这个错误主要注意:Class路径拼写有没错,加密Apk能否正确的转为源Apk,还有就是ClassLoader有没用错
Class ref in pre-verified class resolved to unexpected
implementation:
这是类被重复加载的错误,需要检查报错的类是否被别的ClassLoder已经加载过了,我的Activity在继承android.support.v7.app.AppCompatActivity时候报了这个错误,改成Activity就好了,原因还没去找。