插件化二:Hook AMS实现

一、Android Activity启动流程与Hook技术实现插件化

概述

在Android系统中,通过结合拦截修改startActivity方法和Hook Handler的LAUNCH_ACTIVITY消息,可以实现插件Activity的动态加载和执行,同时绕过AMS(Activity Manager Service)对插件Activity的直接检测。腾讯的QZone和Tinker等应用本质上就是采用了这种hook方式来实现插件化,主要操作的是dexElements。

详细流程
  1. 应用层发起启动请求

    • 用户或代码在应用层通过startActivity方法发起启动插件Activity的请求。
  2. 拦截修改startActivity方法(Hook节点1)

    • startActivity方法执行后,但在Intent被传递给AMS之前,利用反射和动态代理等技术修改Intent的组件信息,将其指向宿主应用中的一个代理Activity。
    • 这一步的目的是为了绕过AMS对插件Activity的直接检测,因为AMS通常不允许直接启动未在Manifest中声明的Activity。
  3. Intent传递给AMS

    • 修改后的Intent(指向代理Activity)被传递给AMS,请求启动该代理Activity。
  4. AMS处理启动请求

    • AMS接收到启动请求后,解析Intent,检查权限等,并准备启动代理Activity。此时,AMS并不知晓最终将启动的是插件Activity。
  5. 准备启动Activity

    • AMS为代理Activity准备必要的资源,如创建Task和ActivityRecord等。
  6. 通知目标应用进程

    • AMS通过IPC机制通知宿主应用进程的ApplicationThread,准备启动代理Activity。
  7. Hook Handler的LAUNCH_ACTIVITY消息(Hook节点2)

    • 在宿主应用进程的ActivityThread中,当Handler接收到来自AMS的LAUNCH_ACTIVITY消息时,准备处理该消息以启动Activity。
    • 在消息被处理之前,通过反射和Hook技术拦截这个消息,并修改其中的Intent对象,将其组件信息更改为插件Activity。
  8. 处理LAUNCH_ACTIVITY消息

    • 使用修改后的Intent(指向插件Activity)启动Activity。
    • ActivityThread调用插件Activity的onCreateonStartonResume等生命周期方法,完成插件Activity的启动和显示。
  9. 自定义DexClassLoader加载相关类

    • 使用自定义的DexClassLoader来加载和执行插件中的类文件。
注意事项
  • 安全性和稳定性:由于此方法涉及对系统底层机制的修改,需特别注意安全性和稳定性问题。不当的Hook操作可能导致系统崩溃或应用行为异常。
  • 兼容性问题:Android系统不断更新,不同版本在系统实现上可能存在差异。因此,实现Hook时需考虑兼容性,确保在不同Android版本上均能正常工作。
  • 权限问题:修改系统级组件或拦截系统消息通常需要较高权限,这些权限可能无法通过普通应用获取。因此,此方法可能需要在系统层面或具有root权限的环境下实现。
结论

通过上述方法,Android系统可实现插件化技术,动态加载和执行插件Activity。然而,此方法具有复杂性和风险性,需谨慎使用,并确保充分测试以避免潜在问题。

二、具体源码实现和解析

1. Hook AMS,绕过AMS对插件Activity的检测
private void hookAmsAction() throws Exception {
    // 获取必要的类
    Class<?> mIActivityManagerClass = Class.forName("android.app.IActivityManager");
    Class<?> mActivityManagerNativeClass2 = Class.forName("android.app.ActivityManagerNative");
    
    // 获取IActivityManager实例
    Object mIActivityManager = mActivityManagerNativeClass2.getMethod("getDefault").invoke(null);

    // 创建IActivityManager的动态代理
    Object mIActivityManagerProxy = Proxy.newProxyInstance(
        HookApplication.class.getClassLoader(),
        new Class[]{mIActivityManagerClass},
        new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("startActivity".equals(method.getName())) {
                    // 修改Intent以启动ProxyActivity
                    Intent intent = new Intent(HookApplication.this, ProxyActivity.class);
                    intent.putExtra("actionIntent", (Intent) args[2]);
                    args[2] = intent;
                }
                Log.d("hook", "拦截到了IActivityManager的方法: " + method.getName());
                // 调用原始方法
                return method.invoke(mIActivityManager, args);
            }
        }
    );

    // 替换ActivityManagerNative中的gDefault为代理对象
    Class<?> mActivityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
    Field gDefaultField = mActivityManagerNativeClass.getDeclaredField("gDefault");
    gDefaultField.setAccessible(true);
    gDefaultField.set(null, mIActivityManagerProxy);

    // 替换Singleton中的mInstance(如果需要)
    // 注意:这部分通常不是必需的,除非有特定实现要求
    // ...
}
2. Hook LAUNCH_ACTIVITY事件,启动插件Activity
private void hookLaunchActivity() throws Exception {
    // 替换Handler的mCallback
    Field mCallbackField = Handler.class.getDeclaredField("mCallback");
    mCallbackField.setAccessible(true);

    // 获取ActivityThread的Handler
    Class<?> mActivityThreadClass = Class.forName("android.app.ActivityThread");
    Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);
    Field mHField = mActivityThreadClass.getDeclaredField("mH");
    mHField.setAccessible(true);
    Handler mH = (Handler) mHField.get(mActivityThread);

    // 替换Handler的Callback
    mCallbackField.set(mH, new MyCallback(mH));
}

class MyCallback implements Handler.Callback {
    private Handler mH;

    public MyCallback(Handler mH) {
        this.mH = mH;
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == LAUNCH_ACTIVITY) {
            // 替换Intent为原始插件Activity的Intent
            try {
                Object obj = msg.obj;
                Field intentField = obj.getClass().getDeclaredField("intent");
                intentField.setAccessible(true);
                Intent intent = (Intent) intentField.get(obj);
                Intent actionIntent = intent.getParcelableExtra("actionIntent");
                if (actionIntent != null) {
                    intentField.set(obj, actionIntent);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 继续处理消息
        mH.handleMessage(msg);
        return true;
    }
}
3. 将插件dex和宿主dex合并
private void mergePluginDex() throws Exception {
    // 获取宿主DexPathList
    PathClassLoader pathClassLoader = (PathClassLoader) this.getClassLoader();
    Class<?> mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
    Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
    pathListField.setAccessible(true);
    Object mDexPathList = pathListField.get(pathClassLoader);

    // 获取插件DexPathList
    File pluginDirFile = getDir("plugin", Context.MODE_PRIVATE);
    File pluginFile = new File(pluginDirFile, "p.apk");
    DexClassLoader dexClassLoader = new DexClassLoader(
        pluginFile.getAbsolutePath(),
        getDir("pluginDir", Context.MODE_PRIVATE).getAbsolutePath(),
        null,
        getClassLoader()
    );
    Class<?> mBaseDexClassLoaderClassPlugin = Class.forName("dalvik.system.BaseDexClassLoader");
    Field pathListFieldPlugin = mBaseDexClassLoaderClassPlugin.getDeclaredField("pathList");
    pathListFieldPlugin.setAccessible(true);
    Object mDexPathListPlugin = pathListFieldPlugin.get(dexClassLoader);

    // 合并DexElements
    Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
    dexElementsField.setAccessible(true);
    Object dexElements = dexElementsField.get(mDexPathList);
    Object dexElementsPlugin = dexElementsField.get(mDexPathListPlugin);

    int mainDexLeng = Array.getLength(dexElements);
    int pluginDexLeng = Array.getLength(dexElementsPlugin);
    Object newDexElements = Array.newInstance(
        dexElements.getClass().getComponentType(),
        mainDexLeng + pluginDexLeng
    );
    System.arraycopy(dexElements, 0, newDexElements, 0, mainDexLeng);
    System.arraycopy(dexElementsPlugin, 0, newDexElements, mainDexLeng, pluginDexLeng);

    // 设置新的DexElements
    dexElementsField.set(mDexPathList, newDexElements);

    // 处理加载插件中的布局等后续操作
    doPluginLayoutLoad();
}
4. 小结

插件化技术主要包括以下三个步骤:

  1. 欺骗AMS:通过Hook IActivityManager的startActivity方法,使得系统认为插件中的Activity是宿主应用的一部分。
  2. Hook LAUNCH_ACTIVITY事件:拦截并修改AMS的LAUNCH_ACTIVITY消息,以启动插件中的Activity。
  3. 合并插件Dex和宿主Dex:动态地将插件的Dex文件合并到宿主应用的Dex列表中,使得插件中的类可以被宿主应用直接加载和使用。

这种插件化方式能够减少对宿主应用的侵入性,但同时也需要处理Android系统版本的适配问题,增加了开发和维护的复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

望佑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值