前言
手把手講解系列文章,是我寫給各位看官,也是寫給我自己的。
文章可能過分詳細,但是這是為了幫助到盡量多的人,畢竟工作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 mH的hook核心代碼,和我自己的SDK 28的Activity mH的hook核心代碼進行了版本兼容設(shè)計.
正文大綱
1.整體思路
2.源碼索引
3.hook核心代碼
4. 最終效果
正文
提示:本文所有源碼索引圖,都基于SDK 28 -android9.0系統(tǒng).
1.整體思路
在之前Activity啟動流程的hook的Demo里,我進行了
Activity流程的hook,最終采用的方案,是Hook了AMS,實現(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看看,既然是合法性校驗,且看他是如何校驗的。
這是
Instrumentation的execStartActivity方法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),所以要手動,找到下圖中的方法,這個ClientTransactionHandler是ActivityThread的父類.
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: 這里
hookmh的手段,并不是針對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);
}
}
}
handler的dispatchMessage邏輯,是 先執(zhí)行mCallback的handlerMessage,然后根據(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
但是,最后我從mH的switch case EXECUTE_TRANSACTION分支,去debug(因為無法繼續(xù)往下查源碼)的時候,發(fā)現(xiàn) 原本塞進去的
ActivityResultItem的list,居然變成了LaunchActivityItem的list,而我居然查了半天,查不到是在源碼何處發(fā)生的變化.而 LaunchActivityItem 和 ActivityResultItem 他們兩個都是ClientTransaction的子類
public class LaunchActivityItem extends ClientTransactionItem public class ActivityResultItem extends ClientTransactionItememmmm...也是很尷尬。=_ =!
不過,最后能夠確定,從
mH的switch 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,其實,他就是!Intent的ComponentName成員:
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)給大家.


































