插件化(三)-啟動Activity

Android知識總結

一、啟動插件的四大組件

1.1、宿主啟動插件的Activity

Activity 是需要在清單文件中注冊的,顯然,插件的 Activity 沒有在宿主的清單文件中注冊,那我們如何來啟動它呢?

這里我們就需要使用 Hook 技術,來繞開系統的檢測。可能有些同學不知道 Hook 是什么,所以我們先簡單的介紹下 Hook 技術。

正常情況下對象A調用對象B,對象B處理后將數據返回給對象A,如下圖:

而加入Hook后的流程就變成了下圖形式:

Hook可以是一個方法或者一個對象,它就像一個鉤子一樣掛在對象B上面,當對象A調用對象B之前,Hook可以做一些處理,起到“欺上瞞下”的作用。而對象B就是我們常說的Hook點。為了保證Hook的穩定性,Hook點一般選擇容易找到并且不易變化的對象,如靜態變量和單例。

那么思路就來了,首先我們在宿主里面創建一個 ProxyActivity 繼承自 Activity,并且在清單中注冊。當啟動插件Activity 的時候,在系統檢測前,找到一個Hook點,然后通過 Hook 將插件 Activity 替換成 ProxyActivity,等到檢測完了后,再找一個Hook點,使用 Hook 將它們換回來,這樣就實現了插件 Activity 的啟動。思路是不是非常的簡單。

HOOK 啟動插件 Activity

通過 動態代理反射 實現 Hook。

查找Hook點的原則:

  • 盡量靜態變量或者單例對象。
  • 盡量 Hook public 的對象和方法。

從 HOOK 一直找到靜態的變量或者直接拿到類的對象,才說明 HOOK 成功。

1.2、Activity啟動流程

首先我們來看一張 Activity 啟動流程的簡單示意圖,如下:


Activity啟動流程

通過這張圖我們可以確定 Hook 點的大致位置。

  • 1、在進入 AMS 之前,找到一個 Hook 點,用來將插件 Activity 替換為 ProxyActivity。
  • 2、從 AMS 出來后,再找一個 Hook 點,用來將 ProxyActivity 替換為插件 Activity。

1.3、Activity啟動流程

圖1
圖2
圖3
API 28時序圖

詳情見:
Launch 啟動 Activity
Activity 啟動流程(一)
Activity 啟動流程(二)

二、HOOK 點的選擇

我們在項目中一般通過 startActivity(new Intent(this,PluginActivity.class)); 啟動 PluginActivity,如果我想換成啟動 ProxyActivity,調用方法 startActivity(new Intent(this,ProxyActivity.class)); 這樣就可以了。是不是已經知道答案了!!!沒錯,我們只要找到能夠修改 Intent 的地方,就可以作為 Hook 點,從這兒也可以看出 Hook 點并不是唯一的。

  • 一般我們選擇 Intent 作為 HOOK 點。

2.1、進入 AMS 之前HOOK 點

  • API30 源碼
// android/app/Instrumentation.java
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    // 這兒就是我們的 Hook 點,替換傳入 startActivity 方法中的 intent 參數
    try {
        intent.migrateExtraStreamToClipData(who);
        intent.prepareToLeaveProcess(who);
        int result = ActivityTaskManager.getService().startActivity(whoThread,
                who.getBasePackageName(), who.getAttributionTag(), 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;
}
  • API 28 源碼
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManager.getService()
            .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;
}

既然 Hook 點找到了,那我們怎么修改呢?
答案是動態代理,所以我們要生成一個代理對象,顯然,我們要代理的是 ActivityTaskManager.getService() 返回的對象。

  • API 30 源碼
// android/app/ActivityTaskManager.java
public static IActivityTaskManager getService() {
    return IActivityTaskManagerSingleton.get();
}

@UnsupportedAppUsage(trackingBug = 129726065)
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
        new Singleton<IActivityTaskManager>() {
            @Override
            protected IActivityTaskManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                return IActivityTaskManager.Stub.asInterface(b);
            }
        };
  • API 28 源碼
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

桶過上面的代碼,我們知道 IActivityManager 是調用的 Singleton 里面的 get 方法,所以下面我們再看下Singleton 是怎么樣的。

// android/util/Singleton
public abstract class Singleton<T> {

    @UnsupportedAppUsage
    public Singleton() {
    }

    @UnsupportedAppUsage
    private T mInstance;

    protected abstract T create();

    @UnsupportedAppUsage
    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

可以看出,IActivityManagerSingleton.get() 返回的實際上就是 mInstance 對象。所以接下來我們要替換的就是這個對象。

2.2、從 AMS 出來后HOOK點

替換回去會調用 Handler 的 handleMessage方法,所以下面我們看下 Handler 的源碼。

public void handleMessage(Message msg) {
}

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

當 mCallback != null 時,首先會執行mCallback.handleMessage(msg),再執行 handleMessage(msg),所以我們可以將 mCallback 作為 Hook 點,創建它。ok,現在問題就只剩一個了,就是找到含有 intent 的對象,沒辦法,只能接著看源碼。

  • API 26 源碼
// android/app/ActivityThread.java
public void handleMessage(Message msg) {
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
            r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
        } break;
    }
}

static final class ActivityClientRecord {
    Intent intent;
}

可以看到,在 ActivityClientRecord 類中,剛好就有個 intent,而且這個類的對象,我們也可以獲取到,就是msg.obj。

  • API 30源碼
// android/app/ActivityThread.H.java
case EXECUTE_TRANSACTION:
    final ClientTransaction transaction = (ClientTransaction) msg.obj;
    mTransactionExecutor.execute(transaction);
    if (isSystem()) {
        transaction.recycle();
    }
    break;
//android/app/servertransaction/ClientTransaction .java
public class ClientTransaction implements Parcelable, ObjectPoolItem {

    /** A list of individual callbacks to a client. */
    @UnsupportedAppUsage
    private List<ClientTransactionItem> mActivityCallbacks;
}
//android/app/servertransaction/TransactionExecutor.java
public class TransactionExecutor {
    public void execute(ClientTransaction transaction) {
        executeCallbacks(transaction);
        executeLifecycleState(transaction);
    }

    public void executeCallbacks(ClientTransaction transaction) {
        final int size = callbacks.size();
        for (int i = 0; i < size; ++i) {
            final ClientTransactionItem item = callbacks.get(i);
            if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
            final int postExecutionState = item.getPostExecutionState();
            final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
                    item.getPostExecutionState());
            if (closestPreExecutionState != UNDEFINED) {
                cycleToPath(r, closestPreExecutionState, transaction);
            }
            //HOOK 點
            item.execute(mTransactionHandler, token, mPendingActions);
            item.postExecute(mTransactionHandler, token, mPendingActions);
        }
    }
}
//android/app/servertransaction/LaunchActivityItem.java
public class LaunchActivityItem extends ClientTransactionItem {

    @UnsupportedAppUsage
    private Intent mIntent;

    @Override
    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        //HOOK 點
        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                mPendingResults, mPendingNewIntents, mIsForward,
                mProfilerInfo, client, mAssistToken, mFixedRotationAdjustments);
        client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }
}

LaunchActivityItem中我們就找到Intent了,可以在此利用 HOOK 技術進行替換。

創建 mCallback 替換系統的Intent,運用拿到 msg 從而可以拿到 msg.obj; 然后在替換 intent
--> ClientTransaction == msg.obj  
--> private List<ClientTransactionItem> mActivityCallbacks; 
--> ClientTransactionItem的子類
--> private Intent mIntent; 
--> LaunchActivityItem 對象 
--> private List<ClientTransactionItem> mActivityCallbacks; 
--> ClientTransaction == msg.obj 

三、源碼

  • 核心代碼
public class HookUtil {

    private static final String TARGET_INTENT = "target_intent";

    public static void hookAMSAidl(){
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P){
            hookIActivityTaskManager();
        }else{
            hookIActivityManager();
        }
    }

    public static void hookIActivityTaskManager(){
        try{
            Field singletonField = null;
            Class<?> actvityManager = Class.forName("android.app.ActivityTaskManager");
            singletonField = actvityManager.getDeclaredField("IActivityTaskManagerSingleton");
            singletonField.setAccessible(true);
            Object singleton = singletonField.get(null);
            //拿IActivityManager對象
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            //原始的IActivityTaskManager
            final Object IActivityTaskManager = mInstanceField.get(singleton);

            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
                    , new Class[]{Class.forName("android.app.IActivityTaskManager")}
                    , new InvocationHandler() {
                        @Override
                        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
//                            Log.i(TAG, "invoke: " + method.getName());

                            //偷梁換柱
                            //真正要啟動的activity目標
                            Intent raw = null;
                            int index = -1;
                            if ("startActivity".equals(method.getName())) {
                                for (int i = 0; i < args.length; i++) {
                                    if(args[i] instanceof  Intent){
                                        raw = (Intent)args[i];
                                        index = i;
                                    }
                                }
                                //代替的Intent
                                Intent newIntent = new Intent();
                                newIntent.setComponent(new ComponentName("com.example.designdemo", StubActivity.class.getName()));
                                newIntent.putExtra(TARGET_INTENT,raw);
                                args[index] = newIntent;
                            }

                            return method.invoke(IActivityTaskManager, args);
                        }
                    });

            //            7. IActivityManagerProxy 融入到framework
            mInstanceField.set(singleton, proxy);

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

    public static void hookIActivityManager() {
        try {
            // 獲取 singleton 對象
            Field singletonField = null;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // 小于8.0
                Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
                singletonField = clazz.getDeclaredField("gDefault");
            } else {
                Class<?> clazz = Class.forName("android.app.ActivityManager");
                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 {
                            // do something
                            // Intent的修改 -- 過濾
                            /**
                             * IActivityManager類的方法
                             * startActivity(whoThread, who.getBasePackageName(), intent,
                             *                         intent.resolveTypeIfNeeded(who.getContentResolver()),
                             *                         token, target != null ? target.mEmbeddedID : null,
                             *                         requestCode, 0, null, options)
                             */
                            // 過濾
                            if ("startActivity".equals(method.getName())) {
                                int index = -1;

                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        index = i;
                                        break;
                                    }
                                }
                                // 啟動插件的
                                Intent intent = (Intent) args[index];

                                Intent proxyIntent = new Intent();
                                proxyIntent.setComponent(new ComponentName("com.example.designdemo", StubActivity.class.getName()));

                                proxyIntent.putExtra(TARGET_INTENT, intent);

                                args[index] = proxyIntent;
                            }

                            // args  method需要的參數  --- 不改變原有的執行流程
                            // mInstance 系統的 IActivityManager 對象
                            return method.invoke(mInstance, args);
                        }
                    });

            // ActivityManager.getService() 替換成 proxyInstance
            mInstanceField.set(singleton, proxyInstance);

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


    public static void hookHandler() {
        try {
            // 獲取 ActivityThread 類的 Class 對象
            Class<?> clazz = Class.forName("android.app.ActivityThread");

            // 獲取 ActivityThread 對象
            Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
            activityThreadField.setAccessible(true);
            Object activityThread = activityThreadField.get(null);

            // 獲取 mH 對象
            Field mHField = clazz.getDeclaredField("mH");
            mHField.setAccessible(true);
            final Handler mH = (Handler) mHField.get(activityThread);

            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);

            // 創建的 callback
            Handler.Callback callback = new Handler.Callback() {

                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    // 通過msg  可以拿到 Intent,可以換回執行插件的Intent
                    // 找到 Intent的方便替換的地方  --- 在這個類里面 ActivityClientRecord --- Intent intent 非靜態
                    switch (msg.what) {
                        case 100: //API 26 以下
                            try {
                                // msg.obj == ActivityClientRecord
                                Field intentField = msg.obj.getClass().getDeclaredField("intent");
                                intentField.setAccessible(true);
                                // 啟動代理Intent
                                Intent proxyIntent = (Intent) intentField.get(msg.obj);
                                // 啟動插件的 Intent
                                Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                                if (intent != null) {
                                    intentField.set(msg.obj, intent);
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            break;
                        case 159: //API 30
                            try {
                                // msg.obj == ClientTransaction
                                // 獲取 mActivityCallbacks 對象
                                Field mActivityCallbacksField = msg.obj.getClass()
                                        .getDeclaredField("mActivityCallbacks");

                                mActivityCallbacksField.setAccessible(true);
                                List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);

                                for (int i = 0; i < mActivityCallbacks.size(); i++) {
                                    if (mActivityCallbacks.get(i).getClass().getName()
                                            .equals("android.app.servertransaction.LaunchActivityItem")) {
                                        Object launchActivityItem = mActivityCallbacks.get(i);

                                        // 獲取啟動代理的 Intent
                                        Field mIntentField = launchActivityItem.getClass()
                                                .getDeclaredField("mIntent");
                                        mIntentField.setAccessible(true);
                                        Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);

                                        // 目標 intent 替換 proxyIntent
                                        Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                                        if (intent != null) {
                                            mIntentField.set(launchActivityItem, intent);
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            break;
                    }
                    // 必須 return false
                    return false;
                }
            };

            // 替換系統的 callBack
            mCallbackField.set(mH, callback);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 初始化工具類
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LoadUtil.loadClass(this);

        HookUtil.hookAMS();
        HookUtil.hookHandler();
    }
}
  • 啟動Activity
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.enjoy.plugin",
        "com.enjoy.plugin.MainActivity"));
startActivity(intent);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容