美文网首页
Android插件化之Hook底层原理

Android插件化之Hook底层原理

作者: 蜗牛是不是牛 | 来源:发表于2022-11-02 22:01 被阅读0次

    最近插件化比较流行,同时也涉及到很多底层源码,内容较多,本文将会将最精华的部分挑出来讲,尽可能把插件化吃下去,将按照下面三个模块来分析,:

    1. 插件化诞生背景
    2. Hook是什么
    3. 底层实现原理

    插件化诞生背景

    什么是插件化?

    我们先来看看下面的场景:

    1、如果要做功能丰富的apk,在里面,我们能玩到各种各样的游戏(如一个游戏平台),如果要将所有的游戏代码都预先写到一个apk里面,用户的下载及安装的时间成本则非常高,用户体验无疑十分糟糕。(按需加载,功能解耦)

    2、现在我们开发的apk功能需要持续迭代更新,更新频率比较高,是否意味着要让用户高频地下载安装来更新apk?有没有什么好的办法,能解决这个问题?(安装成本,热更新)

    为了能同时解决上面两个痛点,插件化就出来了:

    插件化就是能将一个apk的复杂业务按功能分成多个模块,每个模块作为一个单独的apk,通过hook机制让用户按需下载安装,在需要更新时,能够实现免安装更新的技术

    Hook是什么?

    从上面的对插件化的解释,可知实现插件化的关键技术就是Hook,所以问题又来了,什么是Hook?

    我们先来想一下,解决上面两个问题的方法:

    上面两个问题翻译一下就是怎么免去用户主动下载安装这个过程,来到达在安卓系统中运行或更新我们的“业务功能”,我们知道由于安卓系统的设计和权限的限制,任何app都必须先安装之后才能运行业务工能,那有什么好办法?

    Hook

    你android系统即然一定要安装,那我就“欺骗”你一下,让你误以为安装了,这就是Hook:

    一种函数拦截技术,在进程间正常通信的时候进行拦截,将通信过程中传递的结果替换为我们想要的结果,实现欺上瞒下,从而达到免安装运行的目的

    那现在知道了hook是一门“欺骗”的技术,那它要去骗谁,才能达到免安装apk的目的?

    这其中最关键的是对AMS(Activity Manage Service)和PMS(Package Manage Service)以及Handler的hook。AMS负责管理Android中Activity、 Service、Content Provider、Broadcast四大组件的生命周期以及各种调度工作,我们hook它可以实现对插件四大组件的管理及调度;

    PMS负责管理系统中安装的所有App,我们hook它是为了让插件以为自己已经被安装;

    Handler是系统向插件传递消息的一个桥梁,我们hook它则是为了把系统发向宿主的消息转发给插件。

    底层实现原理

    由于篇幅巨大,所以主要讲解图二中Hook是如何“欺骗AMS”,来达到启动一个我们想要的activity(界面)的目的

    由于涉及AMS,而AMS的底层实现是基于Binder的,所以会根据以下思路讲解:

    1. Binder基本原理

    Binder分为Client和Server两个进程

    2. AMS基本原理

    如果站在四大组件的角度来看,AMS就是Binder中的Server
    AMS(ActivityManagerService)从字面意思上看是管理Activity的,但其实四大组件都归它管

    由此而说到了插件化,有两个让人困惑问题:

    1. App的安装过程,为什么不把apk解压缩到本地?这样读取图片就不用每次从apk包中读取了

    2. 为什么Hook永远是在Binder Client端,也就是四大组件这边,而不是在AMS那一侧进行Hook

    这里要说清楚第二个问题。就拿Android剪切板举例吧,它也是个Binder服务

    AMS要负责和所有App的四大组件进行通信。 如果在一个App中,在AMS层面把剪切板功能进行 了Hook,那会导
    致Android系统所有的剪切板功能被Hook——这就是病毒了,如果是这样的话,Android系统早就死翘翘了。所以
    Android系统不允许我们这么做。

    我们只能在AMS的另一侧,即Client端,也就是四大组件这边做Hook,这样即使我们把剪切板功 能进行了
    Hook,也只影响Hook代码所在的App,在 别的App中,剪切板功能还是正常的。

    所以说对于AMS,需要有两个明确的概念:

    • 用Binder思维,它属于Server角色;
    • 在app与AMS通讯过程中,进行Hook的话,我们需要从app端下手

    3. Hook底层原理

    接下来,将以一个Demo讲解Hook原理

    上面我们也说了,Hook可以欺骗安卓系统,来启动我们想要的东西,那行,现在我们来启动一个activity,而且是没在AndroidManifest中声明过的

    那么问题又来了:为什么要起一个未声明过的activity呢?与Hook有什么关系呢?

    安卓系统分配给每个进程的资源是有限的,而这种欺骗本质上就是为了获取系统更多的资源,所以启动一个未声明过的activity,实际上并不会作为宿主的资源去加载,那么我们插件化的目的就达到了,即加载任何我们希望加载的内容,同时不被系统限制

    所以下面的内容,就是要分析如何绕开这种限制。

    既然要启动一个未声明的activity,说白了,还是要走启动的流程,只不过这个流程是我们想要的流程,所以我们先要知道activity原来的启动流程:

    总体上分为两大步骤:(以A界面启动B界面为例,B未声明)

    一:A通知AMS去启动B

    二:AMS通知app进程去启动B

    流程这么多,该Hook哪里,才能达到启动未声明activity的目的呢?

    上面说过,要从client端下手,即app进程,同时结合逆向分析

    逆向分析:

    如果启动一个未声明的activity,那它在原来的启动流程中的哪一个环节会出现问题呢?这个问题点能不能通过
    hook解决呢?能,不就是我们Hook的解决方案了吗?

    事实上,这就是找准Hook点的关键思路:

    在熟悉原有的完整流程前提下,去设想流程中发生了一件我们要想发生的事,比如这里的启动未声明的activity,然后去看这个流程会发生什么问题,那么这个问题点的前后,往往就是我们可以Hook的位置

    回到例子中,因为AMS对Activity是否在AndroidManifest中声明的检查,是在第2步完成的,所以要想办法在它前后做文章

    以下是解决思路:

    1. 在第1步,发送要启动的Activity信息给AMS之前,把这个Activity替换为一个在AndroidManifest中声明的StubActivity,这样就能绕过AMS的检查了。在替换的过程中,要把原来的Activity信息存放在Bundle中

    2. 在第5步,AMS通知App启动StubActivity时,我们自然不会启动StubActivity,而是在即将启动的时候,把StubActivity替换为原先的Activity。原先的Activity信息存放在Bundle中,取出来就好了

    有了解决思路之后,我们来思考具体实现方案:

    一:第一步

    第一步在代码中的具体表达其实就是:

    ActivityManagerNative.getDefault().startActivity();
    

    拦截startActivity方法,从参数中取出原有的Intent,替换为启动StubActivity的newIntent,同时把原有的Intent保存在newIntent中,这就是第一步的具体实现方案,说白了就是把假的信息保存进去,AMS取的时候,就让他取真的

    具体代码:

    辅助类:

    public class AMSHookHelper {
        public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        /**
        * Hook AMS
        * 主要完成的操作是 "把真正要启动的Activity临时替换为在AndroidManifest.xml
        * 的替身Activity",进而骗过AMS
        */
        public static void hookAMN() throws ClassNotFoundException,
                NoSuchMethodException, InvocationTargetException,
                IllegalAccessException, NoSuchFieldException {
    
            //获取AMN的gDefault单例gDefault,gDefault是final静态的\
            Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
    
            // gDefault是一个 android.util.Singleton<T>对象; 我们取出这个单例里面 mInstance字段
            Object mInstance = RefInvoke.getFieldObject("android.util.Singl
    
            // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段
            Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class<?>[] { classB2Interface },
                new MockClass1(mInstance));
    
            //把gDefault的mInstance字段,修改为proxy
            RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance");
        }
    }
    

    实现类代码:

    class MockClass1 implements InvocationHandler {
        private static final String TAG = "MockClass1";
        Object mBase;
        public MockClass1(Object base) {
        mBase = base;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) th
        Log.e("bao", method.getName());
    
    if ("startActivity".equals(method.getName())) { // 只拦截这个方法
    
    // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱 // 找到参数里面的第一个Intent 对象\
            Intent raw;
            int index = 0;
    
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                index = i;
                break; }
    
            }
            raw = (Intent) args[index];
            Intent newIntent = new Intent();
    
    // 替身Activity的包名, 也就是我们自己的包名
    String stubPackage = raw.getComponent().getPackageName();
    
    // 这里我们把启动的Activity临时替换为 StubActivity ComponentName componentName = new ComponentName(stubPackage newIntent.setComponent(componentName);
    
    // 把我们原始要启动的TargetActivity先存起来 newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);
    
    // 替换掉Intent, 达到欺骗AMS的目的 args[index] = newIntent;
            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
    
    }
        return method.invoke(mBase, args);
    }
    

    二:第五步:

    如果成功“欺骗了AMS”,AMS会通知App进程启动StubActivity,也就是第4步。我们没有权限修改AMS进程,只能修改第5步,把StubActivity再替换回TargetActivity

    来自:https://juejin.cn/post/7035773099303764004

    相关文章

      网友评论

          本文标题:Android插件化之Hook底层原理

          本文链接:https://www.haomeiwen.com/subject/uoaetdtx.html