App加壳之旅

前段时间博主去面试的时候,被问到了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 反编译:

App加壳之旅_第1张图片
mq项目的Res.png
App加壳之旅_第2张图片
反编译mq的Res.png

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

App加壳之旅_第3张图片
shell项目中的Res.png

到目前为止,我们解决了2个问题,1.在壳app中解密源app 2.把源app中的资源反编译复制到壳app项目中解决资源问题

那么接下来还会有哪些问题呢?博主懒癌犯了,不不一一截图演示了,目前来说至少还有以下问题
 1.资源复制到壳项目中了,那么资源id是否对应上了呢?很遗憾,这是对不上的
 2.壳app的清单怎么处理

下面来说说如何解决资源id的问题,还记得前面反编译的mq吗?我们反编译后的res中values目录下出现了一个迷之文件public.xml,这到底是何方神圣?

App加壳之旅_第4张图片
图片.png

打开这个文件一看

App加壳之旅_第5张图片
图片.png

  可以看到,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.结语

有什么问题可以发邮件到[email protected]互相探讨

附件:
百度云:http://pan.baidu.com/s/1o7XKNay

你可能感兴趣的:(App加壳之旅)