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

简介: 【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
        // 获取 PathClassLoader
        ClassLoader pathClassloader = MainActivity.class.getClassLoader();
        // 获取 BootClassLoader
        ClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();
        /*
            注意原来的逻辑是 PathClassLoader 用于加载组件类 , 其父节点是 BootClassLoader
            现在将 PathClassLoader 父节点设置为 DexClassLoader ,
            DexClassLoader 父节点设置为 BootClassLoader
            相当于在 PathClassLoader 和 BootClassLoader 之间插入了一个 DexClassLoader
         */
        // 初始化 DexClassLoader
        DexClassLoader 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 之间
        // 插入 DexClassLoader
        if (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;
    @Override
    protected 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 之间
        // 插入 DexClassLoader
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            startDexActivityWithInsertedClassLoader(this, mDexPath);
        }
    }
    /**
     * 打印当前的类加载器及父节点
     */
    private void classloaderLog() {
        // 获取当前 Activity 的 类加载器 ClassLoader
        ClassLoader 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");
        // 初始化 DexClassLoader
        DexClassLoader 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");
        // 初始化 DexClassLoader
        DexClassLoader 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");
        // 初始化 DexClassLoader
        DexClassLoader 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() 方法 , 传入参数 null
        Object 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
        // 获取 PathClassLoader
        ClassLoader pathClassloader = MainActivity.class.getClassLoader();
        // 获取 BootClassLoader
        ClassLoader bootClassloader = MainActivity.class.getClassLoader().getParent();
        /*
            注意原来的逻辑是 PathClassLoader 用于加载组件类 , 其父节点是 BootClassLoader
            现在将 PathClassLoader 父节点设置为 DexClassLoader ,
            DexClassLoader 父节点设置为 BootClassLoader
            相当于在 PathClassLoader 和 BootClassLoader 之间插入了一个 DexClassLoader
         */
        // 初始化 DexClassLoader
        DexClassLoader 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));
        }



目录
相关文章
|
1月前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
2月前
|
存储 Android开发 开发者
深入理解安卓应用开发的核心组件
【10月更文挑战第8天】探索Android应用开发的精髓,本文带你了解安卓核心组件的奥秘,包括Activity、Service、BroadcastReceiver和ContentProvider。我们将通过代码示例,揭示这些组件如何协同工作,构建出功能强大且响应迅速的应用程序。无论你是初学者还是资深开发者,这篇文章都将为你提供新的视角和深度知识。
|
2月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
95 0
|
15天前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
2月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
2月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
52 6
|
3月前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
267 5
|
3月前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
4月前
|
存储 搜索推荐 Java
探索安卓开发中的自定义视图:打造个性化UI组件Java中的异常处理:从基础到高级
【8月更文挑战第29天】在安卓应用的海洋中,一个独特的用户界面(UI)能让应用脱颖而出。自定义视图是实现这一目标的强大工具。本文将通过一个简单的自定义计数器视图示例,展示如何从零开始创建一个具有独特风格和功能的安卓UI组件,并讨论在此过程中涉及的设计原则、性能优化和兼容性问题。准备好让你的应用与众不同了吗?让我们开始吧!
|
4月前
|
XML 搜索推荐 Android开发
安卓开发中的自定义View组件实践
【8月更文挑战第30天】探索Android世界,自定义View是提升应用界面的关键。本文以简洁的语言带你了解如何创建自定义View,从基础到高级技巧,一步步打造个性化的UI组件。