手把手講解 Android Hook-實現(xiàn)無清單啟動Activity

前言

手把手講解系列文章,是我寫給各位看官,也是寫給我自己的。
文章可能過分詳細,但是這是為了幫助到盡量多的人,畢竟工作5,6年,不能老吸血,也到了回饋開源的時候.
這個系列的文章:
1、用通俗易懂的講解方式,講解一門技術(shù)的實用價值
2、詳細書寫源碼的追蹤,源碼截圖,繪制類的結(jié)構(gòu)圖,盡量詳細地解釋原理的探索過程
3、提供Github 的 可運行的Demo工程,但是我所提供代碼,更多是提供思路,拋磚引玉,請酌情cv
4、集合整理原理探索過程中的一些坑,或者demo的運行過程中的注意事項
5、用gif圖,最直觀地展示demo運行效果

如果覺得細節(jié)太細,直接跳過看結(jié)論即可。
本人能力有限,如若發(fā)現(xiàn)描述不當(dāng)之處,歡迎留言批評指正。

學(xué)到老活到老,路漫漫其修遠兮。與眾君共勉 !


引子

前面兩篇Hook博文,寫了兩個demo,一個是hook入門,一個是略微復(fù)雜的Activity啟動流程的hook。

那么玩點更高端的吧, 正常開發(fā)中,所有Activity都要在 AndroidManifest,xml中進行注冊,才可以正常跳轉(zhuǎn),通過hook,可以繞過系統(tǒng)對activity注冊的檢測,即使不注冊,也可以正常跳轉(zhuǎn)。

鳴謝

感謝大神的博文 http://www.itdecent.cn/p/eb4121b037e2
本文中的所有內(nèi)容,在這位大神的文章中基本都有提及,只是大佬寫的東西我看了老久才理解,所以想按照自己的理解寫出一篇更通俗易懂的攻略.
另外,大佬使用SDK 27進行hook開發(fā),我則是使用SDK 28,所以我的最終Demo中,copy了大佬博文里面的Activity mHhook核心代碼,和我自己的SDK 28Activity mHhook核心代碼進行了版本兼容設(shè)計.


正文大綱

1.整體思路
2.源碼索引
3.hook核心代碼
4. 最終效果

正文

提示:本文所有源碼索引圖,都基于SDK 28 -android9.0系統(tǒng).


1.整體思路

在之前Activity啟動流程的hook的Demo里,我進行了Activity流程的hook,最終采用的方案,是HookAMS,實現(xiàn)了全局的startActivity動作的劫持. 現(xiàn)在就從這個AMS的hook為起點,來實現(xiàn)無清單啟動Activity.

Activity啟動流程的hook的Demo里,最后實現(xiàn)的效果是,每次跳轉(zhuǎn)Activity,都能看到這個日志:

image.png

那么,我們既然偵測到了startActivity這個方法的調(diào)用,那么自然就可以拿到里面的實參,比如,Intent
Intent是跳轉(zhuǎn)意圖,從哪里來,跳到哪里去的信息,都包含在Intent里面.
而,manifest Activity的檢測,也是要根據(jù)Intent里面的信息來的.

所以,要騙過系統(tǒng),要假裝我們跳的Activity是已經(jīng)注冊過的,那么只需要將Intent里面的信息換成 已經(jīng)在manifest中注冊的某個Activity就可以了(這里可能就有人像抬杠了,你怎么知道m(xù)anifest里面一定有注冊Activity....如果一個Activity都沒有,你的app是怎么啟動的呢,至少得有一個LauncherActivity吧 - -!).

確定思路:
1.在AMS的hook函數(shù)中,將 真實的Intent中的信息,替換成manifest中已有的Activity信息. 騙過系統(tǒng)的檢測機制。
2.雖然騙過了系統(tǒng)的檢測機制,但是這么一來,每一次的跳轉(zhuǎn),都會跳到"假"Activity,這肯定不是我們想要的效果,那么就必須,在真正的跳轉(zhuǎn)時機之前,將 真實的Activity信息,還原回去, 跳到原本該去的Activity.

對應(yīng)的核心代碼,其實也就兩句話:

image.png


2.源碼索引

下圖大致畫出了:從 Activity.startActivity動作開始,到最終 跳轉(zhuǎn)動作的最終執(zhí)行者 全過程.

方案1(改).png

下面開始看源碼,從Activity.startActivity開始:

image.png

image.png

image.png

image.png

這里開始分支:if(mParent==null),但是兩個分支最終執(zhí)行如下:

true分支

false分支

很顯然,兩者都是同樣的調(diào)用過程:

Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(....);
mMainThread.sendActivityResult(...);

第一句,execStartActivity 是 對一些參數(shù)的合法性校驗,如果不合法,那就會直接拋出異常,比如之前的

image.png

第二句,sendActivityResult才是真正的跳轉(zhuǎn)動作執(zhí)行者

先進入第一句Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity看看,既然是合法性校驗,且看他是如何校驗的。

這是InstrumentationexecStartActivity方法

image.png

結(jié)論:它是通過AMS去校驗的,AMS startActivity會返回一個int數(shù)值,隨后,checkStartActivityResult方法會根據(jù)這個int值,拋出響應(yīng)的異常,或者什么都不做.
image.png

再進入第二句mMainThread.sendActivityResult看真正的跳轉(zhuǎn)動作是如何執(zhí)行的:
ps:這里其實有個訣竅,既然我們的終極目標(biāo)是要騙過系統(tǒng)的Activity Intent檢測,那么,跟著Intent這個變量,就不會偏離方向.

image.png

既然intent被封裝到了ClientTransaction,交給了mAppThread,那么繼續(xù):

image.png

前方有坑,請注意:androidStudio里并不能直接跳轉(zhuǎn),所以要手動,找到下圖中的方法,這個ClientTransactionHandlerActivityThread的父類.
image.png

上圖中,調(diào)用了sendMessage(int,Object),在ActivityThread中找到這個方法的實現(xiàn):
image.png

找它的最終實現(xiàn):
image.png

找到另一個關(guān)鍵點:mH ,
image.png

H類的定義:(太長了,我就不完整截圖了,留下關(guān)鍵的信息)

final H mH = new H();
class H extends Handler {
        ...
        public static final int EXECUTE_TRANSACTION = 159;

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
                    case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case EXECUTE_TRANSACTION:
                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    mTransactionExecutor.execute(transaction);
                    if (isSystem()) {
                        // Client transactions inside system process are recycled on the client side
                        // instead of ClientLifecycleManager to avoid being cleared before this
                        // message is handled.
                        transaction.recycle();
                    }
                    // TODO(lifecycler): Recycle locally scheduled transactions.
                    break;
                    ...
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
    }

很明顯,他就是一個Handler的普通子類,定義了主線程ActivityThread中可能發(fā)生的各種事件。
PS: 這里,我留下了case EXECUTE_TRANSACTION:分支,是因為,之前ClientTransactionHandler 抽象類里面,sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);,就是用的這個 EXECUTE_TRANSACTION常量。
終于找到了startActivity的最終執(zhí)行代碼!

final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);

Ok,就到這里了.(事實上,我本來還想往下追查,Intent被封裝到ClientTransaction之后,又被得到了什么樣的處理,最后發(fā)現(xiàn)居然查到了一個源碼中都不存在的類,我表示看不懂了,就到這里吧,不影響我們hook)


3.hook核心代碼

還記得我們的整體思路么?

1.在AMS的hook函數(shù)中,將 真實的Intent中的信息,替換成manifest中已有的Activity信息. 騙過系統(tǒng)的檢測機制。
2.雖然騙過了系統(tǒng)的檢測機制,但是這么一來,每一次的跳轉(zhuǎn),都會跳到"假"Activity,這肯定不是我們想要的效果,那么就必須,在真正的跳轉(zhuǎn)時機之前,將 真實的Activity信息,還原回去, 跳到原本該去的Activity.

說通俗一點就是,
第一,偽造一個Intent,騙過Activity Manifest檢測。
第二,真正要跳轉(zhuǎn)之前,把原始的Intent還原回去.

開始擼代碼,大量反射代碼即將到來,注釋應(yīng)該很詳盡了,特別注意看反射代碼要對照源代碼來看,不然很容易走神

偽造intent,騙過Activity Manifest檢測

這里,請對照:ActivityManager.java 的 4125-4137行

hook對照源代碼.png

hook核心代碼如下

    /**
     * 這里對AMS進行hook
     *
     * @param context
     */
    private static void hookAMS(Context context) {
        try {
            Class<?> ActivityManagerClz;
            final Object IActivityManagerObj;//這個就是AMS實例
            Method getServiceMethod;
            Field IActivityManagerSingletonField;
            if (ifSdkOverIncluding26()) {//26,27,28的ams獲取方式是通過ActivityManager.getService()
                ActivityManagerClz = Class.forName("android.app.ActivityManager");
                getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");
                IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");//單例類成員的名字也不一樣
            } else {//25往下,是ActivityManagerNative.getDefault()
                ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");
                getServiceMethod = ActivityManagerClz.getDeclaredMethod("getDefault");
                IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("gDefault");//單例類成員的名字也不一樣
            }
            IActivityManagerObj = getServiceMethod.invoke(null);//OK,已經(jīng)取得這個系統(tǒng)自己的AMS實例

            // 2.現(xiàn)在創(chuàng)建我們的AMS實例
            // 由于IActivityManager是一個接口,那么其實我們可以使用Proxy類來進行代理對象的創(chuàng)建
            // 結(jié)果被擺了一道,IActivityManager這玩意居然還是個AIDL,動態(tài)生成的類,編譯器還不認識這個類,怎么辦?反射咯
            Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");

            // 構(gòu)建代理類需要兩個東西用于創(chuàng)建偽裝的Intent
            String packageName = Util.getPMName(context);
            String clz = Util.getHostClzName(context, packageName);
            Object proxyIActivityManager =
                    Proxy.newProxyInstance(
                            Thread.currentThread().getContextClassLoader(),
                            new Class[]{IActivityManagerClz},
                            new ProxyInvocation(IActivityManagerObj, packageName, clz));

            //3.拿到AMS實例,然后用代理的AMS換掉真正的AMS,代理的AMS則是用 假的Intent騙過了 activity manifest檢測.
            //偷梁換柱
            IActivityManagerSingletonField.setAccessible(true);
            Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
            Class<?> SingletonClz = Class.forName("android.util.Singleton");//反射創(chuàng)建一個Singleton的class
            Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);

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

    private static final String ORI_INTENT_TAG = "origin_intent";
    /**
     * 把InvocationHandler的實現(xiàn)類提取出來,因為這里包含了核心技術(shù)邏輯,最好獨立,方便維護
     */
    private static class ProxyInvocation implements InvocationHandler {

        Object amsObj;
        String packageName;//這兩個String是用來構(gòu)建Intent的ComponentName的
        String clz;

        public ProxyInvocation(Object amsInstance, String packageName, String clz) {
            this.amsObj = amsInstance;
            this.packageName = packageName;
            this.clz = clz;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //proxy是創(chuàng)建出來的代理類,method是接口中的方法,args是接口執(zhí)行時的實參
            if (method.getName().equals("startActivity")) {
                Log.d("GlobalActivityHook", "全局hook 到了 startActivity");

                Intent currentRealIntent = null;//偵測到startActivity動作之后,把intent存到這里
                int intentIndex = -1;
                //遍歷參數(shù),找到Intent
                for (int i = 0; i < args.length; i++) {
                    Object temp = args[i];
                    if (temp instanceof Intent) {
                        currentRealIntent = (Intent) temp;//這是原始的Intent,存起來,后面用得著
                        intentIndex = i;
                        break;
                    }
                }

                //構(gòu)造自己的Intent,這是為了繞過manifest檢測(這個Intent是偽造的!只是為了讓通過manifest檢測)
                Intent proxyIntent = new Intent();
                ComponentName componentName = new ComponentName(packageName, clz);//用ComponentName重新創(chuàng)建一個intent
                proxyIntent.setComponent(componentName);
                proxyIntent.putExtra(ORI_INTENT_TAG, currentRealIntent);//將真正的proxy作為參數(shù),存放到extras中,后面會拿出來還原

                args[intentIndex] = proxyIntent;//替換掉intent
                //喲,已經(jīng)成功繞過了manifest清單檢測. 那么,我不能老讓它跳到 偽裝的Activity啊,我要給他還原回去,那么,去哪里還原呢?
                //繼續(xù)看源碼。

            }
            return method.invoke(amsObj, args);
        }
    }
真正要跳轉(zhuǎn)之前,把原始的Intent還原回去

PS: 這里hook mh的手段,并不是針對 mh本身做代理,而是對mh的mCallback成員.
因為:

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

handlerdispatchMessage邏輯,是 先執(zhí)行mCallbackhandlerMessage,然后根據(jù)它的返回值決定要不要執(zhí)行handler本身的handlerMessage函數(shù).
我們的目的是還原Intent,并不需要對ActivityThread原本的mH做出邏輯修改,所以,hook mCallback,加入還原Intent的邏輯,即可.

這次hook,對照的源碼是(源碼太長了,我就直接截取了ActivityThread里面一些關(guān)鍵的代碼):

image.png

下面是Hook Mh的完整代碼:

    //下面進行ActivityThread的mH的hook,這是針對SDK28做的hook
    private static void hookActivityThread_mH_After28() {

        try {
            //確定hook點,ActivityThread類的mh
            // 先拿到ActivityThread
            Class<?> ActivityThreadClz = Class.forName("android.app.ActivityThread");
            Field field = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object ActivityThreadObj = field.get(null);//OK,拿到主線程實例

            //現(xiàn)在拿mH
            Field mHField = ActivityThreadClz.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mHObj = (Handler) mHField.get(ActivityThreadObj);//ok,當(dāng)前的mH拿到了
            //再拿它的mCallback成員
            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);

            //2.現(xiàn)在,造一個代理mH,
            // 他就是一個簡單的Handler子類
            ProxyHandlerCallback proxyMHCallback = new ProxyHandlerCallback();//錯,不需要重寫全部mH,只需要對mH的callback進行重新定義

            //3.替換
            //將Handler的mCallback成員,替換成創(chuàng)建出來的代理HandlerCallback
            mCallbackField.set(mHObj, proxyMHCallback);

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

    private static class ProxyHandlerCallback implements Handler.Callback {

        private int EXECUTE_TRANSACTION = 159;//這個值,是android.app.ActivityThread的內(nèi)部類H 中定義的常量EXECUTE_TRANSACTION

        @Override
        public boolean handleMessage(Message msg) {
            boolean result = false;//返回值,請看Handler的源碼,dispatchMessage就會懂了
            //Handler的dispatchMessage有3個callback優(yōu)先級,首先是msg自帶的callback,其次是Handler的成員mCallback,最后才是Handler類自身的handlerMessage方法,
            //它成員mCallback.handleMessage的返回值為true,則不會繼續(xù)往下執(zhí)行 Handler.handlerMessage
            //我們這里只是要hook,插入邏輯,所以必須返回false,讓Handler原本的handlerMessage能夠執(zhí)行.
            if (msg.what == EXECUTE_TRANSACTION) {//這是跳轉(zhuǎn)的時候,要對intent進行還原
                try {
                    //先把相關(guān)@hide的類都建好
                    Class<?> ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
                    Class<?> LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");

                    Field mActivityCallbacksField = ClientTransactionClz.getDeclaredField("mActivityCallbacks");//ClientTransaction的成員
                    mActivityCallbacksField.setAccessible(true);
                    //類型判定,好習(xí)慣
                    if (!ClientTransactionClz.isInstance(msg.obj)) return true;
                    Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);//根據(jù)源碼,在這個分支里面,msg.obj就是 ClientTransaction類型,所以,直接用
                    //拿到了ClientTransaction的List<ClientTransactionItem> mActivityCallbacks;
                    List list = (List) mActivityCallbacksObj;

                    if (list.size() == 0) return true;
                    Object LaunchActivityItemObj = list.get(0);//所以這里直接就拿到第一個就好了

                    if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;
                    //這里必須判定 LaunchActivityItemClz,
                    // 因為 最初的ActivityResultItem傳進去之后都被轉(zhuǎn)化成了這LaunchActivityItemClz的實例

                    Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
                    mIntentField.setAccessible(true);
                    Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);
                    Intent oriIntent = (Intent) mIntent.getExtras().get(ORI_INTENT_TAG);
                    //那么現(xiàn)在有了最原始的intent,應(yīng)該怎么處理呢?
                    Log.d("1", "2");
                    mIntentField.set(LaunchActivityItemObj, oriIntent);
                    return result;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return result;
        }
    }

PS:這里有個坑(請看上面 if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;, 我為什么要加這個判斷?因為,我通過debug,發(fā)現(xiàn),從mH里面的msg.what得到的ClientTransaction,它有這么一個成員List<ClientTransactionItem> mActivityCallbacks; 注意看,從list里面拿到的ClientTransactionItem 的實際類型是:LaunchActivityItem.

之前我索引源碼的時候,追查Intent的去向,只知道它最后被封裝成了一個ClientTransaction

image.png

image.png

image.png

但是,最后我從mHswitch case EXECUTE_TRANSACTION分支,去debug(因為無法繼續(xù)往下查源碼)的時候,

發(fā)現(xiàn) 原本塞進去的ActivityResultItemlist,居然變成了LaunchActivityItemlist,而我居然查了半天,查不到是在源碼何處發(fā)生的變化.

而 LaunchActivityItem 和 ActivityResultItem 他們兩個都是ClientTransaction的子類

public class LaunchActivityItem extends ClientTransactionItem 
public class ActivityResultItem extends ClientTransactionItem

emmmm...也是很尷尬。=_ =!

不過,最后能夠確定,從mHswitch case EXECUTE_TRANSACTION分支得到的transaction,就是包含了Intent的包裝對象,所以只需要解析這個對象,就可以拿到intent,進行還原.


OK,大功告成,安裝好 android 9.0 SDK 28的模擬器,啟動起來,運行程序,看看能不能無清單跳轉(zhuǎn):

結(jié)果,臉一黑:報錯!***

一份大禮:
2019-02-27 18:20:12.287 28253-28253/study.hank.com.activityhookdemo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: study.hank.com.activityhookdemo, PID: 28253
    java.lang.RuntimeException: Unable to start activity ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)
        at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
        at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61)
        at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
        at study.hank.com.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
     Caused by: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:240)
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:219)
        at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155) 
        at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61) 
        at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72) 
        at study.hank.com.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14) 
        at android.app.Activity.performCreate(Activity.java:7136) 
        at android.app.Activity.performCreate(Activity.java:7127) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 

提取關(guān)鍵信息:

Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)

居然找不到包?
問題出在:
NavUtils.getParentActivityName

還是被谷歌擺了一道,查原因啊,進去NavUtils.getParentActivityName()去看看:

image.png

看來就是這里報的錯,繼續(xù):

image.png

找到可疑點:
image.png

可能就是這里拋出的異常,繼續(xù):
image.png

然而,它是一個接口,那么就找它的實現(xiàn)類(注意,如果這個接口涉及到隱藏@hide的類,你用ctrl+T是不能找到的,不過也有辦法,回到NavUtil.java):
image.png

哦,原來pm對象是來自context,既然提到了context這個抽象類,它的很多抽象方法的實現(xiàn)都在ContextImpl,手動進入ContextImpl:找這個方法:
image.png

這個pm對象原來是來自ActivityThread,然后進行了一次封裝,最后返回出去的是一個ApplicationPackageManager對象.
那就進入主線程咯.
image.png

看看IPackageManager的內(nèi)容:它是AIDL動態(tài)生成的接口,用androidStudio是看不到接口內(nèi)容的,只能到源碼官網(wǎng),查到的接口如下:

interface IPackageManager {
   ...
  ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
}

Ok,看到IBinder,就知道應(yīng)該無法繼續(xù)往下追查了,已經(jīng)跨進程了.
前面提到了,從主線程拿到的pm,被封裝成了ApplicationPackageManager,那么,進入它里面去找:getActivityInfo方法:

image.png

原來異常是這里拋出的,當(dāng)mPm.getActivityInfo為空的時候,才會拋出.
OK,就查到這里,得出結(jié)論:
源碼,其實對Activity的合法性進行了兩次檢測,一次是在AMS,一次是在這里的PMS,前面的AMS,我們用一個已有的Activity偽裝了一下,通過了驗證,那么這里的PMS,我們也可以采用同樣的方式.
注:上圖的參數(shù)ComponentName className,其實,他就是!IntentComponentName成員:
image.png

懂了吧··這里對intent又進行了一次檢查,檢查的就是這個ComponentName.

接下來用同樣的方式對PMS的檢測進行hook,讓它不再報異常.
此次hook的參照的源碼是:

image.png

hook核心代碼如下(對sPackageManager進行代理替換,讓代理類檢查的永遠是合法的Activity):

private static void hookPMAfter28(Context context) throws ClassNotFoundException,
            NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
            InvocationTargetException {
        String pmName = Util.getPMName(context);
        String hostClzName = Util.getHostClzName(context, pmName);

        Class<?> forName = Class.forName("android.app.ActivityThread");//PM居然是來自ActivityThread
        Field field = forName.getDeclaredField("sCurrentActivityThread");
        field.setAccessible(true);
        Object activityThread = field.get(null);
        Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
        Object iPackageManager = getPackageManager.invoke(activityThread);

        String packageName = Util.getPMName(context);
        PMSInvocationHandler handler = new PMSInvocationHandler(iPackageManager, packageName, hostClzName);
        Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
                Class<?>[]{iPackageManagerIntercept}, handler);
        // 獲取 sPackageManager 屬性
        Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
        iPackageManagerField.setAccessible(true);
        iPackageManagerField.set(activityThread, proxy);
    }

    static class PMSInvocationHandler implements InvocationHandler {

        private Object base;
        private String packageName;
        private String hostClzName;

        public PMSInvocationHandler(Object base, String packageName, String hostClzName) {
            this.packageName = packageName;
            this.base = base;
            this.hostClzName = hostClzName;
        }

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

            if (method.getName().equals("getActivityInfo")) {
                ComponentName componentName = new ComponentName(packageName, hostClzName);
                return method.invoke(base, componentName, PackageManager.GET_META_DATA, 0);//破費,一定是這樣
            }

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

4.最終效果

ok,見證奇跡的時候到了,準(zhǔn)備好SDK28 -android 9.0虛擬機,運行demo

無清單注冊啟動Activity

上圖中有3個Activity跳轉(zhuǎn),但是我們看demo的清單文件,只有一個launchActivity
image.png

大功告成~喜歡的大佬,現(xiàn)在就可以下載demo了.

結(jié)語

又是歷時3天,無清單注冊,啟動Activity也完成。結(jié)果是美好的,然而,過程,是糾結(jié)的,但是也是值得的.
完成了3篇博文,自己對Hook技術(shù)也有了更深層次的認識:
所謂hook,套路是簡單的,就是

1.找到hook點(比如上面,3次hook,一次是系統(tǒng)的AMS,一次是 ActivityThread的mH,還有一次是,ActivityThread的sPackageManager,注意,這里的sPackageManager的hook,我只針對了SDK28-9.0設(shè)備進行了hook,在其他版本的設(shè)備上運行可能會出現(xiàn)其他問題,比如,Intent中的參數(shù)傳遞不正常等.)
2.用合適的方式創(chuàng)建代理對象,通常 要hook一個系統(tǒng)類,就用繼承的方式,重寫某方法。hook一個系統(tǒng)接口的實現(xiàn)類,那就用JDK的Proxy動態(tài)代理
3.最后用代理對象,反射set,替換被hook的對象.

套路并不難,掌握好反射,以及代理模式,就行了.
真正難的是哪里? 是 源碼的閱讀能力,還有寫出兼容性Hook核心代碼的能力!androidSDK 有很多版本迭代,現(xiàn)在最新的是SDK 28,我們要保證我們的hook代碼能夠兼容所有的系統(tǒng)版本,就需要大量閱讀源碼,確保萬無一失,比如上面,如果不是在SDK 28-android9.0的模擬器上運行發(fā)現(xiàn)報異常,我根本就不會去做最后一次的 hook.

歐了,hook從入門,到實踐,3篇博文,全都通俗易懂,喜歡的大佬,點個贊,就是對我最大的鼓勵,以后還有干貨,也會用這種方式,呈現(xiàn)給大家.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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