Android 插件化Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 插件化Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )相关的知识,希望对你有一定的参考价值。

android 插件化系列文章目录

【Android 插件化】插件化简介 ( 组件化与插件化 )
【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 )
【Android 插件化】插件化原理 ( 类加载器 )

【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )

【Android 插件化】Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )
【Android 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )
【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 一 | Activity 进程相关源码 )
【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 二 | AMS 进程相关源码 | 主进程相关源码 )

【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )
【Android 插件化】Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )



前言

在上一篇博客 【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 ) 中简要介绍了 hook 插件化原理 , 并开始开发插件化管理类 , 本博客中开始加载插件包中的 Element[] dexElements ;

Android 中的类加载器 DexClassLoader , PathClassLoader 的父类是 BaseDexClassLoader ,

BaseDexClassLoader 中有 private final DexPathList pathList 成员变量 ,

DexPathList 中有 private Element[] dexElements 成员变量 ,

Element[] dexElements 就是最终存放 Dex 字节码数据的内存变量 , 最终将 " 插件包 " 中读取的 dexElements 合并到 " 宿主 " 应用的 dexElements 中 ;






一、通过反射获取 “插件包” 中的 Element[] dexElements



反射 " 插件包 " 应用的 dexElement 执行步骤 :

① 反射获取 BaseDexClassLoader.class

② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段

③ 反射获取 plugin_dexClassLoader 类加载器中的 DexPathList pathList 成员对象

④ 获取 DexPathList.class

⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象

⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象


1、反射获取 BaseDexClassLoader.class


反射获取 BaseDexClassLoader.class , 通过反射获取插件包中的 dexElements , 这种类加载是合并类加载 , 将所有的 Dex 文件 , 加入到应用的 dex 文件集合中 , 可参考 dex 加固 , 热修复 , 插装式插件化 的实现步骤 ;

反射出 BaseDexClassLoader 类 , PathClassLoader 和 DexClassLoader 都是 BaseDexClassLoader 的子类 ;

参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

代码示例 :

// ① 反射获取 BaseDexClassLoader.class

// 通过反射获取插件包中的 dexElements
// 这种类加载是合并类加载 , 将所有的 Dex 文件 , 加入到应用的 dex 文件集合中
//  可参考 dex 加固 , 热修复 , 插装式插件化 的实现步骤
// 反射出 BaseDexClassLoader 类 , PathClassLoader 和 DexClassLoader
//  都是 BaseDexClassLoader 的子类
// 参考 https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
Class<?> baseDexClassLoaderClass = null;
try {
    baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

2、反射获取 DexPathList pathList 字段


// ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
Field plugin_pathListField = null;
try {
    plugin_pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
    // 设置属性的可见性
    plugin_pathListField.setAccessible(true);
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

3、反射获取 DexPathList pathList 对象


根据 Field 字段获取 成员变量 , DexClassLoader 继承了 BaseDexClassLoader, 因此其内部肯定有 private final DexPathList pathList 成员变量 ;

// ③ 反射获取 plugin_dexClassLoader 类加载器中的 DexPathList pathList 成员对象
// 根据 Field 字段获取 成员变量
//  DexClassLoader 继承了 BaseDexClassLoader, 因此其内部肯定有
//  private final DexPathList pathList 成员变量
Object plugin_pathListObject = null;
try {
    plugin_pathListObject = plugin_pathListField.get(plugin_dexClassLoader);
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

4、获取 DexPathList pathList 对象


获取 DexPathList.class , 之前已经通过反射获取了 DexPathList pathList 对象 , 这里直接通过调用该对象的 getClass 方法 , 获取该类对象 ;

参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

// ④ 获取 DexPathList.class
// DexPathList 类中有 private Element[] dexElements 成员变量
// 通过反射获取该成员变量
// 参考 https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
// 获取 DexPathList pathList 成员变量的字节码类型 ( 也可以通过反射获得 )
// 获取的是 DexPathList.class
Class<?> plugin_dexPathListClass = plugin_pathListObject.getClass();

5、反射获取 Element[] dexElements 字段


// ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
Field plugin_dexElementsField = null;
try {
    plugin_dexElementsField = plugin_dexPathListClass.getDeclaredField("dexElements");
    // 设置属性的可见性
    plugin_dexElementsField.setAccessible(true);
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

6、反射获取 Element[] dexElements 对象


// ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
Object plugin_dexElementsObject = null;
try {
    plugin_dexElementsObject = plugin_dexElementsField.get(plugin_pathListObject);
} catch (IllegalAccessException e) {
    e.printStackTrace();
}




二、完整代码示例



package kim.hsl.plugin;

import android.content.Context;

import java.lang.reflect.Field;

import dalvik.system.DexClassLoader;

/**
 * 使用 Hook 实现的插件使用入口
 *  1. 加载插件包中的字节码
 *  2. 直接通过 hook 技术, 钩住系统的 Activity 启动流程实现
 *      ① Activity 对象创建之前 , 要做很多初始化操作 , 先在 ActivityRecord 中加载 Activity 信息
 *          如果修改了该信息 , 将要跳转的 Activity 信息修改为插件包中的 Activity
 *          原来的 Activity 只用于占位 , 用于欺骗 Android 系统
 *      ② 使用 hook 技术 , 加载插件包 apk 中的 Activity
 *      ③ 实现跳转的 Activity ( 插件包中的 )
 *  3. 解决 Resources 资源冲突问题
 *  ( 使用上述 hook 插件化 , 可以不用考虑 Activity 的声明周期问题 )
 *
 *  插件包中的 Activity 是通过正规流程 , 由 AMS 进行创建并加载的
 *      但是该 Activity 并没有在 AndroidManifest.xml 清单文件中注册
 *      这里需要一个已经在清单文件注册的 Activity 欺骗系统
 *
 *  插装式插件化 是通过代理 Activity , 将插件包加载的字节码 Class 作为一个普通的 Java 类
 *      该普通的 Java 类有所有的 Activity 的业务逻辑
 *      该 Activity 的声明周期 , 由代理 Activity 执行相关的生命周期方法
 *  hook 插件化 : hook 插件化直接钩住系统中 Activity 启动流程的某个点
 *      使用插件包中的 Activity 替换占位的 Activity
 */
public class PluginManager {

    /**
     * 上下文
     */
    private Context mBase;

    /**
     * 单例
     */
    private static PluginManager instance;

    public static PluginManager getInstance(Context context) {
        if (instance == null) {
            instance = new PluginManager(context);
        }
        return instance;
    }

    private PluginManager(Context context) {
        this.mBase = context;
    }

    /**
     * Application 启动后 , 调用该方法初始化插件化环境
     *  加载插件包中的字节码
     */
    private void init() {
        // 加载 apk 文件
        loadApk();
    }

    private void loadApk() {
        // 插件包的绝对路径 ,  /data/data/< package name >/files/
        String apkPath = mBase.getFilesDir().getAbsolutePath() + "plugin.apk";
        // 加载插件包后产生的缓存文件路径
        // /data/data/< package name >/app_plugin_cache/
        String cachePath =
                mBase.getDir("plugin_cache", Context.MODE_PRIVATE).getAbsolutePath();
        // 创建类加载器
        DexClassLoader plugin_dexClassLoader =
                new DexClassLoader(
                        apkPath,                // 插件包路径
                        cachePath,              // 插件包加载时产生的缓存路径
                        null,   // 库的搜索路径, 可以设置为空
                        mBase.getClassLoader()  // 父加载器, PathClassLoader
                );

        // 1. 反射 " 插件包 " 应用的 dexElement

        // 执行步骤 :
        // ① 反射获取 BaseDexClassLoader.class
        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        // ③ 反射获取 plugin_dexClassLoader 类加载器中的 DexPathList pathList 成员对象
        // ④ 反射获取 DexPathList.class
        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象

        // ① 反射获取 BaseDexClassLoader.class

        // 通过反射获取插件包中的 dexElements
        // 这种类加载是合并类加载 , 将所有的 Dex 文件 , 加入到应用的 dex 文件集合中
        //  可参考 dex 加固 , 热修复 , 插装式插件化 的实现步骤
        // 反射出 BaseDexClassLoader 类 , PathClassLoader 和 DexClassLoader
        //  都是 BaseDexClassLoader 的子类
        // 参考 https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
        Class<?> baseDexClassLoaderClass = null;
        try {
            baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        Field plugin_pathListField = null;
        try {
            plugin_pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
            // 设置属性的可见性
            plugin_pathListField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        // ③ 反射获取 plugin_dexClassLoader 类加载器中的 DexPathList pathList 成员对象
        // 根据 Field 字段获取 成员变量
        //  DexClassLoader 继承了 BaseDexClassLoader, 因此其内部肯定有
        //  private final DexPathList pathList 成员变量
        Object plugin_pathListObject = null;
        try {
            plugin_pathListObject = plugin_pathListField.get(plugin_dexClassLoader);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        // ④ 获取 DexPathList.class

        // DexPathList 类中有 private Element[] dexElements 成员变量
        // 通过反射获取该成员变量
        // 参考 https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

        // 获取 DexPathList pathList 成员变量的字节码类型 ( 也可以通过反射获得 )
        // 获取的是 DexPathList.class
        Class<?> plugin_dexPathListClass = plugin_pathListObject.getClass();

        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        Field plugin_dexElementsField = null;
        try {
            plugin_dexElementsField = plugin_dexPathListClass.getDeclaredField("dexElements");
            // 设置属性的可见性
            plugin_dexElementsField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
        Object plugin_dexElementsObject = null;
        try {
            plugin_dexElementsObject = plugin_dexElementsField.get(plugin_pathListObject);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}





三、博客资源



博客资源 :

以上是关于Android 插件化Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )的主要内容,如果未能解决你的问题,请参考以下文章

Android 插件化Hook 插件化框架 ( Hook Activity 启动流程 | Hook 点分析 )

Android 插件化Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★

Android 插件化Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )

Android 插件化Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )

Android 插件化Hook 插件化框架 ( Hook Activity 启动流程 | 反射获取 IActivityManager 对象 )

Android 插件化Hook 插件化框架 ( 使用 Hook 方式替换插件 Activity 的 mResources 成员变量 )