Android中的ClassLoader与dex文件加密实现分析

Android中的ClassLoader

这里写图片描述

BaseDexClassLoader

Dex类加载器的基类,包含Dex类加载器之间通用功能的实现。

DexClassLoader

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。

This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:

这个类加载器必须要一个app的私有、可写目录来缓存经过优化的classes(odex文件),使用Context.getDir(String, int)方法可以创建一个这样的目录。 
示例: File dexOutputDir = context.getDir(“dex”, 0);

PathClassLoader

Provides a simple ClassLoader implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).

提供一个简单的ClassLoader实现,可以操作在本地文件系统的文件列表或目录中的classes,但不可以从网络中加载classes。

DexClassLoader与PathClassLoader

DexClassLoader与PathClassLoader的区别就是DexClassLoader可以加载本地或者网络的classes,而PathClassLoader只能加载本地的classes。

DexClassLoader源码分析

以下是DexClassLoader的构造函数,源码是来自Android2.3的系统源码,Dex文件的加载就是在构造函数中实现的。接收3个参数dexPath、dexOutputDir、libPath、parent。

  • dexPath:dex文件路径列表,多个路径使用”:”分隔
  • dexOutputDir:经过优化的dex文件(odex)文件输出目录
  • libPath:动态库路径(将被添加到app动态库搜索路径列表中)
  • parent:这个一个ClassLoader,这个参数的主要作用是保留java中ClassLoader的委托机制(优先父类加载器加载classes,由上而下的加载机制,防止重复加载类字节码)。
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * The path lists are separated using the character specified by
     * the "path.separator" system property, which defaults to ":".
     *
     * @param dexPath
     *  the list of jar/apk files containing classes and resources
     * @param dexOutputDir
     *  directory where optimized DEX files should be written
     * @param libPath
     *  the list of directories containing native libraries; may be null
     * @param parent
     *  the parent class loader
     */
    public DexClassLoader(String dexPath, String dexOutputDir, String libPath,
        ClassLoader parent) {

        super(parent);

        if (dexPath == null || dexOutputDir == null)
            throw new NullPointerException();

        mRawDexPath = dexPath;
        mDexOutputPath = dexOutputDir;
        mRawLibPath = libPath;

        String[] dexPathList = mRawDexPath.split(":");
        int length = dexPathList.length;

        //System.out.println("DexClassLoader: " + dexPathList);
        mFiles = new File[length];
        mZips = new ZipFile[length];
        mDexs = new DexFile[length];

        /* open all Zip and DEX files up front */
        for (int i = 0; i < length; i++) {
            //System.out.println("My path is: " + dexPathList[i]);
            File pathFile = new File(dexPathList[i]);
            mFiles[i] = pathFile;

            if (pathFile.isFile()) {
                try {
                    mZips[i] = new ZipFile(pathFile);
                } catch (IOException ioex) {
                    // expecting IOException and ZipException
                    System.out.println("Failed opening '" + pathFile
                        + "': " + ioex);
                    //ioex.printStackTrace();
                }

                /* we need both DEX and Zip, because dex has no resources */
                try {
                    String outputName =
                        generateOutputName(dexPathList[i], mDexOutputPath);
                    mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);
                } catch (IOException ioex) {
                    // might be a resource-only zip
                    System.out.println("Failed loadDex '" + pathFile
                        + "': " + ioex);
                }
            } else {
                if (VERBOSE_DEBUG)
                    System.out.println("Not found: " + pathFile.getPath());
            }
        }

        /*
         * Prep for native library loading.
         */
        String pathList = System.getProperty("java.library.path", ".");
        String pathSep = System.getProperty("path.separator", ":");
        String fileSep = System.getProperty("file.separator", "/");

        if (mRawLibPath != null) {
            if (pathList.length() > 0) {
                pathList += pathSep + mRawLibPath;
            }
            else {
                pathList = mRawLibPath;
            }
        }

        mLibPaths = pathList.split(pathSep);
        length = mLibPaths.length;

        // Add a '/' to the end so we don't have to do the property lookup
        // and concatenation later.
        for (int i = 0; i < length; i++) {
            if (!mLibPaths[i].endsWith(fileSep))
                mLibPaths[i] += fileSep;
            if (VERBOSE_DEBUG)
                System.out.println("Native lib path " +i+ ":  " + mLibPaths[i]);
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

以上代码大概可以总结为以下几个步骤: 
1. super(parent),调用父类构造函数,关联父类ClassLoader。 
2. 对dexPath进行分割,得到dex文件路径列表dexPathList。 
3. 迭代dexPathList,调用DexFile的静态方法loadDex加载dex文件。 
4. 通过System.getProperty(“Java.library.path”, “.”)获取app动态库搜索路径列表,并把libPath添加其后。

使用DexClassLoader加载dex文件

把jar转换为dex

关于dex的详细解释可以参考这篇文章 
http://blog.csdn.net/androidsecurity/article/details/9428861

这里写图片描述

1.首先我编写了一个Test类,注意我这里让Test继承自Date,并导出为jar2dex.jar

package linchaolong.jar2dex.test;

import java.util.Date;

public class Test extends Date{

    @Override
    public String toString() {
        return "linchaolong";
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

dex2jar里一个脚本d2j-jar2dex可以将jar转换为dex

dex2jar github地址:https://github.com/pxb1988/dex2jar

dex2jar下载地址:http://yun.baidu.com/s/1bnAkIb9

2.把jar拷贝到dexjar解压目录下执行命令:d2j-jar2dex xxx.jar -o xxx.dex(把xxx.jar转换为xxx.dex) 
这里写图片描述

这里写图片描述

实现dex文件的加密解密

dex文件的加密解密算法使用C/C++实现,使用NDK编译成动态库,通过jni调用加密解密算法对dex文件实现加密、解密。

以下是java层中加密、解密算法的接口

package linchaolong.utils;

/**
 * 数据加密解密工具
 * 
 * @author linchaolong
 *
 */
public class DataProtector {

    static{
        // 加载动态库,数据的加密解密算法实现在动态库中
        System.loadLibrary("dataProtector");  
    }

    /**
     * 加密数据
     * 
     * @param buff 数据
     * @param size  数据大小
     * @return 加密后的数据
     */
    public native static byte[] encrypt(byte[] buff, int size);

    /**
     * 解密数据
     * 
     * @param buff 数据
     * @param size  数据大小
     * @return  解密后的数据
     */
    public native static byte[] decrypt(byte[] buff, int size);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

解密dex文件实现(注意:这里的解密实现是比较简单的,实际应用中应该做代码混淆或放到C/C++中实现,而且注意解密文件的保护)

package linchaolong.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;

/**
 * Dex文件保护工具类
 * 
 * @author linchaolong
 *
 */
public class DexProtector {

    private static final int BUFF_SIZE = 1024*1024;
    private static final String TAG = "DexProtector";

    /**
     * 解密Dex文件
     * 
     * @param in 加密文件输入流
     * @param outFile   输出解密文件
     * @return  是否解密成功
     */
    public static boolean decryptDex(InputStream in, File outFile){
        // 加密dex文件所在目录
        File outDir = outFile.getParentFile();

        // 如果目录不存在则创建
        if (!outDir.exists() && outDir.isDirectory()) {
            outDir.mkdirs();
        }
        try {
            if (outFile.exists()) {
                outFile.delete();
            }

            FileOutputStream out = new FileOutputStream(outFile);

            byte[] buff = new byte[BUFF_SIZE];
            int len = 0;

            while((len = in.read(buff)) != -1){
                // 调用native方法解密dex文件数据
                byte[] decryptBuff = DataProtector.decrypt(buff, len);
                out.write(decryptBuff, 0, len);
            }

            // 释放资源
            in.close();
            out.close();

            return true;

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    public static boolean decryptDex(File encryptFile, File outFile){

        if (!encryptFile.exists()) {
            Log.e(TAG, "加密文件 '" + encryptFile.getPath() + " ''不存在");
            return false;
        }

        try {
            return decryptDex(new FileInputStream(encryptFile), outFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

实现dex文件的动态加载

1.新建一个Application,并重写onCreate方法,用于在应用启动的时候解密并加载dex文件。

我这里把加密的test.dex放到了assets目录下,并命名为encrypt.dex 
这里写图片描述

以下是Application的源码:

package linchaolong.dexproctor;

import java.util.Date;
import linchaolong.utils.DataProtector;
import linchaolong.utils.DexProtector;
import android.app.Application;
import android.content.res.AssetManager;
import android.util.Log;
import dalvik.system.DexClassLoader;

public class DexApplicatoin extends Application{

    private static final String TAG = "DexApplicatoin";

    @Override
    public void onCreate() {
        testDexLoader();
        super.onCreate();
    }

    /**
     *  动态加载dex实现
     */
    private void testDexLoader() {
        try {
            // 解密dex文件的File对象
            File decryptFile = new File(getDir("dex",MODE_PRIVATE), "test.dex");
            // 经过优化的dex输出目录
            File odexDir = getDir("odex_dir", MODE_PRIVATE);

            try {
                // 读取assets目录下的encrypt.dex并解密
                InputStream encryptDexIn = getAssets().open("encrypt.dex");
                DexProtector.decryptDex(encryptDexIn, decryptFile);
            } catch (IOException e1) {
                e1.printStackTrace();
                return;
            }

            // 创建类加载器,加载解密后的dex文件
            ClassLoader dexClassLoader = new DexClassLoader(decryptFile.getPath(), odexDir.getPath(), null, getClassLoader());

            // 加载Test类
            String className = "linchaolong.jar2dex.test.Test";
            Class<?> testClass = dexClassLoader.loadClass(className);

            if (testClass == null) {
                Log.e(TAG,"ClassNotFoundException : can not found class " + className);
            }else{
                try {
                    // 创建Test对象,Test继承自Date,这里用Date引用Test对象
                    Date testObj = (Date) testClass.newInstance();
                    if (testObj == null) {
                        Log.e(TAG,"testObj is null ");
                    }else{
                        // 调用Test对象的toStirng方法
                        Log.e("Test", "testObj.toString() = " + testObj.toString()); 
                    }
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

2.在AndroidManifest.xml中配置Application。 
这里写图片描述

运行结果: 
这里写图片描述 
最终打印结果与Test中toString方法实现一致,表示dex解密并加载成功了

项目地址:https://coding.net/u/linchaolong/p/DexProtector/git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值