Android插件化系列第(一)篇---Hook技術(shù)之Activity的啟動過程攔截

這篇文章主要講解如何利用動態(tài)代理技術(shù)Hook掉系統(tǒng)的AMS服務(wù),來實(shí)現(xiàn)攔截Activity的啟動流程,這種hook原理方式來自DroidPlugin。代碼量不是很多,為了更容易的理解,需要掌握J(rèn)AVA的反射,動態(tài)代理技術(shù),以及Activity的啟動流程。

1、尋找Hook點(diǎn)的原則

Android中主要是依靠分析系統(tǒng)源碼類來做到的,首先我們得找到被Hook的對象,我稱之為Hook點(diǎn);什么樣的對象比較好Hook呢?一般來說,靜態(tài)變量和單例變量是相對不容易改變,是一個比較好的hook點(diǎn),而普通的對象有易變的可能,每個版本都不一樣,處理難度比較大。我們根據(jù)這個原則找到所謂的Hook點(diǎn)。

2、尋找Hook點(diǎn)

通常點(diǎn)擊一個Button就開始Activity跳轉(zhuǎn)了,這中間發(fā)生了什么,我們?nèi)绾蜨ook,來實(shí)現(xiàn)Activity啟動的攔截呢?

    public void start(View view) {
        Intent intent = new Intent(this, OtherActivity.class);
        startActivity(intent);
    }

我們的目的是要攔截startActivity方法,跟蹤源碼,發(fā)現(xiàn)最后啟動Activity是由Instrumentation類的execStartActivity做到的。其實(shí)這個類相當(dāng)于啟動Activity的中間者,啟動Activity中間都是由它來操作的

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        ....
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            
        //通過ActivityManagerNative.getDefault()獲取一個對象,開始啟動新的Activity
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
                        
                        
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

對于ActivityManagerNative這個東東,熟悉Activity/Service啟動過程的都不陌生

public abstract class ActivityManagerNative extends Binder implements IActivityManager

繼承了Binder,實(shí)現(xiàn)了一個IActivityManager接口,這就是為了遠(yuǎn)程服務(wù)通信做準(zhǔn)備的"Stub"類,一個完整的AID L有兩部分,一個是個跟服務(wù)端通信的Stub,一個是跟客戶端通信的Proxy。ActivityManagerNative就是Stub,閱讀源碼發(fā)現(xiàn)在ActivityManagerNative 文件中還有個ActivityManagerProxy,這里就多不扯了。

static public IActivityManager getDefault() {
    return gDefault.get();
}

ActivityManagerNative.getDefault()獲取的是一個IActivityManager對象,由IActivityManager去啟動Activity,IActivityManager的實(shí)現(xiàn)類是ActivityManagerService,ActivityManagerService是在另外一個進(jìn)程之中,所有Activity 啟動是一個跨進(jìn)程的通信的過程,所以真正啟動Activity的是通過遠(yuǎn)端服務(wù)ActivityManagerService來啟動的。

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        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;
        }

其實(shí)gDefalut借助Singleton實(shí)現(xiàn)的單例模式,而在內(nèi)部可以看到先從ServiceManager中獲取到AMS遠(yuǎn)端服務(wù)的Binder對象,然后使用asInterface方法轉(zhuǎn)化成本地化對象,我們目的是攔截startActivity,所以改變IActivityManager對象可以做到這個一點(diǎn),這里gDefault又是靜態(tài)的,根據(jù)Hook原則,這是一個比較好的Hook點(diǎn)。

3、Hook掉startActivity,輸出日志

我們先實(shí)現(xiàn)一個小需求,啟動Activity的時候打印一條日志,寫一個工具類HookUtil。

public class HookUtil {

    private Class<?> proxyActivity;

    private Context context;

    public HookUtil(Class<?> proxyActivity, Context context) {
        this.proxyActivity = proxyActivity;
        this.context = context;
    }

    public void hookAms() {
        
        //一路反射,直到拿到IActivityManager的對象
        try {
            Class<?> ActivityManagerNativeClss = Class.forName("android.app.ActivityManagerNative");
            Field defaultFiled = ActivityManagerNativeClss.getDeclaredField("gDefault");
            defaultFiled.setAccessible(true);
            Object defaultValue = defaultFiled.get(null);
            //反射SingleTon
            Class<?> SingletonClass = Class.forName("android.util.Singleton");
            Field mInstance = SingletonClass.getDeclaredField("mInstance");
            mInstance.setAccessible(true);
            //到這里已經(jīng)拿到ActivityManager對象
            Object iActivityManagerObject = mInstance.get(defaultValue);
            
            
            //開始動態(tài)代理,用代理對象替換掉真實(shí)的ActivityManager,瞞天過海
            Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");

            AmsInvocationHandler handler = new AmsInvocationHandler(iActivityManagerObject);

            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{IActivityManagerIntercept}, handler);

            //現(xiàn)在替換掉這個對象
            mInstance.set(defaultValue, proxy);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private class AmsInvocationHandler implements InvocationHandler {

        private Object iActivityManagerObject;

        private AmsInvocationHandler(Object iActivityManagerObject) {
            this.iActivityManagerObject = iActivityManagerObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            Log.i("HookUtil", method.getName());
            //我要在這里搞點(diǎn)事情
            if ("startActivity".contains(method.getName())) {
                Log.e("HookUtil","Activity已經(jīng)開始啟動");
                Log.e("HookUtil","小弟到此一游!?。?);
            }
            return method.invoke(iActivityManagerObject, args);
        }
    }
}

結(jié)合注釋應(yīng)該很容易看懂,在Application中配置一下

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        HookUtil hookUtil=new HookUtil(SecondActivity.class, this);
        hookUtil.hookAms();
    }
}

看看執(zhí)行結(jié)果:


可以看到,我們成功的Hook掉了startActivity,輸出了一條日志。有了上面的基礎(chǔ),現(xiàn)在我們開始來點(diǎn)有用的東西,Activity不用在清單文件中注冊,就可以啟動起來,這個怎么搞呢?

4、無需注冊,啟動Activity

如下,TargetActivity沒有在清單文件中注冊,怎么去啟動TargetActivity?

   public void start(View view) {
        Intent intent = new Intent(this, TargetActivity.class);
        startActivity(intent);
    }

這個思路可以是這樣,上面已經(jīng)攔截了啟動Activity流程,在invoke中我們可以得到啟動參數(shù)intent信息,那么就在這里,我們可以自己構(gòu)造一個假的Activity信息的intent,這個Intent啟動的Activity是在清單文件中注冊的,當(dāng)真正啟動的時候(ActivityManagerService校驗(yàn)清單文件之后),用真實(shí)的Intent把代理的Intent在調(diào)換過來,然后啟動即可。

首先獲取真實(shí)啟動參數(shù)intent信息

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("startActivity".contains(method.getName())) {
                //換掉
                Intent intent = null;
                int index = 0;
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    if (arg instanceof Intent) {
                        //說明找到了startActivity的Intent參數(shù)
                        intent = (Intent) args[i];
                        //這個意圖是不能被啟動的,因?yàn)锳citivity沒有在清單文件中注冊
                        index = i;
                    }
                }

               //偽造一個代理的Intent,代理Intent啟動的是proxyActivity
                Intent proxyIntent = new Intent();
                ComponentName componentName = new ComponentName(context, proxyActivity);
                proxyIntent.setComponent(componentName);
                proxyIntent.putExtra("oldIntent", intent);
                args[index] = proxyIntent;
            }

            return method.invoke(iActivityManagerObject, args);
        }

有了上面的兩個步驟,這個代理的Intent是可以通過ActivityManagerService檢驗(yàn)的,因?yàn)槲以谇鍐挝募凶赃^

      <activity android:name=".ProxyActivity" />

為了不啟動ProxyActivity,現(xiàn)在我們需要找一個合適的時機(jī),把真實(shí)的Intent換過了來,啟動我們真正想啟動的Activity??催^Activity的啟動流程的朋友,我們都知道這個過程是由Handler發(fā)送消息來實(shí)現(xiàn)的,可是通過Handler處理消息的代碼來看,消息的分發(fā)處理是有順序的,下面是Handler處理消息的代碼:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

handler處理消息的時候,首先去檢查是否實(shí)現(xiàn)了callback接口,如果有實(shí)現(xiàn)的話,那么會直接執(zhí)行接口方法,然后才是handleMessage方法,最后才是執(zhí)行重寫的handleMessage方法,我們一般大部分時候都是重寫了handleMessage方法,而ActivityThread主線程用的正是重寫的方法,這種方法的優(yōu)先級是最低的,我們完全可以實(shí)現(xiàn)接口來替換掉系統(tǒng)Handler的處理過程。這里詳見Android源碼解析Handler系列第(一)篇 --- Message全局池

    public void hookSystemHandler() {
        try {
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            //獲取主線程對象
            Object activityThread = currentActivityThreadMethod.invoke(null);
            //獲取mH字段
            Field mH = activityThreadClass.getDeclaredField("mH");
            mH.setAccessible(true);
            //獲取Handler
            Handler handler = (Handler) mH.get(activityThread);
            //獲取原始的mCallBack字段
            Field mCallBack = Handler.class.getDeclaredField("mCallback");
            mCallBack.setAccessible(true);
            //這里設(shè)置了我們自己實(shí)現(xiàn)了接口的CallBack對象
            mCallBack.set(handler, new ActivityThreadHandlerCallback(handler)) ;
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

自定義Callback類

 private class ActivityThreadHandlerCallback implements Handler.Callback {
        
        private Handler handler;

        private ActivityThreadHandlerCallback(Handler handler) {
            this.handler = handler;
        }

        @Override
        public boolean handleMessage(Message msg) {
            Log.i("HookAmsUtil", "handleMessage");
            //替換之前的Intent
            if (msg.what ==100) {
                Log.i("HookAmsUtil","lauchActivity");
                handleLauchActivity(msg);
            }

            handler.handleMessage(msg);
            return true;
        }

        private void handleLauchActivity(Message msg) {
            Object obj = msg.obj;//ActivityClientRecord
            try{
                Field intentField = obj.getClass().getDeclaredField("intent");
                intentField.setAccessible(true);
                Intent proxyInent = (Intent) intentField.get(obj);
                Intent realIntent = proxyInent.getParcelableExtra("oldIntent");
                if (realIntent != null) {
                    proxyInent.setComponent(realIntent.getComponent());
                }
            }catch (Exception e){
                Log.i("HookAmsUtil","lauchActivity falied");
            }

        }
    }

最后在application中注入

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //這個ProxyActivity在清單文件中注冊過,以后所有的Activitiy都可以用ProxyActivity無需聲明,繞過監(jiān)測
        HookAmsUtil hookAmsUtil = new HookAmsUtil(ProxyActivity.class, this);
        hookAmsUtil.hookSystemHandler();
        hookAmsUtil.hookAms();
    }
}

執(zhí)行,點(diǎn)擊MainActivity中的按鈕成功跳轉(zhuǎn)到了TargetActivity。這是插件化系列博客的第一篇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容