引导类加载器。C/C++ 代码实现的加载器,用于加载指定的 JDK 核心类库,比如 java.lang、java.util 等这些系统类。Java 虚拟机的启动就是通过 Bootstrap,该 ClassLoader 在 Java 里无法获取,负责加载 /lib 下的类。
拓展类加载器。Java 中的实现类为 ExtClassLoader,提供了除系统类之外的额外功能,可以在 Java 中获取,负责加载 /lib/ext 下的类。
应用程序类加载器。Java 中的实现类为 AppClassLoader,是与我们接触最多的类加载器,开发人员写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader 的返回值就是它。
我们也可以自定义类加载器,只需要通过继承 java.lang.ClassLoader 类的方式来实现自己的类加载器即可。
◎ ClassLoader 的继承关系
双亲委派模式的工作原理是:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父加载器去执行,如果父加载器还存在其父加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父加载器可以完成类加载任务,就成功返回,倘若父加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子才会自己想办法去完成。
- 避免重复加载,如果已经加载过一次 Class,可以直接读取已经加载的 Class
- 更加安全,无法自定义类来替代系统的类,可以防止核心 API 库被随意篡改
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射来强制创建某个类或接口对应的 java.lang.Class 对象
- 初始化某个类的子类
- 使用 LoadClass() 加载
- 使用 forName() 加载
◎ 类加载流程
查找和导入 Class 文件
其中解析步骤是可选的。
- 验证:检查载入 Class 文件数据的正确性
- 准备:给类的静态变量分配存储空间
- 解析:将符号引用转成直接引用
调用 < clinit > 函数,对静态变量、静态代码块执行初始化工作。
◎ 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();
}
}
|
◎ 运行结果
用于打印当前类的 ClassLoader 及其父加载器,主要使用类的 getParent() 方法实现。
用于获取某个 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 版本,与示例代码功能相同。
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/