Android类加载和双亲委派

类加载器ClassLoader

Bootstrap ClassLoader

引导类加载器。C/C++ 代码实现的加载器,用于加载指定的 JDK 核心类库,比如 java.lang、java.util 等这些系统类。Java 虚拟机的启动就是通过 Bootstrap,该 ClassLoader 在 Java 里无法获取,负责加载 /lib 下的类。

Extensions ClassLoader

拓展类加载器。Java 中的实现类为 ExtClassLoader,提供了除系统类之外的额外功能,可以在 Java 中获取,负责加载 /lib/ext 下的类。

Application ClassLoader

应用程序类加载器。Java 中的实现类为 AppClassLoader,是与我们接触最多的类加载器,开发人员写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader 的返回值就是它。

Custom ClassLoader

我们也可以自定义类加载器,只需要通过继承 java.lang.ClassLoader 类的方式来实现自己的类加载器即可。

image-20220503174024606◎ ClassLoader 的继承关系

双亲委派

加载顺序

双亲委派模式的工作原理是:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父加载器去执行,如果父加载器还存在其父加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父加载器可以完成类加载任务,就成功返回,倘若父加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子才会自己想办法去完成。

优点

  • 避免重复加载,如果已经加载过一次 Class,可以直接读取已经加载的 Class
  • 更加安全,无法自定义类来替代系统的类,可以防止核心 API 库被随意篡改

类加载

加载时机

隐式加载

  • 创建类的实例
  • 访问类的静态变量,或者为静态变量赋值
  • 调用类的静态方法
  • 使用反射来强制创建某个类或接口对应的 java.lang.Class 对象
  • 初始化某个类的子类

显式加载

  • 使用 LoadClass() 加载
  • 使用 forName() 加载

加载流程

image-20220503225358951◎ 类加载流程

加载

查找和导入 Class 文件

链接

其中解析步骤是可选的。

  • 验证:检查载入 Class 文件数据的正确性
  • 准备:给类的静态变量分配存储空间
  • 解析:将符号引用转成直接引用

初始化

调用 < clinit > 函数,对静态变量、静态代码块执行初始化工作。

Android的类加载器

image-20220503232139100◎ ClassLoader 继承关系

  • ClassLoader 为抽象类
  • BootClassLoader 是预加载常用类,单例模式。与 Java 中的 BootClassLoader 不同,它并不是由 C/C++ 代码实现,而是通过 Java 实现的。
  • BaseDexClassLoader 是 PathClassLoader、DexClassLoader、InMemoryDexClassLoader 的父类,类加载的主要逻辑都是在 BaseDexClassLoader 中完成的。
  • SecureClassLoader 继承了抽象类 ClassLoader,拓展了 ClassLoader 类,加入了权限方面的功能,加强了安全性,其子类 URLClassLoader 是用 URL 路径从 jar 文件中加载类和资源。

需要重点关注的是 PathClassLoader 和 DexClassLoader。

  • PathClassLoader 是 Android 默认使用的类加载器,一个 apk 中的 Activity 等类便是在其中加载。
  • DexClassLoader 可以加载任意目录下的 dex/jar/apk/zip 文件,比 PathClassLoader 更灵活,是实现插件化、热修复以及 dex 加壳的重点。
  • Android 8.0 新引入了 InMemoryDexClassLoader,从名字便可看出是用于直接从内存中加载 dex。

相关代码

示例

 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
public static void printClassLoader() {
    ClassLoader thisclassloader = MainActivity.class.getClassLoader();
    assert thisclassloader != null;
    ClassLoader parent = thisclassloader.getParent();
    ClassLoader tmpclassloader = thisclassloader;
    while (parent != null) {
        Log.e("blog.svip.dev", " \ncurrentClassLoader: " + tmpclassloader + "\nparentClassLoader: " + parent);
        tmpclassloader = parent;
        parent = parent.getParent();
    }

    getClassLoaderClasses(thisclassloader);
}

public static void getClassLoaderClasses(ClassLoader classLoader) {
    try {
        Class<?> baseDexClassLoaderClass = classLoader.loadClass("dalvik.system.BaseDexClassLoader");
        @SuppressLint("DiscouragedPrivateApi")
        Field pathList = baseDexClassLoaderClass.getDeclaredField("pathList");
        pathList.setAccessible(true);
        Object pathListObj = pathList.get(classLoader);

        Class<?> dexPathListClass = classLoader.loadClass("dalvik.system.DexPathList");
        @SuppressLint("DiscouragedPrivateApi")
        Field dexElements = dexPathListClass.getDeclaredField("dexElements");
        dexElements.setAccessible(true);
        Object[] dexElementsObj = (Object[]) dexElements.get(pathListObj);

        Class<?> elementClass = classLoader.loadClass("dalvik.system.DexPathList$Element");
        @SuppressLint("DiscouragedPrivateApi")
        Field dexFile = elementClass.getDeclaredField("dexFile");
        dexFile.setAccessible(true);

        Class<?> dexFileClass = classLoader.loadClass("dalvik.system.DexFile");
        @SuppressLint("DiscouragedPrivateApi")
        Field mCookie = dexFileClass.getDeclaredField("mCookie");
        mCookie.setAccessible(true);
        @SuppressLint("DiscouragedPrivateApi")
        Method getClassNameList = dexFileClass.getDeclaredMethod("getClassNameList", Object.class);
        getClassNameList.setAccessible(true);

        assert dexElementsObj != null;
        for (Object elementObj : dexElementsObj) {
            Object dexFileObj = dexFile.get(elementObj);
            Object mCookieObj = mCookie.get(dexFileObj);
            String[] classNameList = (String[]) getClassNameList.invoke(dexFileObj, mCookieObj);
            assert classNameList != null;
            for (String className : classNameList) {
                Log.e("blog.svip.dev", "className: " + className);
            }
        }

    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

image-20220504175525646◎ 运行结果

代码说明

printClassLoader()

用于打印当前类的 ClassLoader 及其父加载器,主要使用类的 getParent() 方法实现。

getClassLoaderClasses()

用于获取某个 ClassLoader 加载的所有类名称。思路如下:通过查看 Android 源码,我们可以提取出以下关键代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public class BaseDexClassLoader extends ClassLoader {
	private final DexPathList pathList;	
}

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public final class DexPathList {
	private Element[] dexElements;	
	static class Element {
		private final DexFile dexFile;
	}
}

// libcore/dalvik/src/main/java/dalvik/system/DexFile.java
public final class DexFile {
	private Object mCookie;
	private static native String[] getClassNameList(Object cookie);
}

由以上内容可以看出,我们可以调用 DexFile 类中的 getClassNameList() 方法来获得类列表,由于需要用到的变量和方法都是私有的,所以我们通过反射实现该方法的功能。

Frida版本

以下代码为 Frida 版本,与示例代码功能相同。

 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
function enumerateClassLoaders() {
    Java.perform(function () {
        Java.enumerateClassLoadersSync().forEach(function (loader) {
            console.log("---------------");
            console.log(loader);
            getClassLoaderClasses(loader);
        });
    });
}

function getClassLoaderClasses(ClassLoaderObj: any) {
    if (Java.available) {
        Java.perform(function () {
            try {
                var BaseDexClassLoaderClass = Java.use("dalvik.system.BaseDexClassLoader");
                var DexPathListClass = Java.use("dalvik.system.DexPathList");
                var DexFileClass = Java.use("dalvik.system.DexFile");
                var ElementClass = Java.use("dalvik.system.DexPathList$Element");
                var basedexclassloaderobj = Java.cast(ClassLoaderObj, BaseDexClassLoaderClass);
                var tmpobj = basedexclassloaderobj.pathList.value;
                var pathlistobj = Java.cast(tmpobj, DexPathListClass);
                console.log("pathlistobj->" + pathlistobj);
                var dexelementsobj = pathlistobj.dexElements.value;
                console.log("dexElementsobj->" + dexelementsobj);
                for (var i in dexelementsobj) {
                    var obj = dexelementsobj[i];
                    var elementobj = Java.cast(obj, ElementClass);
                    console.log("elementobj->" + elementobj);
                    tmpobj = elementobj.dexFile.value;
                    var dexfileobj = Java.cast(tmpobj, DexFileClass);
                    var enumeratorClassNames = dexfileobj.entries();
                    while (enumeratorClassNames.hasMoreElements()) {
                        var classname = enumeratorClassNames.nextElement().toString();
                        console.log("classname->" + classname);
                    }
                }
            } catch (e) {
                console.log(e);
            }
        });
    }
}

参考链接

https://cs.android.com/

updatedupdated2022-05-172022-05-17