Android一代加固学习史是一系列文章,将详细介绍Android第一代壳的相关原理,从源码分析,到代码实现。体现了理论结合实战的方法,帮助初学者理解学习。
在本系列中,使用了3种方法加载dex文件(落地加载),从难到易,逐步理解。
这是第一篇文章,稍微会有一些枯燥,主要介绍一些在第一代加固会用到的相关类。初学者可以对其有个大概的印象,建立相关类的体系结构,后面都会用到的哦
1、总体结构
这里借用寒冰老师的图,主要是不想再画了(

该图介绍了Android ClassLoader相关的继承关系
这里我们只需要记住下面5点
- ClassLoader是抽象基础类,Android各种ClassLoader都继承他的相关属性和方法
- BaseDexClassLoader 实现了大部分CLassLoader类加载的逻辑,是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父类
- PathClassLoader 是Android默认使用的类加载器,APP中的四大组件等类由其加载,在后面我们也会用代码来验证。
- DexClassLoader 可以加载任意目录下的dex、jar、apk、zip等文件,是实现插件化、热修复和dex加壳的重点关注对象
- InMemoryDexClassLoader(Android8+)可以从内存中加载dex
下面开始分别在Android源代码中,寻找这些类(建议初学者能够自己动手寻找,加深理解)
首先,我们寻找三兄弟类(PathClassLoader、DexClassLoader、InMemoryDexClassLoader)
DexClassLoader
1 2 3 4 5 6 7 8 9 | //第一个参数dex文件路径 ,必填的参数//第二个参数编译优化存放路径,选个app可以访问的路径即可//第三个参数so库的搜索路径 ,一般为null//第四个参数为父classLoader,一般需要填写53 public DexClassLoader(String dexPath, String optimizedDirectory,54 String librarySearchPath, ClassLoader parent) {//调用父类:BaseDexClassLoader的构造函数55 super(dexPath, null, librarySearchPath, parent);56 } |
- DexClassLoader 可以加载任意目录下的dex、jar、apk、zip等文件,是实现插件化、热修复和dex加壳的重点关注对象
哦豁,我们发现,大哥DexClassLoader啥都没实现,直接用父类BaseDexClassLoader的构造函数,并传递相关的参数(源码有解释),其他兄弟是不是也是这样的呢?
PathClassLoader
我们来看看二哥 PathClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

PathClassLoader 是Android默认使用的类加载器,APP中的四大组件等类由其加载,在后面我们也会用代码来验证。
跟大哥一样的性格,把重担都交父类BaseDexClassLoader,传递参数并调用父类构造函数
InMemoryDexClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java

InMemoryDexClassLoader(Android8+)可以从内存中加载dex
不再废话,重担都交父类BaseDexClassLoader,传递参数并调用父类构造函数
下面介绍重量级选手,处理大部分逻辑的类
BaseDexClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
我们先看一下他的构造函数,他的三个儿子都在调用
构造函数

可以看到BaseDexClassLoader有多个构造函数,我们向下找到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 150 public BaseDexClassLoader(String dexPath,151 String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,152 ClassLoader[] sharedLibraryLoadersAfter,153 boolean isTrusted) {154 super(parent);155 // Setup shared libraries before creating the path list. ART relies on the class loader156 // hierarchy being finalized before loading dex files.157 this.sharedLibraryLoaders = sharedLibraryLoaders == null158 ? null159 : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);160 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);161 162 this.sharedLibraryLoadersAfter = sharedLibraryLoadersAfter == null163 ? null164 : Arrays.copyOf(sharedLibraryLoadersAfter, sharedLibraryLoadersAfter.length);165 // Run background verification after having set 'pathList'.166 this.pathList.maybeRunBackgroundVerification(this);167 168 reportClassLoaderChain();169 } |
观察一下参数,发现跟子类传递的十分相同。
1 2 3 4 | public BaseDexClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, ClassLoader[] sharedLibraryLoadersAfter, boolean isTrusted) |
这么多代码,其实我们只用关注一行代码,其他的都可以不管。
1 | this.pathList = new DexPathList(this, dexPath, librarySearchPath, null,isTrusted); |
可以发现将相关的dexPath传给了DexPathList类。
那么,我们接下来需要跟踪 DexPathList 类,看看他在搞什么。
这里区分一下:DexPathList 和 DexClassLoader 是不一样的,不要看混了哈
DexPathList
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
DexPathList是DexClassLoader和BaseDexClassLoader等类加载器用于处理DEX文件路径的一个内部类。当使用DexClassLoader或BaseDexClassLoader加载DEX文件时,DexPathList起到了关键的作用。
DexPathList在BaseDexClassLoader的构造函数中被创建。构造DexPathList时,需要提供DEX文件的路径、优化目录、库路径以及父类加载器等参数。
1 2 3 4 5 | public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);} |
内容代码那么多,我们只需要关注他的一个成员:
DexPathList有一个私有的final成员变量??dexElements?? ,是一个Element数组,包含了所有DEX文件的Element对象,每个Element对象对应一个DEX文件。
到这里,有没有读者恍然大悟,为什么很多文章都指向了 Element 对象。这里就是原因,一个Element对象对应一个Dex文件。
Dex文件是我们加固的对象,分析逻辑的对象,能拿到Dex文件,还害怕不能分析吗?
所以,Element对象对我们来说很重要

1 2 3 4 5 6 7 8 9 | private final Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ... this.definingContext = definingContext; this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions); ...} |
那么,与Element对象大大相关的方法就是makeDexElements()
在DexPathList的构造函数中,会调用makeDexElements()方法来加载DEX文件。这个方法会遍历提供的DEX文件路径列表,并为每个DEX文件创建一个Element对象,然后将这些Element对象添加到dexElements数组中。
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 | private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) { // 1.创建Element集合 ArrayList<Element> elements = new ArrayList<Element>(); // 2.遍历所有dex文件(也可能是jar、apk或zip文件) for (File file : files) { ZipFile zip = null; DexFile dex = null; String name = file.getName(); ... // 如果是dex文件 if (name.endsWith(DEX_SUFFIX)) { dex = loadDexFile(file, optimizedDirectory); // 如果是apk、jar、zip文件(这部分在不同的Android版本中,处理方式有细微差别) } else { zip = file; dex = loadDexFile(file, optimizedDirectory); } ... // 3.将dex文件或压缩文件包装成Element对象,并添加到Element集合中 if ((zip != null) || (dex != null)) { elements.add(new Element(file, false, zip, dex)); } } // 4.将Element集合转成Element数组返回 return elements.toArray(new Element[elements.size()]);} |
当类加载器需要加载一个类时,会通过DexPathList的loadClass()方法来实现。这个方法会遍历dexElements数组中的每个Element对象,并尝试从对应的DEX文件中加载类。一旦找到需要加载的类,就会返回该类的Class对象。
同时,这里需要大家记住: Element 类是DexPathList的内部类,在后面的学习中,我们将使用反射来获取Element对象,获取的方法会有一些不同。
Element类
http://androidxref.com/9.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java#606
在DexPathList的构造函数中,会调用makeDexElements()方法来加载DEX文件。这个方法会遍历提供的DEX文件路径列表,并为每个DEX文件创建一个Element对象,然后将这些Element对象添加到dexElements数组中。

Element类有一个很重要的成员
1 | 613 private final DexFile dexFile; |
DexFile类,相信读者们知道,DexFile代表了一个dex文件,在脱壳加固我们都会用到dex文件。
DexFile类
Cross Reference: /libcore/dalvik/src/main/java/dalvik/system/DexFile.java (androidxref.com)
很重要的方法:getClassNameList() 通过这个方法获得 ClassLoader加载的所有类名列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 309 private static class DFEnum implements Enumeration<String> {310 private int mIndex;311 private String[] mNameList;312313 DFEnum(DexFile df) {314 mIndex = 0;315 mNameList = getClassNameList(df.mCookie);316 }317318 public boolean hasMoreElements() {319 return (mIndex < mNameList.length);320 }321322 public String nextElement() {323 return mNameList[mIndex++];324 }325 } |
在介绍完这些常见的类(加固会用到)
我们再来学习他们的祖爷爷:ClassLoader 抽象基类
ClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
CLassLoader类包含了许多公共的方法和属性,这些方法和属性,其子类都可以使用,比如:获取父加载器的parent方法

1 2 3 | 1046 public final ClassLoader getParent() {1047 return parent;1048 } |
还有一些属性,当然在后面文章中,我们遇到的时候再去学习,今天这篇文章先构建一个大概的逻辑。
验证四大组件由PathClassLoader加载

1 2 3 4 5 6 7 8 9 10 11 12 | public static void testClassLoader(){ ClassLoader mainclassloader=MainActivity.class.getClassLoader(); ClassLoader parent=mainclassloader.getParent(); ClassLoader temp=mainclassloader; while (parent!=null){ Log.e("MYclassloader","thisclassloader : "+ temp+" thisclassloader_parent : "+parent); temp=parent; parent=parent.getParent(); }} |
输出日志
1 2 3 | MYclassloader com.example.myapplication E thisclassloader : dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.myapplication-Dav-JtjREj9RrEHH64kzBA==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myapplication-Dav-JtjREj9RrEHH64kzBA==/lib/x86_64, /data/app/com.example.myapplication-Dav-JtjREj9RrEHH64kzBA==/base.apk!/lib/x86_64, /system/lib64, /system/vendor/lib64]]] thisclassloader_parent : java.lang.BootClassLoader@d613d4a |
验证了四大组件由 PathClassLoader 类加载器加载
如果一个APP没有加壳,就会由PathClassLoader类加载
为什么APP没有加壳,就由PathClassLoader加载呢?
不用着急,这个问题在后面讲解都会理解哈哈
思考
这里提出一个小问题,我们如何获得 当前ClassLoader的类名列表?
提供一个思路,串起这篇文章讲的内容:
BaseDexClassLoader的3个子类任选一个 -> BaseDexClassLoader -> DexPathList -> Element -> DexFile -> getClassNameList()
在下篇,我们将使用代码解决这个问题
文章书写不易,换您手中一个赞不过分吧^_^
