HOOK技术七-版本适配及总结

系列文章

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

因为不同版本之间的源码差距,所以再Hook的时候, 往往会遇到版本兼容问题。

兼容的绕过AMS检查

 /**
     * TODO 注意:此方法 适用于 21以下的版本 以及 21_22_23_24_25  26_27_28 等系统版本
     * @param mContext
     * @throws ClassNotFoundException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public static void mHookAMS(final Context mContext) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {

        // 公共区域
        Object mIActivityManagerSingleton = null; // TODO 注意:公共区域 适用于 21以下的版本 以及 21_22_23_24_25  26_27_28 等系统版本
        Object mIActivityManager = null; // TODO 注意:公共区域 适用于 21以下的版本 以及 21_22_23_24_25  26_27_28 等系统版本

        if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
            // @3 的获取    系统的 IActivityManager.aidl
            Class mActivityManagerClass = Class.forName("android.app.ActivityManager");
            mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null);


            // @1 的获取    IActivityManagerSingleton
            Field mIActivityManagerSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
            mIActivityManagerSingletonField.setAccessible(true);
            mIActivityManagerSingleton = mIActivityManagerSingletonField.get(null);

        } else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()) {
            // @3 的获取
            Class mActivityManagerClass = Class.forName("android.app.ActivityManagerNative");
            Method getDefaultMethod = mActivityManagerClass.getDeclaredMethod("getDefault");
            getDefaultMethod.setAccessible(true);
            mIActivityManager = getDefaultMethod.invoke(null);

            // @1 的获取 gDefault
            Field gDefaultField = mActivityManagerClass.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);
            mIActivityManagerSingleton = gDefaultField.get(null);
        }

        // @2 的获取    动态代理
        Class mIActivityManagerClass = Class.forName("android.app.IActivityManager");
        final Object finalMIActivityManager = mIActivityManager;
        Object mIActivityManagerProxy =  Proxy.newProxyInstance(mContext.getClassLoader(),
                new Class[]{mIActivityManagerClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if ("startActivity".equals(method.getName())) {
                            // 把LoginActivity 换成 ProxyActivity
                            // TODO 狸猫换太子,把不能经过检测的LoginActivity 替换 成能够经过检测的ProxyActivity
                            Intent proxyIntent = new Intent(mContext, ProxyActivity.class);

                            // 把目标的LoginActivity 取出来 携带过去
                            Intent target = (Intent) args[2];
                            proxyIntent.putExtra(Parameter.TARGET_INTENT, target);
                            args[2] = proxyIntent;
                        }

                        // @3
                        return method.invoke(finalMIActivityManager, args);
                    }
                });

        if (mIActivityManagerSingleton == null || mIActivityManagerProxy == null) {
            throw new IllegalStateException("实在是没有检测到这种系统,需要对这种系统单独处理..."); // 10.0
        }

        Class mSingletonClass = Class.forName("android.util.Singleton");

        Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);

        // 把系统里面的 IActivityManager 换成 我们自己写的动态代理 【第一步】
        //  @1    @2
        mInstanceField.set(mIActivityManagerSingleton, mIActivityManagerProxy);
    }

兼容的将ProxyActivity还原为原Activity

{

    /**
     * TODO 注意:此方法 适用于 21以下的版本 以及 21_22_23_24_25  26_27_28 等系统版本
     * @param mContext
     * @throws Exception
     */
    public static void mActivityThreadmHAction(Context mContext) throws Exception {
        if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
            do_26_27_28_mHRestore();
        } else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()){
            do_21_22_23_24_25_mHRestore();
        } else {
            throw new IllegalStateException("实在是没有检测到这种系统,需要对这种系统单独处理...");
        }
    }


    /**
     * 高版本 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
     */

    /**
     * TODO 看到此方法,应该明白,就是专门给 26_27_28 系统版本 做【还原操作】的
     */
    private final static void do_26_27_28_mHRestore() throws Exception {
        // @1 怎么得到? 看源码..
        Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
        Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);
        Field mHField = mActivityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object mH = mHField.get(mActivityThread);

        Field mCallbackField = Handler.class.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        // @1  @2
        // 把系统中的Handler.Callback实现 替换成 我们自己写的Custom_26_27_28_Callback,主动权才在我们手上
        mCallbackField.set(mH, new Custom_26_27_28_Callback());
    }

    // @2
    private static class Custom_26_27_28_Callback implements Handler.Callback {

        @Override
        public boolean handleMessage(Message msg) {
            if (Parameter.EXECUTE_TRANSACTION == msg.what) {

                /*final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);*/

                Object mClientTransaction = msg.obj;

                try {
                    // @1
                    // Field mActivityCallbacksField = mClientTransaction.getClass().getDeclaredField("mActivityCallbacks");
                    Class<?> mClientTransactionClass = Class.forName("android.app.servertransaction.ClientTransaction");
                    Field mActivityCallbacksField = mClientTransactionClass.getDeclaredField("mActivityCallbacks");
                    mActivityCallbacksField.setAccessible(true);
                    // List<ClientTransactionItem> mActivityCallbacks;
                    List mActivityCallbacks = (List) mActivityCallbacksField.get(mClientTransaction);

                    // TODO 需要判断
                    if (mActivityCallbacks.size() == 0) {
                        return false;
                    }

                    Object mLaunchActivityItem = mActivityCallbacks.get(0);

                    Class mLaunchActivityItemClass = Class.forName("android.app.servertransaction.LaunchActivityItem");

                    // TODO 需要判断
                    if (!mLaunchActivityItemClass.isInstance(mLaunchActivityItem)) {
                        return false;
                    }

                   Field mIntentField = mLaunchActivityItemClass.getDeclaredField("mIntent");
                   mIntentField.setAccessible(true);

                    // @2 需要拿到真实的Intent
                    Intent proxyIntent = (Intent) mIntentField.get(mLaunchActivityItem);
                    Log.d("hook", "proxyIntent:" + proxyIntent);
                    Intent targetIntent = proxyIntent.getParcelableExtra(Parameter.TARGET_INTENT);
                    if (targetIntent != null) {
                        mIntentField.set(mLaunchActivityItem, targetIntent);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
            return false;
        }
    }








    // >>>>>>>>>>>>>>>>>>>>>>>> 下面是 就是专门给 21_22_23_24_25 系统版本 做【还原操作】的    低版本



    /**
     * TODO 看到此方法,应该明白,就是专门给 21_22_23_24_25 系统版本 做【还原操作】的
     */
    private final static void do_21_22_23_24_25_mHRestore() throws Exception {
        Class<?> mActivityThreadClass = Class.forName("android.app.ActivityThread");
        Field msCurrentActivityThreadField = mActivityThreadClass.getDeclaredField("sCurrentActivityThread");
        msCurrentActivityThreadField.setAccessible(true);
        Object mActivityThread = msCurrentActivityThreadField.get(null);

        // 如何获取@1
        Field mHField = mActivityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Handler mH = (Handler) mHField.get(mActivityThread);
        Field mCallbackFile = Handler.class.getDeclaredField("mCallback");
        mCallbackFile.setAccessible(true);

        // @1    @2
        mCallbackFile.set(mH, new Custom_21_22_23_24_25_Callback());
    }

    // @2
    private static final class Custom_21_22_23_24_25_Callback implements Handler.Callback {

        @Override
        public boolean handleMessage(Message msg) {
            if (Parameter.LAUNCH_ACTIVITY == msg.what) {
                Object mActivityClientRecord = msg.obj;
                try {
                    Field intentField = mActivityClientRecord.getClass().getDeclaredField("intent");
                    intentField.setAccessible(true);
                    Intent proxyIntent = (Intent) intentField.get(mActivityClientRecord);
                    // TODO 还原操作,要把之前的LoginActivity给换回来
                    Intent targetIntent = proxyIntent.getParcelableExtra(Parameter.TARGET_INTENT);
                    if (targetIntent != null) {
                        // :这种方式比方式要好一些哦,但是需要注意:必须是 setComponent的方式才使用哦
                        // proxyIntent.setComponent(targetIntent.getComponent());

                        // 反射的方式
                        intentField.set(mActivityClientRecord, targetIntent);
                    }

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

}

宿主Element与插件Element叠加

{

    // private DexClassLoader dexClassLoader;
    private Resources resources = null;
    private AssetManager assetManager = null;

    /**
     * 此方法的主要目的是,宿主和插件的 DexElement融合
     */
    public void mainPluginFuse(Context mContext) throws Exception {
        // TODO 宿主的dexElements
        // Object mainDexElements = getDexElements(mContext.getClassLoader());
        Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object mDexPathList = pathListField.get(mContext.getClassLoader());
        Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        Object mainDexElements = dexElementsField.get(mDexPathList);

        // TODO 插件的dexElements
        /*File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
        if (file.exists() == false) {
            return;
        }*/
        File fileDir = mContext.getDir("pDir", Context.MODE_PRIVATE);
        DexClassLoader dexClassLoader = new DexClassLoader(MyApplication.pluginPath, fileDir.getAbsolutePath(),null, mContext.getClassLoader());
        // Object pluginDexElements = getDexElements(dexClassLoader);
        Class mBaseDexClassLoaderClass2 = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField2 = mBaseDexClassLoaderClass2.getDeclaredField("pathList");
        pathListField2.setAccessible(true);
        Object mDexPathList2 = pathListField2.get(dexClassLoader);
        Field dexElementsField2 = mDexPathList2.getClass().getDeclaredField("dexElements");
        dexElementsField2.setAccessible(true);
        Object pluginDexElements = dexElementsField2.get(mDexPathList2);


        // TODO 创造出新的 newDexElements
        int mainLen = Array.getLength(mainDexElements);
        int pluginLen = Array.getLength(pluginDexElements);
        int newDexElementsLength = (mainLen + pluginLen);
        Object newDexElements = Array.newInstance(mainDexElements.getClass().getComponentType(), newDexElementsLength);

        // 进行融合
        for (int i = 0; i < newDexElementsLength; i++) {
            // 先融合宿主
            if (i < mainLen) {
                Array.set(newDexElements, i, Array.get(mainDexElements, i));
            } else { // 在融合插件,为什么要i - mainLen,是为了保证取出pluginDexElements,是从0 开始取的
                Array.set(newDexElements, i, Array.get(pluginDexElements, i - mainLen));
            }
        }

        // 把新的替换到宿主中去

        dexElementsField.set(mDexPathList, newDexElements);

        loadResource(mContext);
    }

    // todo   其实 宿主的dexElements 和  插件的dexElements 代码类似,所以可以抽取成方法的
    // todo  我在这里就 不抽取方法了,为了让更好的理解
    // @Deprecated
    private Object getDexElements(ClassLoader classLoader) throws Exception {
        Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object mDexPathList = pathListField.get(classLoader);
        Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        return dexElementsField.get(mDexPathList);
    }

    /*public ClassLoader getClassLoader() {
        return dexClassLoader;
    }*/

    /**
     * 拥有加载资源的能力
     * @param mContext
     * @throws Exception
     */
    private void loadResource(Context mContext) throws Exception {
        Resources r = mContext.getResources();
        assetManager = AssetManager.class.newInstance();
        Method addAssetpathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
        addAssetpathMethod.setAccessible(true);
        // File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
        addAssetpathMethod.invoke(assetManager, MyApplication.pluginPath);

        resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
    }

    public Resources getResources() {
        return resources;
    }

    /**
     * 只需要 让插件去那 宿主的getResources 就好了,不需要让插件去那AssetManager了,为什么呢?
     * 答:因为宿主和插件进行了融合,插件只要拿到宿主中的Resources,就等于拿到了 AssetManager了,因为AssetManager属于单利的哦
     * @return
     */
    public AssetManager getAssetManager() {
        return assetManager;
    }

}

总结

写Hook的时候,从结果往过程推导比较容易,不然从过程开始看会觉得莫名其妙,实际上过程中的变量都是为了最后实现目的。

1.占位插件化 Activity - ProxyActivity -->插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
2.占位插件化 Service - ProxyService -->插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
3.占位插件化 动态广播 - ProxyReceiver -->插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
4.占位插件化 静态广播 – 分析系统是如何解析APK文件的,源码的阅读,阅读源码的思路,PMS入手-(模仿系统是如何解析,我们就怎么解析)(难点)
稳定,但是插件化开发很痛苦 比如:开源中的框架 插件化框架 DL

5.Hook从入门 到 熟练 --(1.替换,2.被替换的 动态代理/接口设置) --> Hook系统源码 – (Hook1)AMS检测是否注册 — (Hook2)换回来
6.安卓的类加载 -->PathClassLoader(加载运行App中的class),DexClassLoader(apk zip), DexPathList Element,插件Element和宿主Element
7.真正的融合–>就可以加载插件里面的class, 插件里面的Layout怎么去加载呢,AseetManager Resources (难点)
Hook式插件化框架 --> 融为一体,Hook方式: 不用考虑宿主的环境,不稳定==兼容性, 360开源框架 Hook方式实现的

8.LoadedApk插件化-- ActivityThread LAUNCH_ACTIVITY源码切入点,ClassLoader–>宿主 --> LoadedApk.ClassLoader --> 宿主class
9.自定义LoadedApk.自定义ClassLoader —> 插件的class, mPackages – size=2 , 没法绕过PMS检测,检测插件包名是否安装了
10.开关 切换 宿主 插件, 再次运行会报错,是因为 PMS检测, Hook PMS,绕过了pi == null的情况
不稳定且有兼容性问题。

所以版本适配中也是采用的融合的方式做插件化的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值