android-类加载器

参考资料

[原创]Android加壳脱壳学习(1)——动态加载和类加载机制详解-Android安全-看雪-安全社区|安全招聘|kanxue.com

类加载器

Android中的类加载器机制与JVM一样遵循双亲委派模式

双亲委派模式

1.加载.class文件的时候,以递归的的形式逐级向上委托给父加载器ParentClassLoader去加载,如果加载过了,就不用在加载一遍

2.如果父加载器也没加载过,则继续委托给父加载器去加载,一直到这条链路的顶级,顶级classLoader判断如果没加载过,则尝试加载,加载失败,则逐级向下交还调用者来加载.

905443_WAMK8ZY23F6MM42

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
//1.先检查是否已经加载过--findLoaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
//2.如果自己没加载过,存在父类,则委托父类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}

if (c == null) {
//3.如果父类也没加载过,则尝试本级classLoader加载
c = findClass(name);
}
}
return c;
}

加载流程

905443_9TRKTFAGXAU92CP

1
2
3
4
5
6
7
我们要加载一个class文件,我们定义了一个CustomerClassLoader类加载器:
(1)首先会判断自己的CustomerClassLoader否加载过,如果加载过直接返回,
(2)如果没有加载过则会调用父类PathClassLoader去加载,该父类同样会判断自己是否加载过,如果没有加载过则委托给父类BootClassLoader去加载,
(3)这个BootClassLoader是顶级classLoader,同样会去判断自己有没有加载过,如果也没有加载过则会调用自己的findClass(name)去加载,
(4)如果顶级BootClassLoader加载失败了,则会把加载这个动作向下交还给PathClassLoader,
(5)这个PathClassLoader也会尝试去调用findClass(name);去加载,如果加载失败了,则会继续向下交还给CustomClassLoader来完成加载,这整个过程感觉是一个递归的过程,逐渐往上然后有逐渐往下,直到加载成功
其实这个String.class在系统启动的时候已经被加载了,我们自己定义一个CustomerClassLoader去加载,其实也是父类加载的

Android中的主要类加载器

1.PathClassLoader复杂的加载系统类和英勇程序的类,通常用来加载已安装apkdex文件,实际上外部存储的dex文件也能加载

2.DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk,dex,jar,zip)

3.BaseDexClassLoader全名是dalvik/system.PathClassLoader,可以加载已经安装的Apk,也就是/data/app/package 下的apk文件,也可以加载/vendor/lib, /system/lib下的nativeLibrarypathList来完成

4.BootClassLoaderAndroid平台上所有ClassLoader的最终parent,Android系统启动时会使用BootClassLoader来预加载常用类

andriod8以后引入了InMemoryDexClassLoader,从内存加载

几个类之间的关系

2083810-64e93a71d99c1459

查看已经当前的class类和父类

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
package com.eeee.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClassLoader thisclassloader = MainActivity.class.getClassLoader();
Log.i("test","testim:"+thisclassloader);
ClassLoader secondclassloader =null;
ClassLoader parentclassloader = thisclassloader.getParent();
while (parentclassloader!=null){
Log.i("test","testim:"+thisclassloader+"parent"+parentclassloader);
thisclassloader=parentclassloader;
parentclassloader=thisclassloader.getParent();
}
Log.i("root","final"+thisclassloader);

}
}
1
2
3
2025-04-08 19:39:58.591 9327-9327/com.eeee.myapplication I/test: testim:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~Nm6s9RY_9WyoaS0gsxNIog==/com.eeee.myapplication-gDnm4BKglPQGA3i4PsNz4g==/base.apk"],nativeLibraryDirectories=[/data/app/~~Nm6s9RY_9WyoaS0gsxNIog==/com.eeee.myapplication-gDnm4BKglPQGA3i4PsNz4g==/lib/x86_64, /system/lib64, /system/system_ext/lib64, /system/product/lib64, /system/vendor/lib64]]]
2025-04-08 19:39:58.591 9327-9327/com.eeee.myapplication I/test: testim:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~Nm6s9RY_9WyoaS0gsxNIog==/com.eeee.myapplication-gDnm4BKglPQGA3i4PsNz4g==/base.apk"],nativeLibraryDirectories=[/data/app/~~Nm6s9RY_9WyoaS0gsxNIog==/com.eeee.myapplication-gDnm4BKglPQGA3i4PsNz4g==/lib/x86_64, /system/lib64, /system/system_ext/lib64, /system/product/lib64, /system/vendor/lib64]]]parentjava.lang.BootClassLoader@7d15f50
2025-04-08 19:39:58.591 9327-9327/com.eeee.myapplication I/root: finaljava.lang.BootClassLoader@7d15f50

类加载时机

隐式加载

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

显示加载

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

显示父类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.eeee.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClassLoader thisclassloader = MainActivity.class.getClassLoader();
Log.i("test","testim:"+thisclassloader);
ClassLoader secondclassloader =null;
ClassLoader parentclassloader = thisclassloader.getParent();
while (parentclassloader!=null){
Log.i("test","testim:"+thisclassloader+"parent"+parentclassloader);
parentclassloader=parentclassloader.getParent();
}
Log.i("root","final"+thisclassloader);

}
}

尝试使用bootclassloader加载mainactivity

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
package com.eeee.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClassLoader classLoader=MainActivity.class.getClassLoader();
Log.i("test","this"+classLoader.toString());
ClassLoader parent=classLoader.getParent();
Log.i("test","this"+parent.toString());

try {
Class main=classLoader.loadClass("com.eeee.myapplication.MainActivity");
Log.i("test","pathclassloader main"+main.toString());
}catch (ClassNotFoundException e){
Log.i("fail","pathclassloader main fail");
e.printStackTrace();

}
try {
Class main=parent.loadClass("com.eeee.myapplication.MainActivity");
Log.i("test","bootclassloader main"+main.toString());
}catch (ClassNotFoundException e){
Log.i("fail","bootclassloader main fail");
e.printStackTrace();

}


}
}

接下来使用DexClassLoader类实现一个最简单的动态加载插件dex,并
验证此时的ClassLoader|间的继承关系;
DexClassLoader方法参数:
dexPath:目标所在的apk或者jar文件的路径,装载器将从路径中寻找指
定的目标类。
dexOutputDir:由于dex文件在APK或者jar文件中,所以在装载前面前
先要从里面解压出dex文件,这个路径就是dex文件存放的路径,在
android系统中,一个应用程序对应一个inux用户id,应用程序只对自
己的数据目录有写的权限,所以我们需要存放在一个app可写的径中
libPath:目标类中使用的c/c++库。
最后一个参数是该装载器的父装载器,一般为当前执行类的装载器

编写插件dex

新建工程

1
2
3
4
5
6
7
8
9
10
11
12
package com.eeee.plugindex;

import android.util.Log;

public class dex {

public static void test(){
Log.i("dextest","suessfull");

}
}

直接编译为apk 提取dex

image-20250409234512720

再次新建 这里我们由于把dex塞sdcard文件夹里面了 要增加一下sdcard的读写权限

修改androidmainifest.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eeee.loaddex">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Loaddex">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
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
57
58
59
60
61
62
63
64
package com.eeee.loaddex;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.eeee.loaddex.databinding.ActivityMainBinding;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

// Used to load the 'loaddex' library on application startup.
static {
System.loadLibrary("loaddex");
}

private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());

String path ="/sdcard/pdex.dex";

DexClassLoader dexClassLoader =new DexClassLoader(path,this.getApplication().getCacheDir().getAbsolutePath(),null,MainActivity.class.getClassLoader());
try {

Class pdex= dexClassLoader.loadClass("com.eeee.plugindex.dex");
Log.i("mainactivity", dexClassLoader.toString()+"---"+pdex);
Method test=pdex.getDeclaredMethod("test");
test.invoke(null);

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


}

/**
* A native method that is implemented by the 'loaddex' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
1
2
2025-04-10 00:41:33.620 13630-13630/com.eeee.loaddex I/mainactivity: dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/pdex.dex"],nativeLibraryDirectories=[/system/lib64, /system/system_ext/lib64, /system/product/lib64, /system/vendor/lib64]]]---class com.eeee.plugindex.dex

可以看到我们成功加载了sdcard中的dex文件

1
2025-04-10 00:41:33.621 13630-13630/com.eeee.loaddex I/dextest: suessfull

同时dex文件中的方法也成功调用