插件化原理之hook系統函數
插件化主要問題之一是如startActivity一個未在註冊表裏面註冊的acitivity。
我們都知道開啓一個activity是涉及到app進程和系統服務進程的交互過程,其中驗證要打開的acitivity是否在清單文件中也是在系統服務進程進行的,那麼”如何”欺騙系統服務進程?
l 方案一是設置一個代理ProxyActivity,這個ProxyActivity在清單文件中註冊過,然後在該ProxyActivity裏面注入一個真實的Activity,ProxyActivity所有的生命週期方法裏面都回調真實的Activity生命週期方法。關於這個方案有個框架實現的挺好的,可以看動態代理框架。這個方案調用startyActivity是要特別處理,首先要把真實的activity信息隱藏在intent裏面,然後在代理ProxyActivity裏面在解析出真正的activity信息並且實例化,然後回調真正的activity生命週期方法。
github地址:https://github.com/singwhatiwanna/dynamic-load-apk
l 方案二 是直接hook app的startActivity方法,這裏先梳理一下startActivity的邏輯。startActivity雖然只有一行代碼,但裏面涉及的調用卻很複雜,app持有一個AMS的Binder引用,在app端最終調用ActivityManagerNative.getDefault().startActivity後,系統服務進程開始做一些準備處理,而ActivityManagerNative.getDefault()是個單例模式,我們可以在這裏傳遞一個註冊表裏面註冊過的activity過去,這樣系統服務進程驗證的時候就會通過,然後回調給ApplicationThreadNative,ApplicationThreadNative再通過一個內部類H發送消息到ActivityThread,這樣消息發送過來的時候我們再換回真實的activity,如此一來app就會認爲這個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說起 https://blog.csdn.net/mr_lu_/article/details/80137617
主要方法如 下
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);
DebugLog.d(o.toString());
//4獲取Singleton類信息
Class<?> singleton=Class.forName("android.util.Singleton");
//5 獲取mInstance 字段信息
Field field2= getField(singleton,"mInstance");
//6 獲取該第三步對象裏面的變量對象
Object mInstance=field2.get(o);
DebugLog.d(mInstance.toString());
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 {
if (method.getName().equals("startActivity")){
int index=-1;
DebugLog.d("invoke:"+method.getName());
for (Object o:args){
index++;
if (o instanceof Intent){
// intent.setComponent(componentName);
Intent oldIntent= (Intent) o;
//把原來的intent的跳轉類信息保存起來
// Intent intent=new Intent();
DebugLog.d("原來要跳轉的getAction:"+oldIntent.getAction());
DebugLog.d("原來要跳轉的getPackage:"+oldIntent.getPackage());
DebugLog.d("原來要跳轉的Component():"+(oldIntent.getComponent()==null?"null":oldIntent.getComponent().toString()));
//替換
ComponentName componentName=new ComponentName(context,TestAidlActivity.class);
oldIntent.putExtra("realComponentName",oldIntent.getComponent());
oldIntent.setComponent(componentName);
break;
}
}
// if (index!=-1){
// args[index]=
// }
}
return method.invoke(target,args);
}
}
我們自己的callback
Handler.Callback myCallback=new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what==100){
DebugLog.d("handleMessage");
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){
DebugLog.d("AppCompatActivity............");
isReplaceOncreate=true;
}
//重新替換過來
intent.setComponent(componentName);
} catch (Exception e) {
e.printStackTrace();
}
}
Manifest.xml信息如下
TestAidi2Activity我並沒有在清單文件中註冊。運行後打印信息如下
所有代碼上次到github:https://github.com/helloworld777/hello-jni