android-类加载器 grand 2025-04-10 2025-04-28 参考资料 [原创]Android加壳脱壳学习(1)——动态加载和类加载机制详解-Android安全-看雪-安全社区|安全招聘|kanxue.com
类加载器 Android中的类加载器机制与JVM一样遵循双亲委派模式
双亲委派模式 1.加载.class
文件的时候,以递归的的形式逐级向上委托给父加载器ParentClassLoader
去加载,如果加载过了,就不用在加载一遍
2.如果父加载器也没加载过,则继续委托给父加载器去加载,一直到这条链路的顶级,顶级classLoader
判断如果没加载过,则尝试加载,加载失败,则逐级向下交还调用者来加载.
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; }
加载流程
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
复杂的加载系统类和英勇程序的类,通常用来加载已安装apk
的dex
文件,实际上外部存储的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.BootClassLoader
Android平台上所有ClassLoader
的最终parent
,Android系统启动时会使用BootClassLoader
来预加载常用类
andriod8以后引入了InMemoryDexClassLoader,从内存加载
几个类之间的关系
查看已经当前的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
再次新建 这里我们由于把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文件中的方法也成功调用