前段时间博主去面试的时候,被问到了app安全加固的问题,博主工作2年多,一直在外包公司,对加固这个概念并不熟悉,仅停留在知道用360加固的这个层次,回来跟之前的老大吐槽了一下,老大安慰我一番说加壳这个东西你现在不了解也很正常,感谢老大让我get到了一个新名词T^T。但是博主是个有(xian)好(de)奇(dan)心(teng)的程序猿,不知道则已,一旦知道了有这个东西存在,体内的洪荒之力就压制不住了。
1.准备工作
俗语有云:外事问谷歌,内事问百度。但博主作为一个英语1级伤残的假猿,不到迫不得已是不会找谷爹的,在百度上一搜,很容易就发现大量的app加固介绍,原理等,但是有一个问题,就是这写文章都他喵一毛一样,偶尔有一点不一样的都是90%相同的,而且都是基于android 2.3 的(what the fuck ?问号脸)。不过作为入门这篇文章给了博主巨大的帮助,还是得点个赞。传送门:Android中的Apk的加固(加壳)原理解析和实现
小白请到原文去查看原理,这里不再赘述,只稍微画下重点:dex的文件格式,对于我们理解如何去插入和读取被加壳的app有很大作用。
2.技术细节
app加壳主要分3个模块:
1.被加壳的app,也就是你想要保护的app
2.壳app,一个用于保护你app的壳
3.为你的app穿上壳。
2.1被加壳app
随便新建一个项目,打包成一个apk,我这里重命名为mq.apk
2.2 壳app
注意,这里之后都是重点(敲黑板)。
刚刚上面传送的那篇文章,由于基于android 2.3,所以有些地方已经会出现bug了,先贴一下代码(因为代码太长,所以先划重点,源码后面附上):
public class App extends Application {
private static final String appkey = "APPLICATION_CLASS_NAME";
private String apkFileName;
private String odexPath;
private String libPath;
//真正的application
private Application reallyApplication;
//资源文件路径
private String externalResourceFile;
//这是context 赋值
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
·····
//重点1 sdk 19及之后 mPackages不再是HashMap而是ArrayMap了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
wr = (WeakReference) ((ArrayMap) mPackages).get(packageName);
} else {
wr = (WeakReference) ((HashMap) mPackages).get(packageName);
}
·····
//重点2 替换资源,旧版的替换资源方法好像不是很好用,我上网另外找了个替换资源的方法
InstantRunApp.monkeyPatchExistingResources(apkFileName);
·····
} catch (Exception e) {
e.printStackTrace();
Log.i(appkey, "classloader error:" + e.toString());
}
}
private void initApplication() {
// 重点3 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
String appClassName = "com.xiao.ge.base.App";
//有值的话调用该Applicaiton
Object currentActivityThread = RefInvoke.invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[]{}, new Object[]{});
//获取当前ActivityThread中的mBoundApplication变量
//mBoundApplication的作用是用来makeApplication,详见
......
}
/**
* 释放被加壳的apk文件,so文件
*
* @throws IOException
*/
private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
······
String name = localZipEntry.getName();
String cpu = PropertiesUtils.getProperties();
if (name.startsWith("lib/") && name.endsWith(".so")) {
String contains = name.substring(name.indexOf('/') + 1, name.lastIndexOf('/'));
//重点4 我这里判断了设备CPU支持何种类型的so文件,原版是没有的,无脑全部复制
//结果导致32位的手机上用了64位的so,所以我粗略加了个判断,目前没发现有毛病
if (contains.equals(cpu)) {
File storeFile = new File(libPath + "/"
+ name.substring(name.lastIndexOf('/')));
storeFile.createNewFile();
FileOutputStream fos = new FileOutputStream(storeFile);
byte[] arrayOfByte = new byte[1024];
while (true) {
int i = localZipInputStream.read(arrayOfByte);
if (i == -1)
break;
fos.write(arrayOfByte, 0, i);
}
fos.flush();
fos.close();
}
}
localZipInputStream.closeEntry();
}
localZipInputStream.close();
}
}
重点5:如果你的源app里用到了'com.android.support:appcompat-v7:25.3.1'这个依赖,那么壳app里是不允许再有这个依赖的,会报类冲突,其他依赖应该同理
好的目前壳app的重点就划到这里,接下来是资源文件的配置,我们知道,要启动一个activity,是要在清单文件 AndroidManifest.xml中声明的,那我这个壳app清单文件里啥也么有,难道要一个个从被加壳的app复制进来?这是不科学的 ,还有源app的各种资源,我该如何去使用呢,莫急,且听贫道娓娓道来。
先说res下的资源,光从源app中的res中复制资源到壳app,那是不行的,因为有很多依赖中的资源,源app中的res中也是没有的,要获取这些资源,我们可以把前面打包好的mq.apk 用apktools 反编译:


可以看到,反编译之后的资源多出了n多,我们把这些资源全部复制到壳app项目中去

到目前为止,我们解决了2个问题,1.在壳app中解密源app 2.把源app中的资源反编译复制到壳app项目中解决资源问题
那么接下来还会有哪些问题呢?博主懒癌犯了,不不一一截图演示了,目前来说至少还有以下问题
1.资源复制到壳项目中了,那么资源id是否对应上了呢?很遗憾,这是对不上的
2.壳app的清单怎么处理
下面来说说如何解决资源id的问题,还记得前面反编译的mq吗?我们反编译后的res中values目录下出现了一个迷之文件public.xml,这到底是何方神圣?

打开这个文件一看

可以看到,public.xml文件下存储着大量的id,这个id就是资源在源app中引用的id,我们只需要把这个文件copy出来,待会备用,有同学问,我直接复制到壳项目中的values下不就好了吗?这样做的话在壳项目打包时,public.xml会被重新生成覆盖,等于没改,所以public.xml要在壳app打包之后修改。
那么清单文件如何处理呢?相信聪明机智的你已经猜到了,没错,还是把反编译的mq中的AndroidManifest.xml复制出来备用
到此,资料基本已经完成了,我们可以把壳app打个包,命名为shell.apk
到现在,我们应该有以下资料:
1.源app->mq.apk
2.源app反编译出来的public.xml
3.源app反编译出来的AndroidManifest.xml
4.壳app->shell.apk
2.3 加壳
1.反编译shell.apk,把其中的public.xml和AndroidManifest.xml替换成前面源apk中反编译出来的public.xml和AndroidManifest.xml,并把
AndroidManifest.xml中的application 修改成壳app的application
2.用apktools重新打包第一步解压了的shell.apk
3.把重新打包的壳app重命名为shell.zip并解压得到其中的classes.dex,复制出来重命名为shell.dex
4.把mq.apk、shell.dex、Shell.class放在同一目录下(稍后给出Shell.java,读者可自行编译得到class文件)
5.第4步之后我们会得到一个新的classes.dex,把这个classes.dex复制并替换掉shell.zip中的classes.dex
6.重命名shell.zip为shell.apk
7.重新给shell.apk签名
8.完成
3.结语
有什么问题可以发邮件到714273725@163.com互相探讨
附件:
百度云:http://pan.baidu.com/s/1o7XKNay
网友评论