欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 运维知识 > Android >内容正文

Android

【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( 在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader )

发布时间:2025/6/17 Android 58 豆豆
生活随笔 收集整理的这篇文章主要介绍了 【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( 在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader ) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

文章目录

  • 前言
  • 一、在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader
    • 1、创建 DexClassLoader
    • 2、使用 DexClassLoader 实例对象作为 PathClassLoader 的父节点
  • 二、完整代码示例
  • 三、执行结果
  • 四、博客资源





前言



在 上一篇博客 【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( 替换 LoadedApk 中的类加载器 | 加载 DEX 文件中的 Activity 类并启动成功 ) 中 , 通过 替换 LoadedApk 中的类加载器可以成功加载 DEX 字节码文件中的 Activity 类 , 并成功启动 Activity ;

本篇博客中尝试使用 【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( 使用 DexClassLoader 获取组件类失败 | 失败原因分析 | 自定义类加载器没有加载组件类的权限 ) 博客中 提出的 加载组件类的 第二种方案 ;





一、在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader




1、创建 DexClassLoader


原来的逻辑是 PathClassLoader 用于加载组件类 , 其父节点是 BootClassLoader , 现在将 PathClassLoader 父节点设置为 DexClassLoader , DexClassLoader 父节点设置为 BootClassLoader , 相当于在 PathClassLoader 和 BootClassLoader 之间插入了一个 DexClassLoader ;


代码示例 :

// I. 创建 DexClassLoader , 并设置其 父类节点为 BootClassLoader// 获取 PathClassLoaderClassLoader pathClassloader = MainActivity.class.getClassLoader();// 获取 BootClassLoaderClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();/*注意原来的逻辑是 PathClassLoader 用于加载组件类 , 其父节点是 BootClassLoader现在将 PathClassLoader 父节点设置为 DexClassLoader ,DexClassLoader 父节点设置为 BootClassLoader相当于在 PathClassLoader 和 BootClassLoader 之间插入了一个 DexClassLoader*/// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字节码文件路径optFile.getAbsolutePath(), // 优化目录libFile.getAbsolutePath(), // 依赖库目录bootClassloader // 父节点类加载器);

2、使用 DexClassLoader 实例对象作为 PathClassLoader 的父节点


首先 , 获取 ClassLoader 的 private final ClassLoader parent; 成员 ;

然后 , 设置 PathClassLoader 的 parent 字段为 自定义的 DexClassLoader 实例对象 ;


代码示例 :

// II. 使用 DexClassLoader 实例对象作为 PathClassLoader 的父节点// 获取 ClassLoader 的 private final ClassLoader parent; 成员Field parentField = null;try {parentField = ClassLoader.class.getDeclaredField("parent");// 设置可访问性parentField.setAccessible(true);} catch (NoSuchFieldException e) {e.printStackTrace();}// 设置 PathClassLoader 的 parent 字段为 自定义的 DexClassLoader 实例对象try {parentField.set(pathClassloader, dexClassLoader);} catch (IllegalAccessException e) {e.printStackTrace();}



二、完整代码示例



下面代码中

// 在类加载器的双亲委派机制中的 PathClassLoader 和 BootClassLoader 之间// 插入 DexClassLoaderif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {startDexActivityWithInsertedClassLoader(this, mDexPath);}

就是先替换 LoadedApk 中的 类加载器 ClassLoader , 然后使用替换的类加载器加载 DEX 字节码文件中的 Activity 组件 ;


完整代码示例 :

package com.example.classloader_demo;import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity;import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.util.ArrayMap; import android.util.Log;import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;import dalvik.system.DexClassLoader;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";/*** Dex 文件路径*/private String mDexPath;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 打印类加载器及父节点classloaderLog();// 拷贝 dex 文件mDexPath = copyFile2();// 测试 DEX 文件中的方法testDex(this, mDexPath);// 拷贝 dex2 文件//mDexPath = copyFile2();// 启动 DEX 中的 Activity 组件 , 此处启动会失败//startDexActivityWithoutClassLoader(this, mDexPath);// 替换 LoadedApk 中的 类加载器 ClassLoader// 然后使用替换的类加载器加载 DEX 字节码文件中的 Activity 组件if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//startDexActivityWithReplacedClassLoader(this, mDexPath);}// 在类加载器的双亲委派机制中的 PathClassLoader 和 BootClassLoader 之间// 插入 DexClassLoaderif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {startDexActivityWithInsertedClassLoader(this, mDexPath);}}/*** 打印当前的类加载器及父节点*/private void classloaderLog() {// 获取当前 Activity 的 类加载器 ClassLoaderClassLoader classLoader = MainActivity.class.getClassLoader();// 打印当前 Activity 的 ClassLoader 类加载器Log.i(TAG, "MainActivity ClassLoader : " + classLoader);// 获取 类加载器 父类ClassLoader parentClassLoader = classLoader.getParent();// 打印当前 Activity 的 ClassLoader 类加载器 的父类Log.i(TAG, "MainActivity Parent ClassLoader : " + parentClassLoader);}/*** 将 app\src\main\assets\classes.dex 文件 ,* 拷贝到 /data/user/0/com.example.classloader_demo/files/classes.dex 位置*/private String copyFile() {// DEX 文件File dexFile = new File(getFilesDir(), "classes.dex");// DEX 文件路径String dexPath = dexFile.getAbsolutePath();Log.i(TAG, "开始拷贝文件 dexPath : " + dexPath);// 如果之前已经加载过 , 则退出if (dexFile.exists()) {Log.i(TAG, "文件已经拷贝 , 退出");return dexPath;}try {InputStream inputStream = getAssets().open("classes.dex");FileOutputStream fileOutputStream = new FileOutputStream(dexPath);byte[] buffer = new byte[1024 * 4];int readLen = 0;while ((readLen = inputStream.read(buffer)) != -1) {fileOutputStream.write(buffer, 0, readLen);}inputStream.close();fileOutputStream.close();} catch (IOException e) {e.printStackTrace();} finally {Log.i("HSL", "classes.dex 文件拷贝完毕");}return dexPath;}/*** 将 app\src\main\assets\classes2.dex 文件 ,* 拷贝到 /data/user/0/com.example.classloader_demo/files/classes2.dex 位置*/private String copyFile2() {// DEX 文件File dexFile = new File(getFilesDir(), "classes2.dex");// DEX 文件路径String dexPath = dexFile.getAbsolutePath();Log.i(TAG, "开始拷贝文件 dexPath : " + dexPath);// 如果之前已经加载过 , 则退出if (dexFile.exists()) {Log.i(TAG, "文件已经拷贝 , 退出");return dexPath;}try {InputStream inputStream = getAssets().open("classes2.dex");FileOutputStream fileOutputStream = new FileOutputStream(dexPath);byte[] buffer = new byte[1024 * 4];int readLen = 0;while ((readLen = inputStream.read(buffer)) != -1) {fileOutputStream.write(buffer, 0, readLen);}inputStream.close();fileOutputStream.close();} catch (IOException e) {e.printStackTrace();} finally {Log.i("HSL", "classes2.dex 文件拷贝完毕");}return dexPath;}/*** 测试调用 Dex 字节码文件中的方法** @param context* @param dexFilePath*/private void testDex(Context context, String dexFilePath) {// 优化目录File optFile = new File(getFilesDir(), "opt_dex");// 依赖库目录 , 用于存放 so 文件File libFile = new File(getFilesDir(), "lib_path");// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字节码文件路径optFile.getAbsolutePath(), // 优化目录libFile.getAbsolutePath(), // 依赖库目录context.getClassLoader() // 父节点类加载器);// 加载 com.example.dex_demo.DexTest 类// 该类中有可执行方法 test()Class<?> clazz = null;try {clazz = dexClassLoader.loadClass("com.example.dex_demo.DexTest");} catch (ClassNotFoundException e) {e.printStackTrace();}// 获取 com.example.dex_demo.DexTest 类 中的 test() 方法if (clazz != null) {try {// 获取 test 方法Method method = clazz.getDeclaredMethod("test");// 获取 Object 对象Object object = clazz.newInstance();// 调用 test() 方法method.invoke(object);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}/*** 不修改类加载器的前提下 , 运行 Dex 字节码文件中的组件** @param context* @param dexFilePath*/private void startDexActivityWithoutClassLoader(Context context, String dexFilePath) {// 优化目录File optFile = new File(getFilesDir(), "opt_dex");// 依赖库目录 , 用于存放 so 文件File libFile = new File(getFilesDir(), "lib_path");// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字节码文件路径optFile.getAbsolutePath(), // 优化目录libFile.getAbsolutePath(), // 依赖库目录context.getClassLoader() // 父节点类加载器);// 加载 com.example.dex_demo.DexTest 类// 该类中有可执行方法 test()Class<?> clazz = null;try {clazz = dexClassLoader.loadClass("com.example.dex_demo.MainActivity2");} catch (ClassNotFoundException e) {e.printStackTrace();}// 启动 com.example.dex_demo.MainActivity2 组件if (clazz != null) {context.startActivity(new Intent(context, clazz));}}/*** 替换 LoadedApk 中的 类加载器 ClassLoader** @param context* @param dexFilePath*/@RequiresApi(api = Build.VERSION_CODES.KITKAT)private void startDexActivityWithReplacedClassLoader(Context context, String dexFilePath) {// 优化目录File optFile = new File(getFilesDir(), "opt_dex");// 依赖库目录 , 用于存放 so 文件File libFile = new File(getFilesDir(), "lib_path");// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字节码文件路径optFile.getAbsolutePath(), // 优化目录libFile.getAbsolutePath(), // 依赖库目录context.getClassLoader() // 父节点类加载器);//------------------------------------------------------------------------------------------// 下面开始替换 LoadedApk 中的 ClassLoader// I. 获取 ActivityThread 实例对象// 获取 ActivityThread 字节码类 , 这里可以使用自定义的类加载器加载// 原因是 基于 双亲委派机制 , 自定义的 DexClassLoader 无法加载 , 但是其父类可以加载// 即使父类不可加载 , 父类的父类也可以加载Class<?> ActivityThreadClass = null;try {ActivityThreadClass = dexClassLoader.loadClass("android.app.ActivityThread");} catch (ClassNotFoundException e) {e.printStackTrace();}// 获取 ActivityThread 中的 sCurrentActivityThread 成员// 获取的字段如下 :// private static volatile ActivityThread sCurrentActivityThread;// 获取字段的方法如下 :// public static ActivityThread currentActivityThread() {return sCurrentActivityThread;}Method currentActivityThreadMethod = null;try {currentActivityThreadMethod = ActivityThreadClass.getDeclaredMethod("currentActivityThread");// 设置可访问性 , 所有的 方法 , 字段 反射 , 都要设置可访问性currentActivityThreadMethod.setAccessible(true);} catch (NoSuchMethodException e) {e.printStackTrace();}// 执行 ActivityThread 的 currentActivityThread() 方法 , 传入参数 nullObject activityThreadObject = null;try {activityThreadObject = currentActivityThreadMethod.invoke(null);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}// II. 获取 LoadedApk 实例对象// 获取 ActivityThread 实例对象的 mPackages 成员// final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();Field mPackagesField = null;try {mPackagesField = ActivityThreadClass.getDeclaredField("mPackages");// 设置可访问性 , 所有的 方法 , 字段 反射 , 都要设置可访问性mPackagesField.setAccessible(true);} catch (NoSuchFieldException e) {e.printStackTrace();}// 从 ActivityThread 实例对象 activityThreadObject 中// 获取 mPackages 成员ArrayMap mPackagesObject = null;try {mPackagesObject = (ArrayMap) mPackagesField.get(activityThreadObject);} catch (IllegalAccessException e) {e.printStackTrace();}// 获取 WeakReference<LoadedApk> 弱引用对象WeakReference weakReference = (WeakReference) mPackagesObject.get(this.getPackageName());// 获取 LoadedApk 实例对象Object loadedApkObject = weakReference.get();// III. 替换 LoadedApk 实例对象中的 mClassLoader 类加载器// 加载 android.app.LoadedApk 类Class LoadedApkClass = null;try {LoadedApkClass = dexClassLoader.loadClass("android.app.LoadedApk");} catch (ClassNotFoundException e) {e.printStackTrace();}// 通过反射获取 private ClassLoader mClassLoader; 类加载器对象Field mClassLoaderField = null;try {mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");// 设置可访问性mClassLoaderField.setAccessible(true);} catch (NoSuchFieldException e) {e.printStackTrace();}// 替换 mClassLoader 成员try {mClassLoaderField.set(loadedApkObject, dexClassLoader);} catch (IllegalAccessException e) {e.printStackTrace();}//------------------------------------------------------------------------------------------// 加载 com.example.dex_demo.DexTest 类// 该类中有可执行方法 test()Class<?> clazz = null;try {clazz = dexClassLoader.loadClass("com.example.dex_demo.MainActivity2");} catch (ClassNotFoundException e) {e.printStackTrace();}// 启动 com.example.dex_demo.MainActivity2 组件if (clazz != null) {context.startActivity(new Intent(context, clazz));}}/*** 在类加载器的父类子类节点中 , 插入自定义 DexClassLoader* 基于双亲 委派机制的解决方案** @param context* @param dexFilePath*/@RequiresApi(api = Build.VERSION_CODES.KITKAT)private void startDexActivityWithInsertedClassLoader(Context context, String dexFilePath) {// 优化目录File optFile = new File(getFilesDir(), "opt_dex");// 依赖库目录 , 用于存放 so 文件File libFile = new File(getFilesDir(), "lib_path");//------------------------------------------------------------------------------------------// 下面开始 在 ClassLoader 的双亲委派体系中 , 插入自定义的 DexClassLoader// I. 创建 DexClassLoader , 并设置其 父类节点为 BootClassLoader// 获取 PathClassLoaderClassLoader pathClassloader = MainActivity.class.getClassLoader();// 获取 BootClassLoaderClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();/*注意原来的逻辑是 PathClassLoader 用于加载组件类 , 其父节点是 BootClassLoader现在将 PathClassLoader 父节点设置为 DexClassLoader ,DexClassLoader 父节点设置为 BootClassLoader相当于在 PathClassLoader 和 BootClassLoader 之间插入了一个 DexClassLoader*/// 初始化 DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(dexFilePath, // Dex 字节码文件路径optFile.getAbsolutePath(), // 优化目录libFile.getAbsolutePath(), // 依赖库目录bootClassloader // 父节点类加载器);// II. 使用 DexClassLoader 实例对象作为 PathClassLoader 的父节点// 获取 ClassLoader 的 private final ClassLoader parent; 成员Field parentField = null;try {parentField = ClassLoader.class.getDeclaredField("parent");// 设置可访问性parentField.setAccessible(true);} catch (NoSuchFieldException e) {e.printStackTrace();}// 设置 PathClassLoader 的 parent 字段为 自定义的 DexClassLoader 实例对象try {parentField.set(pathClassloader, dexClassLoader);} catch (IllegalAccessException e) {e.printStackTrace();}//------------------------------------------------------------------------------------------// 加载 com.example.dex_demo.DexTest 类// 该类中有可执行方法 test()Class<?> clazz = null;try {clazz = dexClassLoader.loadClass("com.example.dex_demo.MainActivity2");} catch (ClassNotFoundException e) {e.printStackTrace();}// 启动 com.example.dex_demo.MainActivity2 组件if (clazz != null) {context.startActivity(new Intent(context, clazz));}}}



三、执行结果



执行结果 : 参考 【错误记录】Android 应用运行报错 ( java.lang.VerifyError: Verifier rejected class androidx. | 逆向中遇到的问题 ) 博客 , 启动 Activity 组件有报错 , 但是使用类加载器加载 Activity 组件是成功的 ;

在 启动 Activity 组件之前打上断点 , 可以发现 , dexClassLoader.loadClass 操作是成功的 , 加载 Activity 组件操作是成功的 ;

// 启动 com.example.dex_demo.MainActivity2 组件if (clazz != null) {context.startActivity(new Intent(context, clazz));}





四、博客资源



GitHub 地址 : https://github.com/han1202012/ClassLoader_Demo

CSDN 下载 :

总结

以上是生活随笔为你收集整理的【Android 逆向】启动 DEX 字节码中的 Activity 组件 ( 在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader )的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。