Android中的ClassLoader

Android中的ClassLoader与dex文件加密实现分析_第1张图片

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. 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

Android中的ClassLoader与dex文件加密实现分析_第2张图片

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";    }}

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)
Android中的ClassLoader与dex文件加密实现分析_第3张图片

Android中的ClassLoader与dex文件加密实现分析_第4张图片

实现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);}

解密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;    }}

实现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();        }    }}

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

运行结果:
Android中的ClassLoader与dex文件加密实现分析_第5张图片
最终打印结果与Test中toString方法实现一致,表示dex解密并加载成功了

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

更多相关文章

  1. Android中将资源文件转为Bitmap对象
  2. Android 文件下载 downloadManager
  3. Android渐变色xml文件
  4. android 选中效果xml文件
  5. android 在配置文件中指定上级activity
  6. android录制更大视频文件的修改
  7. Android中Activity的4种加载模式
  8. Android加密之文件级加密
  9. Android官方ORM数据库Room技术解决方案简介(一)

随机推荐

  1. 一个c程序的执行是从哪里开始到哪里结束
  2. devc++怎么改成中文
  3. 在c语言中引用数组元素时,其数组下标的数
  4. c++中=和==的区别有哪些?
  5. 在c程序中,注释语句只能位于一条语句的后
  6. c++中不能重载的运算符有哪些
  7. c语言语句以什么结束
  8. textbox控件属性有哪些
  9. C语言中sizeof和strlen的区别是什么
  10. c++清屏函数是什么