【31】File IO 项目实战—dex文件改造

(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

File IO 项目实战—dex文件改造

1.dex文件加密

(1)热修复:大部分时间用的是gradle
(2)为什么用IO讲解?
因为学的是IO,思想是相通的。

2.APK文件反编译

2.1什么是反编译

(1)定义
利用编译程序从源语言编写的源程序产生目标程序的过程。

2.2怎么进行反编译

(1)先了解apk的文件构造

在这里插入图片描述

在这里插入图片描述

(2)Android中的dex文件就相当于Java中的jar文件,反编译的过程其实就是将classes.dex文件进行反编译,找出源代码之后,就可以修改功能。

(3)如果应用程序没有加固,就没有密秘可言,至少需要prguard进行代码混淆。

  • 混淆其实是一个代码替换,ActivityA—>a,但是里面的一些逻辑是看的很清楚的。意义并不是很大,但是有意义,因为这样做确实可以带来一定的安全性。

  • 免费的加固方案意义也不是很大,付费与不付费是有区别的。

2.3加固方案的手段

在这里插入图片描述

(1)反模拟器
(2)代码虚拟化

在这里插入图片描述

  • 由Android系统架构得知我们所开发的应用程序在上层,代码很容易使用通用的模式与通用的API,这种情况之下,所有的代码都是运行在虚拟机之上的。

  • 既然APP是在虚拟机上运行的,可以尝试不让其在虚拟机上运行。

  • 因为所有的Java代码都是运行在虚拟机上的,所有的编解码都是一样的,在这种情况下如何去加固?无论如何去加固,都逃不脱编解码方式。

  • 现在的商业加固方案,是另外起一套内存虚拟机的方式,让所有的应用都是通过这种内存虚拟机的方式编解码,相当于是多了一套编解码的方式。相当于在虚拟机上多了一个虚拟机,即运行在自己的虚拟机上,而不是在JVM虚拟机上,这就是代码虚拟化。

(3)加密

  • 样本的部分代码以压缩或者加密的形式存在。

  • 因为dex文件加载,都是一个类加载。

  • 任何一个.class文件都是需要在内存里面才能运行,因此必须将dex文件加载到内存中。

  • 如何加载dex文件呢?

  • 加载到内存中之后,首先会检查这个类有没有加载进来,如果没有加载进来,虚拟机就把这个类填充到内存中来,然后运行。

  • 这一过程当中,可以给我们一个空隙,即在运行之前加载进来即可。

  • 一个app存在多个dex文件,它并不是一次性就加载到内存中来,而是需要的时候再去做加载。因此可以将dex文件分成很多部分,非核心代码与核心代码。

  • 可以将非核心代码展示出来,当非核心代码在去使用一些核心代码的时候,然后再将它加载进来,动态的去加载。加载的过程中核心代码加固,非核心代码不加固,以此来提高代码的安全性。

  • 没必要全部加固,因为解密速度会比较慢。

2.4加固方案的总体思想

一个程序员的故事:
辛辛苦苦找到一个对象,结婚后发现是个母夜叉。不给管钱就闹,晚上睡觉她趴着睡,导致这
程序员无法去洗脚了。然而这个程序员很努力,平时除了上班,还能够做点外包,赚点外快。
所以他就想到了把工资卡上交,而把赚到的外快放到了自己的小金库。从此过上了性福生活。

2.5加固的方案

在这里插入图片描述

(1)将dex文件分为两个部分,一个是壳dex文件,一个是源dex文件。

(2)源dex文件给它加密,壳dex文件不加密。所以看到的是壳,而运行的时候,去解密源dex文件。即可以使用壳dex文件去解密源dex文件。

3.APK加固的方案原理

3.1APK加固总体架构

在这里插入图片描述

3.1.1壳dex与源dex如何划分?

(1)将apk的文件分为两类,第一类为dex文件,其他的文件为第二类。是因为我们需要对dex文件进行加密,不加密的东西踢出去,需要加密的文件加进来。

(2)文件解压缩,对文件进行分类。即对文件进行unzip操作,然后对文件进行过滤,过滤完成之后,一类是dex文件,一类是除了dex文件之外其他的文件。

(3)dex文件加密,即使用AES加密,需要将文件的二进制全部读取出来,读出来之后,然后对每一个二进制用AES进行加密,加密完之后再把这个二进制写回去。 因此就涉及到大量的文件IO。

(4)做一个壳dex,将加了密的源dex文件和索dex文件进行合并,合并之后构成新的dex文件。这个新的dex文件即为整个apk文件。加了密之后的文件有可能为变得更大,因此可以做一个对齐操作。

  • 对齐:即对dex文件的优化。

(5)此时的APK文件是不能运行的,因为没有签名。因此需要完成签名。

(6)整个过程会涉及到大量的文件IO操作。

3.1.2问题

(1)dex文件可以随便拼凑吗?

(2)壳dex怎么来的

(3)如何签名?

(4)如何运行新apk(如何脱壳)?

3.2dex文件的意义

3.2.1Dex文件是什么?

(1)加固的目的是保护dex,直接而言就是对dex文件进行操作,对dex文件动刀子,必须知道dex文件是什么,能否直接动刀子。什么是源dex?什么是壳dex?

3.2.2APK打包的流程

(1)加壳是在原来apk的基础上加一层保护壳,dex文件修改了就需要重新打包,否则apk安装不了。这就需要我们详细学习apk如何打包的。

3.2.3Dex文件加载流程

(1)加壳后的文件是不能直接用的,dex文件是加密的,所以我们需要对他进行解密,解密后的dex文件如何加载?

3.3Dex文件

在这里插入图片描述

(1)每一个文件右键-属性中看到的信息,都来自于文件头。

(2)每一个dex文件分为文件头、索引区与数据区

(3)数据区中类的定义区可以展示,而数据区不展示,即方法名可看见,而方法体看不见。

(4)如果要对文件进行修改的话,需要对checksum,file_size,siganature修改一下即可。

(5)因为明白了dex文件的构造,才能完成对dex文件的修改。

3.4APK打包的过程

(1)依赖的工具
D:\sdk\build-tools\28.0.2

在这里插入图片描述

即打包的过程依赖这些工具去完成,而Android Studio将打包的过程可视化了。比如在xml布局文件中添加一个控件,就会相应的在R文件中产生生成一个对应的id,就是通过这些工具自动去完成的。打包的过程是由gradle去完成的。

在这里插入图片描述

(1)首先将资源文件利用aapt工具将其纳入到R文件中管理。
例如往xml中添加一个控件,就会在R文件中添加一个id.
aapt工具谁调用的?是由gradle调用的。

(2)有了aidl文件,会通过aidl工具自动的生成java的接口。

(3)再加上Android本身的java代码(Application Source Code)

(4)通过以上三步将所有的文件变成了Java代码,然后使用Java编译器将其打包,编译成class文件。

(5)Android运行的不是java包,是dex文件,因此首先需要将class文件生成dex文件,dex与jar包之间是互通的,因此这一步是使用dx.bat工具将class文件生成dex文件。

(6)将Compiled Resources 编译资源、.dex files (dex文件)、Other Resources(其他资源文件)通过apkbuilder工具打包成apk文件。

(7)使用jarsigner工具对apk文件进行签名,生成签名的apk文件。

学任何技术首先并不是马上就深入,而是先学一些基础性的知识,然后再往深处走。
例如学习Android的AMS,需要先掌握如下技术基础:
(1)handler
(2)binder
(3)WMS
(4)热修复
然后再去学。

3.5加密过程

  • 加密文件只需要对apk文件中的dex文件进行加密
3.5.1将apk中的dex文件进行加密

(1)首先需要对dex文件进行解压缩,要从一系列文件中找到dex文件,即过滤文件。

  • BufferOutputStream缓存是减少磁盘磁头的操作。
  • 输出流只要不close就一定内存泄漏

(2)对加密后的文件进行重新命名

  • 因为所有的文件都是.dex,而dex文件会有壳dex文件和源dex文件
  • 为了区分源dex与壳dex,因此做一个重命名的操作
File tempFileApk = new File("src/source/apk/temp");
        if (tempFileApk.exists()) {
            File[]files = tempFileApk.listFiles();
            for(File file: files){
                if (file.isFile()) {
                    file.delete();
                }
            }
        }

        File tempFileAar = new File("src/source/aar/temp");
        if (tempFileAar.exists()) {
            File[]files = tempFileAar.listFiles();
            for(File file: files){
                if (file.isFile()) {
                    file.delete();
                }
            }
        }

        /**
         * 1.处理原始APK,加密dex
         */
        AES.init(AES.DEFAULT_PWD);
        //1.1解压apk
        File apkFile = new File("src/source/apk/app-debug.apk");
        File newApkFile = new File(apkFile.getParent() + File.separator + "temp");
        if(!newApkFile.exists()) {
            newApkFile.mkdirs();
        }

        Zip.unZip(apkFile,newApkFile);

        //1.2加密文件
        File mainDexFile = AES.encryptAPKFile(apkFile,newApkFile);

        /*
        1.3对加密后的文件进行重新命名
        (1)因为所有的文件都是.dex,而dex文件会有壳dex文件和源dex文件
        (2)为了区分源dex与壳dex,因此做一个重命名的操作
         */

        if (newApkFile.isDirectory()) {
            File[] listFiles = newApkFile.listFiles();
            for (File file : listFiles) {
                if (file.isFile()) {
                    if (file.getName().endsWith(".dex")) {
                        String name = file.getName();
                        System.out.println("rename step1:"+name);
                        int cursor = name.indexOf(".dex");
                        String newName = file.getParent()+ File.separator + name.substring(0, cursor) + "_" + ".dex";
                        System.out.println("rename step2:"+newName);
                        file.renameTo(new File(newName));
                    }
                }
            }
        }
3.5.2处理aar 获得壳dex

在这里插入图片描述

(1)aar文件就是lib

(2)new Module的时候,选择的是android library,这个library,一旦执行Build-make,就会生成一个aar文件。

(3)aar文件与apk文件的差别是怎样的

  • 它除了没有签名文件Meta INFO,其他的都是一模一样的。
  • aar中也没有dex文件,只有jar包,因为jar与dex文件是相通的。如果要把jar包转变为dex文件,又是大量的文件IO操作。
3.5.2.1将aar文件解压缩并且转换为dex文件
        File aarFile = new File("src/source/aar/mylibrary-debug.aar");

        //2.1将aar文件解压缩并且转换为dex文件
        File aarDex  = Dx.jar2Dex(aarFile);
 public static File jar2Dex(File aarFile) throws IOException, InterruptedException {
        File fakeDex = new File(aarFile.getParent() + File.separator + "temp");
        System.out.println("jar2Dex: aarFile.getParent(): " + aarFile.getParent());
        //解压aar到 fakeDex 目录下
        Zip.unZip(aarFile, fakeDex);
        //过滤找到对应的fakeDex 下的classes.jar
        File[] files = fakeDex.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.equals("classes.jar");
            }
        });
        if (files == null || files.length <= 0) {
            throw new RuntimeException("the aar is invalidate");
        }
        File classes_jar = files[0];
        // 将classes.jar 变成classes.dex
        File aarDex = new File(classes_jar.getParentFile(), "classes.dex");

        //我们要将jar 转变成为dex 需要使用android tools 里面的dx.bat
        //使用java 调用windows 下的命令
        Dx.dxCommand(aarDex, classes_jar);
        return aarDex;
    }
3.5.2.2将得到的壳dex文件写到apk/temp/classes.dex文件中
File tempMainDex = new File(newApkFile.getPath() + File.separator + "classes.dex");
        if (!tempMainDex.exists()) {
            tempMainDex.createNewFile();
        }
        FileOutputStream fos = new FileOutputStream(tempMainDex);
        byte[] fbytes = Utils.getBytes(aarDex);
        fos.write(fbytes);
        fos.flush();
        fos.close();
3.5.2.1项目中Application写在library中会引发的问题

(1)意味着在App里面无法写自己的Application了,这样就会导致Application会很臃肿。
(2)在真正的商业开发中,在Library中写了Application之后,也要在App中写一个Application,然后通过Hook Application的方式(插件化的方式),也就是将library库变成一个插件.就像activity hook插件一样,即在清单文件中写一个虚假的Activity,然后在虚假Activity加载的时候,将真的Activity替换上去。
(3)这个技术在反射中使用

3.5.3混合打包
		/**
         * 3.打包签名
         */
        File unsignedApk = new File("src/result/apk-unsigned.apk");
        unsignedApk.getParentFile().mkdirs();
        //3.1对apk/temp中的文件文件进行打包压缩
        Zip.zip(newApkFile, unsignedApk);
        //3.2不用插件就不能自动使用原apk的签名...
        File signedApk = new File("src/result/apk-signed.apk");
        Signature.signature(unsignedApk, signedApk);
3.5.4解密的过程

(1)即脱壳
(2)对源dex解密,加载这些dex文件
(3)脱壳的过程是在运行apk的过程完成,不是在安装apk的过程完成。
(4)安装apk的过程app不会运行,apk都没有启动,是无法运行自己写的代码的。
(5)APP运行最早的时机去解密是最好的,而最早是什么时候呢?
是Application的attachBaseContext()方法

    /**
     * 解密APK中dex文件的最早时机
     * @param base
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        AES.init(getPassword());

        //1.获取APK包
        File apkFile = new File(getApplicationInfo().sourceDir);
        //data/data/包名/files/fake_apk/
        //2.解压缩apk包
        File unZipFile = getDir("fake_apk", MODE_PRIVATE);
        File app = new File(unZipFile, "app");
        //3.2一旦APK已经解密了,就不需要再解密
        if (!app.exists()) {
            Zip.unZip(apkFile, app);
            File[] files = app.listFiles();
            for (File file : files) {
                String name = file.getName();
                if (name.equals("classes.dex")) {

                } else if (name.endsWith(".dex")) {
                    /**
                     * 3.对所有的dex文件进行解密,解密完之后,再将dex文件写回去,即将dex文件
                     * 3.1即将data/data/包名/files/fake_apk/中的大量的解密之后的dex文件同样
                     * 需要去加载进来。
                     * 3.2是否每次启动的时候都会解密呢?
                     *不需要,加个标识。
                     * 3.3真正的保护是要保护好密钥。
                     */
                    try {
                        byte[] bytes = getBytes(file);
                        FileOutputStream fos = new FileOutputStream(file);
                        byte[] decrypt = AES.decrypt(bytes);
                        //                        fos.write(bytes);
                        fos.write(decrypt);
                        fos.flush();
                        fos.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        List list = new ArrayList<>();
        Log.d("FAKE", Arrays.toString(app.listFiles()));
        for (File file : app.listFiles()) {
            if (file.getName().endsWith(".dex")) {
                list.add(file);
            }
        }

        Log.d("FAKE", list.toString());
        try {
            V19.install(getClassLoader(), list, unZipFile);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
3.5.5加载dex文件

(1)涉及到classloader
(2)双亲委拖机制

在这里插入图片描述

(3)涉及到如何从dex中找class

在这里插入图片描述

(4)tinker热修复代码

  • 官方代码

https://github.com/Tencent/tinker

  • tinker实现hook dex文件加载的流程

  • hook技术就是利用反射技术去反射Android源码的过程,加勾子。

因为反射是可以修改源码的

  • Android源码流程是正常运行的,改变不了源码的流程,因此去反射源码的某个点之后,让它去执行我Hook的点,然后回过头来再执行Android源码的过程。

-正因为有了hook技术,才有了对activity进行hook,插件化

private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                File optimizedDirectory) throws IllegalArgumentException,
                IllegalAccessException, NoSuchFieldException, InvocationTargetException,
                NoSuchMethodException {

            Field pathListField = findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList suppressedExceptions = new ArrayList();
            Log.d(TAG, "Build.VERSION.SDK_INT " + Build.VERSION.SDK_INT);
            if (Build.VERSION.SDK_INT >= 23) {
                expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList, new
                                ArrayList(additionalClassPathEntries), optimizedDirectory,
                        suppressedExceptions));
            } else {
                expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new
                                ArrayList(additionalClassPathEntries), optimizedDirectory,
                        suppressedExceptions));
            }

            if (suppressedExceptions.size() > 0) {
                Iterator suppressedExceptionsField = suppressedExceptions.iterator();

                while (suppressedExceptionsField.hasNext()) {
                    IOException dexElementsSuppressedExceptions = (IOException)
                            suppressedExceptionsField.next();
                    Log.w("MultiDex", "Exception in makeDexElement",
                            dexElementsSuppressedExceptions);
                }

                Field suppressedExceptionsField1 = findField(loader,
                        "dexElementsSuppressedExceptions");
                IOException[] dexElementsSuppressedExceptions1 = (IOException[]) ((IOException[])
                        suppressedExceptionsField1.get(loader));
                if (dexElementsSuppressedExceptions1 == null) {
                    dexElementsSuppressedExceptions1 = (IOException[]) suppressedExceptions
                            .toArray(new IOException[suppressedExceptions.size()]);
                } else {
                    IOException[] combined = new IOException[suppressedExceptions.size() +
                            dexElementsSuppressedExceptions1.length];
                    suppressedExceptions.toArray(combined);
                    System.arraycopy(dexElementsSuppressedExceptions1, 0, combined,
                            suppressedExceptions.size(), dexElementsSuppressedExceptions1.length);
                    dexElementsSuppressedExceptions1 = combined;
                }

                suppressedExceptionsField1.set(loader, dexElementsSuppressedExceptions1);
            }
        }

        private static Object[] makeDexElements(Object dexPathList,
                ArrayList<File> files, File
                optimizedDirectory,
                ArrayList<IOException> suppressedExceptions) throws
                IllegalAccessException, InvocationTargetException, NoSuchMethodException {

            Method makeDexElements = findMethod(dexPathList, "makeDexElements", new
                    Class[]{ArrayList.class, File.class, ArrayList.class});
            return ((Object[]) makeDexElements.invoke(dexPathList, new Object[]{files,
                    optimizedDirectory, suppressedExceptions}));
        }

4.对称加密与非对称加密

4.1对称加密

(1)加密和解密的秘钥使用的是同一个

(2)例如:DES、3DES、Blowfish、IDEA、RC4、RC5、RC6 和 AES

(3)问题是无论哪一方泄漏了密码,这个密码就无效了。

4.2非对称加密算法

(1)公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

(2)加密的密钥与解密的密钥不同。

(3)公钥是别人拿着的,私钥是自己拿着的。

(4)用公钥对用户名与密码进行加密,加密后的用户名与密码,只有私钥可以打开,而这个私钥只有银行有。如果公钥被别人拦截了,是没有用的。因为只有银行知道私钥,只有银行才知道你的用户名和密码是否是正确的,银行将判断结果反馈给客户,不会将密码反馈回来,告诉你说你可以登录了。

(5)如果公钥泄密,私钥有无数个可能。要解密有无限个可能,暴力破解要花很久的时间,比如一百年。

5.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

5.1微信打赏

在这里插入图片描述

5.2支付宝打赏

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值