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

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

2、插件包管理器类


在 init 方法中 , 进行插件的总体初始化操作 , 包括 加载 APK 文件 , 加载 APK 中的资源文件 , Activity 替换 , 加载插件 Activity 资源 ;



loadApk 方法中 , 读取文件系统中的插件包 , 加载其中的 Dex 字节码文件 , 将其合并到宿主字节码数据中 ;


loadResources 方法中 , 读取文件系统中的插件包 , 加载其中的资源文件 ;


package kim.hsl.plugin;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
/**
 * 使用 Hook 实现的插件使用入口 <br><br>
 *  1. 加载插件包中的字节码<br><br>
 *  2. 直接通过 hook 技术, 钩住系统的 Activity 启动流程实现<br>
 *      ① Activity 对象创建之前 , 要做很多初始化操作 , 先在 ActivityRecord 中加载 Activity 信息<br>
 *          如果修改了该信息 , 将要跳转的 Activity 信息修改为插件包中的 Activity<br>
 *          原来的 Activity 只用于占位 , 用于欺骗 Android 系统<br>
 *      ② 使用 hook 技术 , 加载插件包 apk 中的 Activity<br>
 *      ③ 实现跳转的 Activity ( 插件包中的 )<br><br>
 *  3. 解决 Resources 资源冲突问题
 *  ( 使用上述 hook 插件化 , 可以不用考虑 Activity 的声明周期问题 )
 *  <br><br>
 *  插件包中的 Activity 是通过正规流程 , 由 AMS 进行创建并加载的
 *      但是该 Activity 并没有在 AndroidManifest.xml 清单文件中注册
 *      这里需要一个已经在清单文件注册的 Activity 欺骗系统<br><br>
 *
 *  插装式插件化 是通过代理 Activity , 将插件包加载的字节码 Class 作为一个普通的 Java 类<br>
 *      该普通的 Java 类有所有的 Activity 的业务逻辑<br>
 *      该 Activity 的声明周期 , 由代理 Activity 执行相关的生命周期方法<br>
 *  hook 插件化 : hook 插件化直接钩住系统中 Activity 启动流程的某个点<br>
 *      使用插件包中的 Activity 替换占位的 Activity<br>
 */
public class PluginManager {
    /**
     * 上下文
     */
    private Context mBase;
    /**
     * 单例
     */
    private static PluginManager mInstance;
    /**
     * 要加载的插件包中的资源文件
     */
    private Resources mResources;
    public static PluginManager getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new PluginManager(context);
        }
        return mInstance;
    }
    private PluginManager(Context context) {
        this.mBase = context;
    }
    /**
     * Application 启动后 , 调用该方法初始化插件化环境
     *  加载插件包中的字节码
     */
    public void init() {
        // 加载 apk 文件
        loadApk();
        // 加载插件包中的资源文件
        loadResources();
        // 在 AMS 启动之前使用占坑 Activity 替换插件包 Activity
        HookUtils.hookAms(mBase);
        // 在 AMS 执行完毕后 , 主线程 ActivityThread 中创建 Activity 实例对象之间 ,
        //      再将插件包 Activity 替换回去
        HookUtils.hookActivityThread(mBase);
        // 通过 Hook 方式修改 Activity 中的 Resources 资源
        HookUtils.hookInstrumentation();
    }
    private void loadApk() {
        // 插件包的绝对路径 ,  /data/user/0/com.example.plugin_hook/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 成员字段
        // ③ 反射获取 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();
        }
        // 2. 反射 " 宿主 " 应用的 dexElement
        // 执行步骤 :
        // ① 反射获取 BaseDexClassLoader.class
        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        // ③ 反射获取 PathClassLoader 类加载器中的 DexPathList pathList 成员对象
        // ④ 反射获取 DexPathList.class
        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
        // ① 反射获取 BaseDexClassLoader.class
        Class<?> host_baseDexClassLoaderClass = null;
        try {
            host_baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // ② 反射获取 BaseDexClassLoader.calss 中的 private final DexPathList pathList 成员字段
        Field host_pathListField = null;
        try {
            host_pathListField = host_baseDexClassLoaderClass.getDeclaredField("pathList");
            // 设置属性的可见性
            host_pathListField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        // ③ 反射获取 DexClassLoader 类加载器中的 DexPathList pathList 成员对象
        // 根据 Field 字段获取 成员变量
        //  DexClassLoader 继承了 BaseDexClassLoader, 因此其内部肯定有
        //  private final DexPathList pathList 成员变量
        PathClassLoader host_pathClassLoader = (PathClassLoader) mBase.getClassLoader();
        Object host_pathListObject = null;
        try {
            host_pathListObject = host_pathListField.get(host_pathClassLoader);
        } 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<?> host_dexPathListClass = host_pathListObject.getClass();
        // ⑤ 反射获取 DexPathList.class 中的 private Element[] dexElements 成员变量的 Field 字段对象
        Field host_dexElementsField = null;
        try {
            host_dexElementsField = host_dexPathListClass.getDeclaredField("dexElements");
            // 设置属性的可见性
            host_dexElementsField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        // ⑥ 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
        Object host_dexElementsObject = null;
        try {
            host_dexElementsObject = host_dexElementsField.get(host_pathListObject);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // 3. 合并 “插件包“ 与 “宿主“ 中的 Element[] dexElements
        // 将两个 Element[] dexElements 数组合并 ,
        //  合并完成后 , 设置到 PathClassLoader 中的
        //  DexPathList pathList 成员的 Element[] dexElements 成员中
        // 获取 “宿主“ 中的 Element[] dexElements 数组长度
        int host_dexElementsLength = Array.getLength(host_dexElementsObject);
        // 获取 “插件包“ 中的 Element[] dexElements 数组长度
        int plugin_dexElementsLength = Array.getLength(plugin_dexElementsObject);
        // 获取 Element[] dexElements 数组中的 , 数组元素的 Element 类型
        // 获取的是 Element.class
        Class<?> elementClazz = host_dexElementsObject.getClass().getComponentType();
        // 合并后的 Element[] dexElements 数组长度
        int new_dexElementsLength = plugin_dexElementsLength + host_dexElementsLength;
        // 创建 Element[] 数组 , elementClazz 是 Element.class 数组元素类型
        Object newElementsArray = Array.newInstance(elementClazz, new_dexElementsLength);
        //  为新的 Element[] newElementsArray 数组赋值
        //      先将 “插件包“ 中的 Element[] dexElements 数组放入到新数组中
        //      然后将 “宿主“ 中的 Element[] dexElements 数组放入到新数组中
        for (int i = 0; i < new_dexElementsLength; i++) {
            if (i < plugin_dexElementsLength) {
                // “插件包“ 中的 Element[] dexElements 数组放入到新数组中
                Array.set(newElementsArray, i, Array.get(plugin_dexElementsObject, i));
            } else {
                //  “宿主“ 中的 Element[] dexElements 数组放入到新数组中
                Array.set(newElementsArray, i, Array.get(host_dexElementsObject, i - plugin_dexElementsLength));
            }
        }
        // 4. 重新设置 PathClassLoader 中的 DexPathList pathList 成员的 Element[] dexElements 属性值
        Field elementsFiled = null;
        try {
            elementsFiled = host_pathListObject.getClass().getDeclaredField("dexElements");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        elementsFiled.setAccessible(true);
        //  设置 DexPathList pathList 的 Element[] dexElements 属性值
        //      host_pathListObject 是原来的属性值
        //      newElementsArray 是新的合并后的 Element[] dexElements 数组
        //  注意 : 这里也可以使用 host_dexElementsField 字段进行设置
        try {
            elementsFiled.set(host_pathListObject, newElementsArray);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    /**
     * 加载资源文件
     * @return
     */
    public Resources loadResources() {
        // 使用反射工具类进行链式调用 , 创建 AssetManager 对象
        AssetManager assetManager = Reflector.on(AssetManager.class).newInstance();
        // 获取插件包 APK 文件路径 , 加载该 APK 下的资源
        // /data/user/0/com.example.plugin_hook/files/plugin.apk
        String pluginPath = mBase.getFilesDir() + "/plugin.apk";
        // 使用反射调用 AssetManager 中的 addAssetPath 方法 , 传入 APK 插件包的路径
        // addAssetPath 方法的参数为 /data/user/0/com.example.plugin_hook/files/plugin.apk
        Reflector.on(assetManager).method("addAssetPath", String.class).call(pluginPath);
        // 创建 Resources 并返回
        return mResources = new Resources(
                assetManager,
                mBase.getResources().getDisplayMetrics(),
                mBase.getResources().getConfiguration()
        );
    }
    public Resources getResources() {
        return mResources;
    }
}




目录
相关文章
|
SQL 人工智能 Dart
Android Studio的插件生态非常丰富
Android Studio的插件生态非常丰富
1100 1
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
6068 77
|
Java Android开发
安卓虚拟摄像头过人脸,免root虚拟hook相机,虚拟相机hook版【jar】
两种Hook Android相机的方法:Xposed模块和Frida脚本。Xposed模块需要安装在已root的设备
|
安全 算法 小程序
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
1117 28
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
API Android开发
微信虚拟摄像头模块,微信虚拟视频聊天,安卓虚拟摄像头插件
该实现包含虚拟摄像头服务核心、视频流生成和Android配置三个关键模块,使用Camera2
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
1057 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
791 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
583 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex

热门文章

最新文章