HOOK技术六-LoadedApk式插件化代码实现

系列文章

HOOK技术一-HOOK技术初探
HOOK技术二-未注册Activity的启动
HOOK技术三-插件Activity启动前提分析
HOOK技术四-插件中Activity启动实战
HOOK技术五-使用LoadedApk式插件化的理论分析
HOOK技术六-LoadedApk式插件化代码实现
HOOK技术七-版本适配及总结

代码实现

 /**
     * 自己创造一个LoadedApk.ClassLoader 添加到 mPackages,此LoadedApk 专门用来加载插件里面的 class
     */
    private void customLoadedApkAction() throws Exception {
        File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
        if (!file.exists()) {
            throw new FileNotFoundException("插件包不存在..." + file.getAbsolutePath());
        }
        String pulginPath = file.getAbsolutePath();

        // mPackages 添加 自定义的LoadedApk
        // final ArrayMap<String, WeakReference<LoadedApk>> mPackages 添加自定义LoadedApk
        Class mActivityThreadClass = Class.forName("android.app.ActivityThread");

        // 执行此方法 public static ActivityThread currentActivityThread() 拿到 ActivityThread对象
        Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);

        Field mPackagesField = mActivityThreadClass.getDeclaredField("mPackages");
        mPackagesField.setAccessible(true);
        // 拿到mPackages对象
        Object mPackagesObj = mPackagesField.get(mActivityThread);

        Map mPackages = (Map) mPackagesObj;

        // 如何自定义一个 LoadedApk,系统是如何创造LoadedApk的,我们就怎么去创造LoadedApk
        // 执行此 public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo)
        Class mCompatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
        Field defaultField = mCompatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
        defaultField.setAccessible(true);
        Object defaultObj = defaultField.get(null);

        /**
         * ApplicationInfo 如何获取,我们之前学习 APK解析源码分析
         */
        ApplicationInfo applicationInfo = getApplicationInfoAction();

        Method mLoadedApkMethod = mActivityThreadClass.getMethod("getPackageInfoNoCheck", ApplicationInfo.class, mCompatibilityInfoClass); // 类类型
        // 执行 才能拿到 LoedApk 对象
        Object mLoadedApk = mLoadedApkMethod.invoke(mActivityThread, applicationInfo, defaultObj);

        // 自定义加载器 加载插件
        // String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent

        File fileDir = getDir("pulginPathDir", Context.MODE_PRIVATE);

        // 自定义 加载插件的 ClassLoader
        ClassLoader classLoader = new PluginClassLoader(pulginPath,fileDir.getAbsolutePath(), null, getClassLoader());

        Field mClassLoaderField = mLoadedApk.getClass().getDeclaredField("mClassLoader");
        mClassLoaderField.setAccessible(true);
        mClassLoaderField.set(mLoadedApk, classLoader); // 替换 LoadedApk 里面的 ClassLoader

        // 添加自定义的 LoadedApk 专门加载 插件里面的 class

        // 最终的目标 mPackages.put(插件的包名,插件的LoadedApk);
        WeakReference weakReference = new WeakReference(mLoadedApk); // 放入 自定义的LoadedApk --》 插件的
        mPackages.put(applicationInfo.packageName, weakReference); // 增加了我们自己的LoadedApk
    }

 /**
     * 获取 ApplicationInfo 为插件服务的
     * @return
     * @throws
     */
    private ApplicationInfo getApplicationInfoAction() throws Exception {
        // 执行此public static ApplicationInfo generateApplicationInfo方法,拿到ApplicationInfo
        Class mPackageParserClass = Class.forName("android.content.pm.PackageParser");

        Object mPackageParser = mPackageParserClass.newInstance();

        // generateApplicationInfo方法的类类型
        Class $PackageClass = Class.forName("android.content.pm.PackageParser$Package");
        Class mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");

        Method mApplicationInfoMethod = mPackageParserClass.getMethod("generateApplicationInfo",$PackageClass,
                int.class, mPackageUserStateClass);

        File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
        String pulginPath = file.getAbsolutePath();

        // 执行此public Package parsePackage(File packageFile, int flags)方法,拿到 Package
        // 获得执行方法的对象
        Method mPackageMethod = mPackageParserClass.getMethod("parsePackage", File.class, int.class);
        Object mPackage = mPackageMethod.invoke(mPackageParser, file, PackageManager.GET_ACTIVITIES);

        // 参数 Package p, int flags, PackageUserState state
        ApplicationInfo applicationInfo = (ApplicationInfo)
                mApplicationInfoMethod.invoke(mPackageParser, mPackage, 0, mPackageUserStateClass.newInstance());

        // 获得的 ApplicationInfo 就是插件的 ApplicationInfo
        // 我们这里获取的 ApplicationInfo
        // applicationInfo.publicSourceDir = 插件的路径;
        // applicationInfo.sourceDir = 插件的路径;
        applicationInfo.publicSourceDir = pulginPath;
        applicationInfo.sourceDir = pulginPath;

        return applicationInfo;
    }

这样我们就有了LoadedApk来加载插件中的Activity了, 但是目前还没有办法启动插件中的Activity,
因为在Activity的启动过程中,还有一步是在LoadedApk加载class之后的检查package是否安装。因此还需要Hook住,欺骗系统

首先在启动插件中Activity的时候,将包名传递过去。

try {
                        // 我们要获取之前Hook携带过来的 TestActivity
                        Field intentField = obj.getClass().getDeclaredField("intent");
                        intentField.setAccessible(true);

                        // 获取 intent 对象,才能取出携带过来的 actionIntent
                        Intent intent = (Intent) intentField.get(obj);
                        // actionIntent == TestActivity的Intent
                        Intent actionIntent = intent.getParcelableExtra("actionIntent");

                        if (actionIntent != null) {
                            /*
                            if (activityList.contains(actionIntent.getComponent().getClassName())) {
                                intentField.set(obj, actionIntent); // 把ProxyActivity 换成  TestActivity
                            } else { // 没有权限
                                intentField.set(obj, new Intent(HookApplication.this, PermissionActivity.class));
                            }
                            */

                            intentField.set(obj, actionIntent); // 把ProxyActivity 换成  TestActivity


                            /***
                             *  我们在以下代码中,对插件  和 宿主 进行区分
                             */
                            Field activityInfoField = obj.getClass().getDeclaredField("activityInfo");
                            activityInfoField.setAccessible(true); //授权
                            ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(obj);

                            // 什么时候 加载插件的  ?
                            if (actionIntent.getPackage() == null) { // 证明是插件
                                activityInfo.applicationInfo.packageName = actionIntent.getComponent().getPackageName();

                                // Hook 拦截此 getPackageInfo 做自己的逻辑
                                hookGetPackageInfo();

                            } else { // 宿主
                                activityInfo.applicationInfo.packageName = actionIntent.getPackage();
                            }
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }

然后Hook住检测包名的步骤

   // Hook 拦截此 getPackageInfo 做自己的逻辑
    private void hookGetPackageInfo() {
        try {
            // sPackageManager 替换  我们自己的动态代理
            Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThreadField = mActivityThreadClass.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);

            Field sPackageManagerField = mActivityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            final Object packageManager = sPackageManagerField.get(null);

            /**
             * 动态代理
             */
            Class mIPackageManagerClass = Class.forName("android.content.pm.IPackageManager");

            Object mIPackageManagerProxy = Proxy.newProxyInstance(getClassLoader(),

                    new Class[]{mIPackageManagerClass}, // 要监听的接口

                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if ("getPackageInfo".equals(method.getName())) {
                                // 如何才能绕过 PMS, 欺骗系统

                                // pi != null
                                return new PackageInfo(); // 成功绕过 PMS检测
                            }
                            // 让系统正常继续执行下去
                            return method.invoke(packageManager, args);
                        }
                    });


            // 替换  狸猫换太子   换成我们自己的 动态代理
            sPackageManagerField.set(null, mIPackageManagerProxy);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这样就能启动了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值