一、前言:
前面几篇的插件化,基本上是基于接口“标准”的方式,将宿主的环境注入的插件apk中实现的插件化思想。
今天我们从Activity的启动流程的角度,寻找Hook点,实现我们占位式插件化。
二、基础知识储备:
根Activity的启动流程(应用程序进程的启动流程)、普通Activity的启动流程。后面会专门写几篇来介绍他们的启动流程。
(1)根Activity启动流程示意图如下:
首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动之后,AMS会请求应用程序进程创建并创建根Activity。
(2)普通Activity启动流程示意图如下:
在应用程序进程中的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()是啥呢?
发现返回的是返回了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验证
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类来实现单例。
那么我们可以将我们的代理IActivityManagerProxy设置给mInstance。如下:
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的实例对象,可以查看源码:
从上图可以得知,只需要获取这个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(); } }}
测试验证:
(3)还原插件中的Activity
上面我们用占位Activity通过AMS的验证,但是我们还是要启动插件Activity而不是占位Activity.
熟悉Activity启动流程的我们只会,Activity启动最终会调用到我们ActivityThread,通过H类将代码的逻辑切换到主线程中,H类如下:
从上图可以发现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方法。如下:
从上面可以知道,当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
这一行为什么是这样的呢。因为:
是不是清楚了
接着我们替换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)); }
成功的实现了跳转目标activity.
虽然官方在不断的调整系统API,导致插件化的兼容性问题很多,但是这种插件化实现方式的思想是非常值得学习的,也是深入学习系统源码的一种方式。
源码:https://github.com/FOREVER001/Hook_Activity_plugin