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

    xiaoxiao2021-09-22  56

    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]); } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697

    以上代码大概可以总结为以下几个步骤:  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"; } } 1234567891011 1234567891011

    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); } 123456789101112131415161718192021222324252627282930313233 123456789101112131415161718192021222324252627282930313233

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

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

    2.在AndroidManifest.xml中配置Application。 

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

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

    转载请注明原文地址: https://ju.6miu.com/read-677806.html

    最新回复(0)