插件化原理之hook系统函数
插件化主要问题之一是如何startActivity
一个未在注册表里面注册的acitivity
。
我们都知道开启一个activity是涉及到app进程和系统服务进程的交互过程,其中验证要打开的acitivity是否在清单文件中也是在系统服务进程进行的,那么”如何”欺骗系统服务进程?
方案一是设置一个代理ProxyActivity
,这个ProxyActivity
在清单文件中注册过,然后在该ProxyActivity
里面注入一个真实的Activity
,ProxyActivity
所有的生命周期方法里面都回调真实的Activity
生命周期方法。
关于这个方案有个框架实现的挺好的,可以看动态代理DL框架。这个方案调用startyActivity
是要特别处理,首先要把真实的activity
信息隐藏在intent
里面,然后在代理ProxyActivity
里面在解析出真正的activity
信息并且实例化,然后回调真正的activity
生命周期方法。
DL 的github地址:dynamic-load-apk
方案二 是直接hook app
的startActivity
方法
这里先梳理一下startActivity
的逻辑。startActivity
虽然只有一行代码,但里面涉及的调用却很复杂,app持有一个AMS
的Binder
引用,在app端最终调用ActivityManagerNative.getDefault().startActivity
后,系统服务进程开始做一些准备处理,而ActivityManagerNative.getDefault()
是个单例模式,我们可以在这里传递一个注册表里面注册过的activity
过去,这样系统服务进程验证的时候就会通过,然后回调给ApplicationThreadNative
,ApplicationThreadNative
再通过一个内部类H
发送消息到ActivityThread
,这样消息发送过来的时候我们再换回真实的activity
,如此一来系统就会认为这个activity
已经被系统验证过了,生命周期的调用和其他acitivity
的生命周期方法调用过程一模一样。查看startActivity
调用流程时,发现ActivityManagerNative.getDefault()
是个单例,我们可以反射重新设置我们自己的代理对象,ActivityThread
里面所有AMS
发送过来的消息都会通过内部类H
发送到主线程,查看Handler
的源码我们发现,Hander
在处理消息的方法是会先查看内部一个 变量Callback
是否存在,如果存在则先处理Callback
的方法,
Handler
的dispatchMessage
方法源码,我们通常是复写handleMessage
,如果我们要拦截哪个方法,我们看一看设置Handler
的Callback
并且设置handleMessage
返回true
,这样就总不会走Handler
的handleMessage
方法了。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们可以在ActivityThread
的H
里面的mCallback
里面拦截startActivity
发出来的消息,然后在这里把真实的activity
替换。
本文讲解的是第二种方案,当然这种方案要对startActivity
具体流程和动态代理有个清楚的认识,不清楚的可以查看我的另两篇博客
startActivity方法解读
设计模式之代理模式2
主要方法如 下
public void hookSystem() throws Exception{
// 1 加载ActivityManagerNative类信息
Class<?> activityManagerNative=Class.forName("android.app.ActivityManagerNative");
//2 获取gDefault字段属性
Field field= getField(activityManagerNative,"gDefault");
//3 获取Singleton 对象
Object o= field.get(null);
//4获取Singleton类信息
Class<?> singleton=Class.forName("android.util.Singleton");
//5 获取mInstance 字段信息
Field field2= getField(singleton,"mInstance");
//6 获取该第三步对象里面的变量对象
Object mInstance=field2.get(o);
MyInvocationHandler myInvocationHandler=new MyInvocationHandler(mInstance);
//7 生成代理类
Object proxy= Proxy.newProxyInstance(mInstance.getClass().getClassLoader(),mInstance.getClass().getInterfaces(),myInvocationHandler);
//8 替换Singleton类里面的mInstance属性
field2.set(o,proxy);
//9获取ActivityThread类信息
activityThreadClass=Class.forName("android.app.ActivityThread");
//10获取mH字段信息,该变量是个Handler对象
Field mHField=getField(activityThreadClass,"mH");
//11 ActivityThread类里面有个唯一对象,就是sCurrentActivityThread属性
Field sCurrentActivityThread=getField(activityThreadClass,"sCurrentActivityThread");
//12 获取到ActivityThread对象
activityThread=sCurrentActivityThread.get(null);
//13 获取ActivityThread对象 mH对象
Object mH= mHField.get(activityThread);
//14 获取Handler类里面的mCallback字段信息
Field field1=getField(Handler.class,"mCallback");
//15 设置mH对面里面的callback为我们自己写的callback
field1.set(mH,myCallback);
}
第二步我们就获取到ActivityManagerNative
类里面的gDefault属性了
由于该变量是个static
所以第三步我们调用get
传入null
参数就可以获取该对象了,然后通过动态代理生成一个代理对象,由于代理对象的方法都会调用我们设置的MyInvocationHandler
对象的invoke
方法,故我们可以在这里拦截startActivity
方法,我们把真实的activity
随便替换一个在注册表里面注册过的activity
信息传输到AMS那里。
查看Singleton
源代码我们可以知道,该get
方法返回的就是create
方法创建的对象,也是内部的一个变量,我们只要把这个对象替换成我们的对象就可以了。
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;
}
}
}
到第八步的时候我们已经把ActivityManagerNative
里面的gDefault
变量里面的mInstance
变量换成我们自己的代理对象,这样AcitivityManagerNative.getDefault
对象也就是我们的代理对象了。
12步的时候我们已经获取到当前ActivityThread
类的唯一对象,这个和我们在自定义Application
返回Application
实例一样。
13步的我们已经获取到ActivityThread
里面mH
这个变量
15步的时候我们已经把ActivityThread
里面的mH对象里面的mCallback
设置成我们自己的callback
,这样AMS
发送给APP
的消息,我们这里都能进行拦截。
MyInvocationHandler
类代码如下
这里我们拦截了APP的startActivity
方法,把要开启的Activity
信息保存起来,替换一个新注册过的Activity
信息放在里面
class MyInvocationHandler implements InvocationHandler {
Object target;
public MyInvocationHandler(Object o){
this.target=o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//拦截startActivity方法
if (method.getName().equals("startActivity")){
for (Object o:args){
if (o instanceof Intent){
// intent.setComponent(componentName);
Intent oldIntent= (Intent) o;
//把原来的intent的跳转类信息保存起来 //
Intent intent=new Intent();
//替换一个在清单文件中存在的Activity类
ComponentName componentName=new ComponentName(context,TestAidlActivity.class);
oldIntent.putExtra("realComponentName",oldIntent.getComponent());
oldIntent.setComponent(componentName);
break;
}
}
}
return method.invoke(target,args);
}
}
我们注入H
里面的callback
Handler.Callback myCallback=new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what==100){
handleStartActivity(msg);
}
return false;
};
};
查看H源码我们知道开启一个activity
消息是100.所以我们这里处理msg.what==100
的情况,看下面知道所有的生命周期回调都有对应的消息,我们都能进行拦截处理。目前我们暂时处理开启activity
的消息
这个方法就好理解了,获取msg
里面的intent
对象,然后把里面的Component
设置成我们保持的那个Component
信息
private void handleStartActivity(Message msg){
Object activityClientRecord= msg.obj;
try {
isReplaceOncreate=false;
Field field=getField(activityClientRecord.getClass(),"intent");
//拿到intent对象。
Intent intent= (Intent) field.get(activityClientRecord);
ComponentName componentName=intent.getParcelableExtra("realComponentName");
String className=componentName.getClassName();
Class<?> clazz=Class.forName(className);
if (clazz.newInstance() instanceof AppCompatActivity){
isReplaceOncreate=true;
}
//重新替换过来
intent.setComponent(componentName);
} catch (Exception e) {
e.printStackTrace();
}
}
Manifest.xml信息如下
图片6.png
TestAidi2Activity我并没有在清单文件中注册。运行后打印信息如下
图片7.png
所有代码上传到github
网友评论