背景:
前面我们通过梳理了Activity启动的整个流程知道:Activity的启动主要经历如下几个阶段:
- 发起Activity启动
- 与AMS建立通信节点,主要是在Instrumentation类中获取AMS的Binder代理,准备通信。
- 由应用进程切到系统进程(system_service)的AMS服务,由AMS判断应用进程是否创建,如果没有创建则AMS与Zegote进程通信创建应用进程。
- 应用进程创建后,启动应用进程,再由应用进程切到AMS服务,AMS进一步做启动Activity的校验(如:Activity是否注册等)。
- 启动Activity,在ActivityThread的Handler中执行Activity生命周期
我们都知道正常情况下要启动一个Activity必须要在AndroidManifest文件中注册这个Activity。那么有没有办法不注册也能够正常启动呢?
通过阅读源码,我们知道AMS的工作之一就是服务Activity的启动管理。在这个过程中会对要启动的Activity进行一系列的检验,就比如这里的Activity是否注册就是AMS检验的一环。而要做到不注册就启动就 必须骗过AMS的检验。所以方案就是通过Hook AMS和ActivityThread中的Handler来实现。
一、Hook AMS
要想骗过AMS的启动检验,那就必须在AMS的StartActivity检验之前拦截并替换Intent对象中所指向的目标Activity为已经注册的代理Activity。这样AMS在检验的时候检验的就是代理Activity。从而达到骗过AMS检验的目的。
经过上一节的流程梳理,我们可以通过Hook获取AMS通信节点来达到拦截AMS的startActivity方法,在检验之前替换Intent的目的
public static void hookAms(){
try {
// 获取Singleton对象
Class<?> clazz = Class.forName("android.app.ActivityManager");
Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
// 获取IActivityManager 对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// IActivityManager 的方法执行的时候,都会先跑这儿
Log.d(TAG,"methodName="+method.getName());
if ("startActivity".equals(method.getName())) {
// 替换Intent
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 启动插件的intent
Intent intent = (Intent) args[index];
Intent proxyIntent = new Intent();
//这里的packageName要填Activity的主工程的包名,也就是应用包名,而不是Activity类的包名
proxyIntent.setClassName("com.single.code.app.plugin",
"com.single.code.app.pluginlib.PluginProxyActivity");
// 保存原来的
proxyIntent.putExtra(TARGET_INTENT, intent);
Log.e(TAG, "startActivity: proxyIntent intent ="+proxyIntent);
Log.e(TAG, "startActivity: target intent ="+intent);
args[index] = proxyIntent;
}
return method.invoke(mInstance, args);
}
});
// 替换系统的 IActivityManager对象
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
二、Hook Handler
通过Hook AMS来替换Intent达到骗过AMS的Activity启动检验后,我们还需要在Activity真正被启动之前再把Intent替换成真实的目标Activity,否则就会发现启动的就是我们的代理Activity了。同样通过梳理Activity启动流程我们知道,Activity在经过AMS的一系列检验之后最终在ActivityThread的Handler中执行启动并执行生命周期。
所以我们要做的就是在Activity启动执行生命周期之前再次把Intent替换回来即可。而ActivityThread的mH属性就是个完美的Hook点。
public static void hookHandler(){
try {
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object activityThread = sCurrentActivityThreadField.get(null);
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(activityThread);
// new 一个 Callback 替换系统的 mCallback对象
Class<?> handlerClass = Class.forName("android.os.Handler");
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
// 将Intent换回来
Log.e(TAG, "handleMessage:"+msg);
switch (msg.what) {
case 100:
try {
// 获取ActivityClientRecord中的intent对象
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(msg.obj);
// 拿到插件的Intent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if(intent != null){
Log.e(TAG, "handleMessage: " + intent);
// 替换回来
proxyIntent.setComponent(intent.getComponent());
}
} catch (Exception e) {
e.printStackTrace();
}
break;
case 159:
try {
Class<?> clazz = Class.forName("android.app.servertransaction.ClientTransaction");
Field mActivityCallbacksField = clazz.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List activityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < activityCallbacks.size(); i++) {
if (activityCallbacks.get(i).getClass().getName()
.equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = activityCallbacks.get(i);
Field mIntentField = launchActivityItem.getClass().getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
Log.e(TAG, "handleMessage: proxyIntent intent ="+proxyIntent);
//插件的intent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
Log.e(TAG, "handleMessage: target intent ="+intent);
if (intent != null) {
mIntentField.set(launchActivityItem, intent);
}
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
这里的Hook只针对Android10一下源码。所以Android10及以上需要自行去适配。可能有人疑惑这不是脱裤子放屁吗?你直接去注册不叫好了,干嘛搞这么一套。搞这么一套除了装13之外,还是因为在学习插件化是发现这也是插件化的必备知识之一。
这里贴一下GitHub的Demo:https://github.com/279154451/SAppPlugin
有兴趣的同学可以clone下来跑一下看看