bootstrap 报错 验证插件_Activity插件化之Hook IActivityManager

一、前言:

前面几篇的插件化,基本上是基于接口“标准”的方式,将宿主的环境注入的插件apk中实现的插件化思想。

今天我们从Activity的启动流程的角度,寻找Hook点,实现我们占位式插件化。

二、基础知识储备:

根Activity的启动流程(应用程序进程的启动流程)、普通Activity的启动流程。后面会专门写几篇来介绍他们的启动流程。

(1)根Activity启动流程示意图如下:

849f7ac128d12f1e85a70c2764d6ade1.png

首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动之后,AMS会请求应用程序进程创建并创建根Activity。

(2)普通Activity启动流程示意图如下:

acec664d1c125fa7e8e5fcd0879127f2.png

在应用程序进程中的Activity向AMS请求创建普通Activity,AMS会对这个Activity的生命周期和栈进行管理,校验Activity等,如果Activity满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动普通Activity。

三、Hook IActivityManager方案的实现

我们知道,当我们启动一个Activity,这个Activity没有在AndroidManifest.xml中注册,就会报错。为什么会报错呢?从上面Activity的启动流程中,我们知道启动Activity会通过AMS进行校验,当发现Activity没有被注册在Activity中的时候,就会出现报错。所以我们的方案就是利用一个占位Activity,在ActivityManifest.xml中进行注册,然后利用这个占位Activity通过AMS验证,同时携带没有注册的Activity,方便后期恢复。然后在启动后期在ActivityThread中将目标Activity恢复过来,实现了我们的Hook式插件化。

(1)分析源码,寻找hook点

分析startActivity可知:

startActivity--->startActivityForResult--->mInstrumentation.execStartActivity

然后进入Instrumentation中查看execStartActivity方法,其中核心代码为:

 ActivityManager.getService().startActivity       源码基于Android8.0+     

ActivityManager.getService()是啥呢?

32f66561170037392b543682781e3994.png

发现返回的是返回了IActivityManager,还是个单例,是通过Singleton这个类返回的,那么上面的get方法,应该就是这个Singleton中提供的。进入查看,如下:

public abstract class Singleton<T> {    private T mInstance;    protected abstract T create();    public final T get() {        synchronized (this) {            if (mInstance == null) {                mInstance = create();            }            return mInstance;        }    }}

那么android8.0以下的这段代码是有一点不同的,我们以android7.0为例分析:

首先我们查看一下android7.0中Instrumentation中查看execStartActivity方法,核心代码如下:

public ActivityResult execStartActivity(            Context who, IBinder contextThread, IBinder token, Activity target,            Intent intent, int requestCode, Bundle options, UserHandle user) {       ....       ....       省略其他代码        try {         ...         ...               int result = ActivityManagerNative.getDefault()                .startActivityAsUser(whoThread, who.getBasePackageName(), intent,                        intent.resolveTypeIfNeeded(who.getContentResolver()),                        token, target != null ? target.mEmbeddedID : null,                        requestCode, 0, null, options, user.getIdentifier());         ....        } catch (RemoteException e) {            throw new RuntimeException("Failure from system", e);        }        return null;    }

从上面可以看出关键点:

ActivityManagerNative.getDefault() .startActivityAsUser

那么我们来接着查看ActivityManagerNative.getDefault(),如下:

   /**     * Retrieve the system's default/global activity manager.     */    static public IActivityManager getDefault() {        return gDefault.get();    }  private static final Singleton gDefault = new Singleton() {        protected IActivityManager create() {            IBinder b = ServiceManager.getService("activity");            if (false) {                Log.v("ActivityManager", "default service binder = " + b);            }            IActivityManager am = asInterface(b);            if (false) {                Log.v("ActivityManager", "default service = " + am);            }            return am;        }    };}

发现同样是通过Singleton返回IActivityManager的单例。

按照之前的hook经验:静态变量和单例都是比较好的Hook点

这里我们发现getDefault()是个静态方法,IActivityManager借助了Singleton类来实现单例,因此在android7.0版本以下IActivityManager是个比较好的Hook点。从上面分析Android8.0+的源码知道,也是IActivityManager借助了Singleton类来实现单例。所以这里IActivityManager是个比较好的Hook点。

(2)使用占位Activity通过AMS验证

a0d4ddf55824504f579ba0f67955226e.png

Hook点IActivityManager是一个接口,建议采用动态代理来实现。如下:

代码如下:

public class IActivityManagerProxy implements InvocationHandler {    private Object mActivityManager;    public IActivityManagerProxy(Object activityManager) {        mActivityManager = activityManager;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if ("startActivity".equals(method.getName())) {            int index = 0;            for (int i = 0; i < args.length; i++) {                if (args[i] instanceof Intent) {                    index = i;                    break;                }            }            //默认传递过来的没有注册的Activity的Intent            Intent intent = (Intent) args[index];            //创建一个新的Intent用来设置占位Activity和携带原来的Intent            Intent newInTent = new Intent();            String packageName = "com.zxh.hookplugin";            newInTent.setClassName(packageName, packageName + ".StubActivity");            //携带原来的Intent,方便复原的时候使用            newInTent.putExtra("target_intent", intent);            args[index] = newInTent;        }        return method.invoke(mActivityManager, args);    }}

这样我们启动的目标就变成了了StubActivity , 用来通过AMS校验,最后用代理类IIActivityManagerProxy   替换掉我们系统的IActivityManager。

从上面可以知道不管是android7.0还是8.0都是最终IActivityManager借助了Singleton类来实现单例。

05ce7cf29f97b94bdb5a576e5e353537.png

那么我们可以将我们的代理IActivityManagerProxy设置给mInstance。如下:

0a79cd5e9ef5c8b9bd97043cd75e0a38.png

mInstanceField.set方法有2个参数,第一个参数是所属的类的对象,也就是
Singleton对象,另外一个参数是IActivityManagerProxy对象。

那么这里有个版本兼容性问题,android7.0和8.0获取Singleton的方式不同。

在android7.0中,如下:

   /**     * Retrieve the system's default/global activity manager.     */    static public IActivityManager getDefault() {        return gDefault.get();    }  private static final Singleton gDefault = new Singleton() {        protected IActivityManager create() {            IBinder b = ServiceManager.getService("activity");            if (false) {                Log.v("ActivityManager", "default service binder = " + b);            }            IActivityManager am = asInterface(b);            if (false) {                Log.v("ActivityManager", "default service = " + am);            }            return am;        }    };}

从上面可以看出通过静态变量gDefault就是Singleton类的实例,于是我们在android7.0中可以通过以下方式获取Singleton的实例对象。代码如下:

 Class> mActivityManagerNativeClass =  Class.forName("android.app.ActivityManagerNative");   //获取ActivityManagerNative中的gDefault字段  Field gDefaultField =   mActivityManagerNativeClass.getDeclaredField("gDefault");  gDefaultField.setAccessible(true);  Object defaultSingleton = gDefaultField.get(null);

同理,在android8.0中要获取获取Singleton的实例对象,可以查看源码:

32f66561170037392b543682781e3994.png

从上图可以得知,只需要获取这个ActivityManager中的IActivityManagerSingleton字段即可。

获取代码如下:

 Class> mActivityManagerClass = Class.forName("android.app.ActivityManager");Field iActivityManagerSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton"); iActivityManagerSingletonField.setAccessible(true);   Object defaultSingleton = iActivityManagerSingletonField.get(null);

完整代码如下:

public class HookHelper {        public static void hookAMS() throws Exception{        try {              Object defaultSingleton=null;            if(Build.VERSION.SDK_INT>=26){//8.0                Class> mActivityManagerClass = Class.forName("android.app.ActivityManager");                Field iActivityManagerSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");                iActivityManagerSingletonField.setAccessible(true);                 defaultSingleton = iActivityManagerSingletonField.get(null);            }else {                Class> mActivityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");                //获取ActivityManagerNative中的gDefault字段                Field gDefaultField = mActivityManagerNativeClass.getDeclaredField("gDefault");                gDefaultField.setAccessible(true);                 defaultSingleton = gDefaultField.get(null);            }                        Class> mSingletonClass = Class.forName("android.util.Singleton");            Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");            mInstanceField.setAccessible(true);                //获取IActivityManager的实例            Object iActivityManager = mInstanceField.get(defaultSingleton);            Class> mIActivityManagerClass = Class.forName("android.app.IActivityManager");            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()                    , new Class[]{mIActivityManagerClass}, new IActivityManagerProxy(iActivityManager));            mInstanceField.set(defaultSingleton,proxy);        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }}

测试验证:

0459a6ff634f8e6d70841c073578ad21.png

da3b350902ace5897efcd37e5ae3f712.png

(3)还原插件中的Activity

上面我们用占位Activity通过AMS的验证,但是我们还是要启动插件Activity而不是占位Activity.

熟悉Activity启动流程的我们只会,Activity启动最终会调用到我们ActivityThread,通过H类将代码的逻辑切换到主线程中,H类如下:

d972662a8a37d9f3e00a786ea4ce8787.png

从上图可以发现H类重新了Handler的handleMessage方法,处理LAUNCH_ACTIVITY类型的消息,最终会调用到Activity的onCreate方法。那么我们在哪里替换呢。了解Handler机制的同学们都知道,我们消息轮询取出是通过我们ActivityThread中Looper.loop方法。查看一下核心源码:

    /**     * Run the message queue in this thread. Be sure to call     * {@link #quit()} to end the loop.     */    public static void loop() {        ....       //省略其他代码            try {                msg.target.dispatchMessage(msg);                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;            } finally {               ...               //省略其他代码            }           //省略其他代码    }

发现最终通过msg.target.dispatchMessage处理消息,msg.target就是Handler。那么我们进入Handler中查看dispatchMessage方法。如下:

37081684751eee12135da538e7c96f2c.png

从上面可以知道,当mCallback不为null的时候,就会执行

mCallback.handleMessage(msg)

因此,mCallback可以作为一个Hook点。我们可以通过自定义Callback来替换mCallback.自定义Callback如下:

public class HCallback implements Handler.Callback {    public static final int LAUNCH_ACTIVITY = 100;    private Handler mHandler;    public HCallback(Handler handler) {        mHandler = handler;    }    @Override    public boolean handleMessage(Message msg) {        if(msg.what==LAUNCH_ACTIVITY){            Object r = msg.obj;            try {                //得到消息中的Intent启动(启动SubActivity的intent)                Field intentField = r.getClass().getDeclaredField("intent"); //1                intentField.setAccessible(true);                Intent intent = (Intent) intentField.get(r);                                //得到此前保存起来的Intent(启动TargetActivity的intent)               Intent targetIntent= intent.getParcelableExtra("target_intent");               //将要启动的SubActivity的Intent替换为TargetActvity的Intent                intent.setComponent(targetIntent.getComponent());            } catch (Exception e) {                e.printStackTrace();            }        }        mHandler.handleMessage(msg);        return true;    }}

其中

 Field intentField = r.getClass().getDeclaredField("intent"); //1

这一行为什么是这样的呢。因为:

d0ce03f438e2bc4bf9ebce51dd120c61.png

1d87d06e0f05e2af2856f150d80af14a.png

是不是清楚了

接着我们替换Handler中的mCallback

  public static void hookHandler() throws Exception{        Class> activityThreadClass = Class.forName("android.app.ActivityThread");        Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");        sCurrentActivityThreadField.setAccessible(true);        Object activityThread = sCurrentActivityThreadField.get(null);        Field mHField = activityThreadClass.getDeclaredField("mH");        mHField.setAccessible(true);        Handler mHObj = (Handler) mHField.get(activityThread);        Class> handlerClass = Class.forName("android.os.Handler");        Field mCallbackField = handlerClass.getDeclaredField("mCallback");        mCallbackField.setAccessible(true);        mCallbackField.set(mHObj,new HCallback(mHObj));    }

9a30ce68ebde6f21e7d6821307f2140e.png

成功的实现了跳转目标activity.

虽然官方在不断的调整系统API,导致插件化的兼容性问题很多,但是这种插件化实现方式的思想是非常值得学习的,也是深入学习系统源码的一种方式。

源码:https://github.com/FOREVER001/Hook_Activity_plugin

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值