android 全局hook_【Hook】实现无清单启动Activity
引子
Hook技術(shù)在android開(kāi)發(fā)領(lǐng)域算是一項(xiàng)黑科技,那么一個(gè)新的概念進(jìn)入視線,我們最關(guān)心的3個(gè)問(wèn)題就是,它是什么,有什么用,怎么用
本系列將由淺入深 手把手講解這三大問(wèn)題。
本文是第三篇, 高級(jí)篇。
前面兩篇Hook博文,寫了兩個(gè)demo,一個(gè)是hook入門,一個(gè)是略微復(fù)雜的Activity啟動(dòng)流程的hook。
那么玩點(diǎn)更高端的吧, 正常開(kāi)發(fā)中,所有 Activity都要在 AndroidManifest.xml中進(jìn)行注冊(cè),才可以正常跳轉(zhuǎn),通過(guò) hook,可以繞過(guò)系統(tǒng)對(duì) activity注冊(cè)的檢測(cè),即使不注冊(cè),也可以正常跳轉(zhuǎn)。
鳴謝
感謝大神的博文 https://www.jianshu.com/p/eb4121b037e2
正文大綱
一.整體思路
二.源碼索引
三.hook核心代碼
四.最終效果
Demo地址:
https://github.com/18598925736/ActivityHookDemo/tree/startActivityWithoutRegiste
正文
提示:本文所有源碼索引圖,都基于 SDK28-android9.0系統(tǒng).
一.整體思路
在之前Activity啟動(dòng)流程的hook的Demo里,我進(jìn)行了 Activity流程的 hook,最終采用的方案,是 Hook了 AMS,實(shí)現(xiàn)了全局的 startActivity動(dòng)作的 劫持. 現(xiàn)在就從這個(gè)AMS的 hook為起點(diǎn),來(lái)實(shí)現(xiàn) 無(wú)清單啟動(dòng)Activity.
在 Activity啟動(dòng)流程的 hook的Demo里,最后實(shí)現(xiàn)的效果是,每次跳轉(zhuǎn) Activity,都能看到這個(gè)日志:
那么,我們既然偵測(cè)到了 startActivity這個(gè)方法的調(diào)用,那么自然就可以拿到里面的實(shí)參,比如, Intent。Intent是跳轉(zhuǎn)意圖,從哪里來(lái),跳到哪里去的信息,都包含在 Intent里面.而, manifestActivity的檢測(cè),也是要根據(jù) Intent里面的信息來(lái)的.所以,要騙過(guò)系統(tǒng),要假裝我們跳的 Activity是已經(jīng)注冊(cè)過(guò)的,那么只需要將 Intent里面的信息換成 已經(jīng)在 manifest中注冊(cè)的某個(gè) Activity就可以了( 這里可能就有人像抬杠了,你怎么知道m(xù)anifest里面一定有注冊(cè)Activity....如果一個(gè)Activity都沒(méi)有,你的app是怎么啟動(dòng)的呢,至少得有一個(gè)LauncherActivity吧--!).
確定思路: 1.在AMS的hook函數(shù)中,將 真實(shí)的Intent中的信息,替換成manifest中已有的Activity信息. 騙過(guò)系統(tǒng)的檢測(cè)機(jī)制。2.雖然騙過(guò)了系統(tǒng)的檢測(cè)機(jī)制,但是這么一來(lái),每一次的跳轉(zhuǎn),都會(huì)跳到 "假"的 Activity,這肯定不是我們想要的效果,那么就必須,在真正的跳轉(zhuǎn)時(shí)機(jī)之前,將 真實(shí)的Activity信息,還原回去, 跳到原本該去的 Activity.
對(duì)應(yīng)的核心代碼,其實(shí)也就兩句話:
二.源碼索引
下圖大致畫出了:從 Activity.startActivity動(dòng)作開(kāi)始,到最終 跳轉(zhuǎn)動(dòng)作的最終執(zhí)行者 全過(guò)程.
下面開(kāi)始看源碼,從 Activity.startActivity開(kāi)始:
這里開(kāi)始分支:if(mParent==null),但是兩個(gè)分支最終執(zhí)行如下:
很顯然,兩者都是同樣的調(diào)用過(guò)程:
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(....);
mMainThread.sendActivityResult(...);
第一句, execStartActivity 是 對(duì)一些參數(shù)的合法性校驗(yàn),如果不合法,那就會(huì)直接拋出異常,比如之前的 第二句, sendActivityResult才是真正的 跳轉(zhuǎn)動(dòng)作執(zhí)行者
先進(jìn)入第一句Instrumentation.ActivityResultar=mInstrumentation.execStartActivity看看,既然是合法性校驗(yàn),且看他是如何校驗(yàn)的。
這是 Instrumentation的 execStartActivity方法結(jié)論:它是通過(guò)AMS去校驗(yàn)的,AMS startActivity會(huì)返回一個(gè)int數(shù)值,隨后, checkStartActivityResult方法會(huì)根據(jù)這個(gè) int值,拋出響應(yīng)的異常,或者什么都不做.
再進(jìn)入第二句 mMainThread.sendActivityResult看真正的跳轉(zhuǎn)動(dòng)作是如何執(zhí)行的:ps:這里其實(shí)有個(gè)訣竅,既然我們的終極目標(biāo)是要騙過(guò)系統(tǒng)的Activity Intent檢測(cè),那么,跟著Intent這個(gè)變量,就不會(huì)偏離方向.
既然intent被封裝到了 ClientTransaction,交給了 mAppThread,那么繼續(xù):
前方有坑,請(qǐng)注意:androidStudio里并不能直接跳轉(zhuǎn),所以要手動(dòng),找到下圖中的方法,這個(gè) ClientTransactionHandler是 ActivityThread的父類.上圖中,調(diào)用了 sendMessage(int,Object),在 ActivityThread中找到這個(gè)方法的實(shí)現(xiàn):找它的最終實(shí)現(xiàn):找到另一個(gè)關(guān)鍵點(diǎn):mH ,H類的定義:(太長(zhǎng)了,我就不完整截圖了,留下關(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));
}
}
很明顯,他就是一個(gè) Handler的普通子類,定義了主線程 ActivityThread中可能發(fā)生的各種事件。PS: 這里,我留下了 caseEXECUTE_TRANSACTION:分支,是因?yàn)?#xff0c;之前 ClientTransactionHandler 抽象類里面, sendMessage(ActivityThread.H.EXECUTE_TRANSACTION,transaction);,就是用的這個(gè) EXECUTE_TRANSACTION常量。終于找到了 startActivity的最終執(zhí)行代碼!
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
Ok,就到這里了.(事實(shí)上,我本來(lái)還想往下追查, Intent被封裝到 ClientTransaction之后,又被得到了什么樣的處理,最后發(fā)現(xiàn)居然查到了一個(gè)源碼中都不存在的類,我表示看不懂了,就到這里吧,不影響我們 hook)
三.hook核心代碼
還記得我們的整體思路么?
1.在AMS的hook函數(shù)中,將 真實(shí)的Intent中的信息,替換成manifest中已有的Activity信息. 騙過(guò)系統(tǒng)的檢測(cè)機(jī)制。2.雖然騙過(guò)了系統(tǒng)的檢測(cè)機(jī)制,但是這么一來(lái),每一次的跳轉(zhuǎn),都會(huì)跳到 "假"的 Activity,這肯定不是我們想要的效果,那么就必須,在真正的跳轉(zhuǎn)時(shí)機(jī)之前,將 真實(shí)的Activity信息,還原回去, 跳到原本該去的 Activity.
說(shuō)通俗一點(diǎn)就是,第一,偽造一個(gè) Intent,騙過(guò) ActivityManifest檢測(cè)。第二,真正要跳轉(zhuǎn)之前,把原始的Intent還原回去.開(kāi)始擼代碼,大量 反射代碼即將到來(lái),注釋應(yīng)該很詳盡了,特別注意:看反射代碼要對(duì)照源代碼來(lái)看,不然很容易走神:
偽造intent,騙過(guò)Activity Manifest檢測(cè)
這里,請(qǐng)對(duì)照:ActivityManager.java的4125-4137行hook核心代碼如下
/**
* 這里對(duì)AMS進(jìn)行hook
*
* @param context
*/
private static void hookAMS(Context context) {
try {
Class> ActivityManagerClz;
final Object IActivityManagerObj;//這個(gè)就是AMS實(shí)例
Method getServiceMethod;
Field IActivityManagerSingletonField;
if (ifSdkOverIncluding26()) {//26,27,28的ams獲取方式是通過(guò)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)取得這個(gè)系統(tǒng)自己的AMS實(shí)例
// 2.現(xiàn)在創(chuàng)建我們的AMS實(shí)例
// 由于IActivityManager是一個(gè)接口,那么其實(shí)我們可以使用Proxy類來(lái)進(jìn)行代理對(duì)象的創(chuàng)建
// 結(jié)果被擺了一道,IActivityManager這玩意居然還是個(gè)AIDL,動(dòng)態(tài)生成的類,編譯器還不認(rèn)識(shí)這個(gè)類,怎么辦?反射咯
Class> IActivityManagerClz = Class.forName("android.app.IActivityManager");
// 構(gòu)建代理類需要兩個(gè)東西用于創(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實(shí)例,然后用代理的AMS換掉真正的AMS,代理的AMS則是用 假的Intent騙過(guò)了 activity manifest檢測(cè).
//偷梁換柱
IActivityManagerSingletonField.setAccessible(true);
Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
Class> SingletonClz = Class.forName("android.util.Singleton");//反射創(chuàng)建一個(gè)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的實(shí)現(xiàn)類提取出來(lái),因?yàn)檫@里包含了核心技術(shù)邏輯,最好獨(dú)立,方便維護(hù)
*/
private static class ProxyInvocation implements InvocationHandler {
Object amsObj;
String packageName;//這兩個(gè)String是用來(lái)構(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)建出來(lái)的代理類,method是接口中的方法,args是接口執(zhí)行時(shí)的實(shí)參
if (method.getName().equals("startActivity")) {
Log.d("GlobalActivityHook", "全局hook 到了 startActivity");
Intent currentRealIntent = null;//偵測(cè)到startActivity動(dòng)作之后,把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,存起來(lái),后面用得著
intentIndex = i;
break;
}
}
//構(gòu)造自己的Intent,這是為了繞過(guò)manifest檢測(cè)(這個(gè)Intent是偽造的!只是為了讓通過(guò)manifest檢測(cè))
Intent proxyIntent = new Intent();
ComponentName componentName = new ComponentName(packageName, clz);//用ComponentName重新創(chuàng)建一個(gè)intent
proxyIntent.setComponent(componentName);
proxyIntent.putExtra(ORI_INTENT_TAG, currentRealIntent);//將真正的proxy作為參數(shù),存放到extras中,后面會(huì)拿出來(lái)還原
args[intentIndex] = proxyIntent;//替換掉intent
//喲,已經(jīng)成功繞過(guò)了manifest清單檢測(cè). 那么,我不能老讓它跳到 偽裝的Activity啊,我要給他還原回去,那么,去哪里還原呢?
//繼續(xù)看源碼。
}
return method.invoke(amsObj, args);
}
}
真正要跳轉(zhuǎn)之前,把原始的Intent還原回去
PS: 這里 hook mh的手段,并不是針對(duì) mh本身做代理,而是對(duì)mh的mCallback成員. 因?yàn)?#xff1a;
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,并不需要對(duì)ActivityThread原本的mH做出邏輯修改,所以, hook mCallback,加入還原 Intent的邏輯,即可.
這次hook,對(duì)照的源碼是(源碼太長(zhǎng)了,我就直接截取了ActivityThread里面一些關(guān)鍵的代碼):
下面是 HookMh的完整代碼:
//下面進(jìn)行ActivityThread的mH的hook,這是針對(duì)SDK28做的hook
private static void hookActivityThread_mH_After28() {
try {
//確定hook點(diǎn),ActivityThread類的mh
// 先拿到ActivityThread
Class> ActivityThreadClz = Class.forName("android.app.ActivityThread");
Field field = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object ActivityThreadObj = field.get(null);//OK,拿到主線程實(shí)例
//現(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)在,造一個(gè)代理mH,
// 他就是一個(gè)簡(jiǎn)單的Handler子類
ProxyHandlerCallback proxyMHCallback = new ProxyHandlerCallback();//錯(cuò),不需要重寫全部mH,只需要對(duì)mH的callback進(jìn)行重新定義
//3.替換
//將Handler的mCallback成員,替換成創(chuàng)建出來(lái)的代理HandlerCallback
mCallbackField.set(mHObj, proxyMHCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
private static class ProxyHandlerCallback implements Handler.Callback {
private int EXECUTE_TRANSACTION = 159;//這個(gè)值,是android.app.ActivityThread的內(nèi)部類H 中定義的常量EXECUTE_TRANSACTION
@Override
public boolean handleMessage(Message msg) {
boolean result = false;//返回值,請(qǐng)看Handler的源碼,dispatchMessage就會(huì)懂了
//Handler的dispatchMessage有3個(gè)callback優(yōu)先級(jí),首先是msg自帶的callback,其次是Handler的成員mCallback,最后才是Handler類自身的handlerMessage方法,
//它成員mCallback.handleMessage的返回值為true,則不會(huì)繼續(xù)往下執(zhí)行 Handler.handlerMessage
//我們這里只是要hook,插入邏輯,所以必須返回false,讓Handler原本的handlerMessage能夠執(zhí)行.
if (msg.what == EXECUTE_TRANSACTION) {//這是跳轉(zhuǎn)的時(shí)候,要對(duì)intent進(jìn)行還原
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ù)源碼,在這個(gè)分支里面,msg.obj就是 ClientTransaction類型,所以,直接用
//拿到了ClientTransaction的List mActivityCallbacks;
List list = (List) mActivityCallbacksObj;
if (list.size() == 0) return true;
Object LaunchActivityItemObj = list.get(0);//所以這里直接就拿到第一個(gè)就好了
if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;
//這里必須判定 LaunchActivityItemClz,
// 因?yàn)?最初的ActivityResultItem傳進(jìn)去之后都被轉(zhuǎn)化成了這LaunchActivityItemClz的實(shí)例
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:這里有個(gè)坑(請(qǐng)看上面 if(!LaunchActivityItemClz.isInstance(LaunchActivityItemObj))returntrue;, 我為什么要加這個(gè)判斷?因?yàn)?#xff0c;我通過(guò) debug,發(fā)現(xiàn),從 mH里面的 msg.what得到的 ClientTransaction,它有這么一個(gè)成員 ListmActivityCallbacks; 注意看,從 list里面拿到的 ClientTransactionItem 的實(shí)際類型是:LaunchActivityItem. ) 之前我索引源碼的時(shí)候,追查Intent的去向,只知道它最后被封裝成了一個(gè) ClientTransaction
但是,最后我從 mH的 switchcaseEXECUTE_TRANSACTION分支,去 debug(因?yàn)闊o(wú)法繼續(xù)往下查源碼)的時(shí)候,
發(fā)現(xiàn) 原本塞進(jìn)去的?ActivityResultItem的?list,居然變成了?LaunchActivityItem的list,而我居然查了半天,查不到是在源碼何處發(fā)生的變化.
而 LaunchActivityItem 和 ActivityResultItem 他們兩個(gè)都是ClientTransaction的子類
public class LaunchActivityItem extends ClientTransactionItem
public class ActivityResultItem extends ClientTransactionItem
emmmm...也是很尷尬。=_ =!
不過(guò),最后能夠確定,從 mH的 switchcaseEXECUTE_TRANSACTION分支得到的 transaction,就是包含了Intent的包裝對(duì)象,所以只需要解析這個(gè)對(duì)象,就可以拿到intent,進(jìn)行還原.
OK,大功告成,安裝好 android 9.0 SDK 28的模擬器,啟動(dòng)起來(lái),運(yùn)行程序,看看能不能無(wú)清單跳轉(zhuǎn):
結(jié)果,臉一黑:報(bào)錯(cuò)!*
一份大禮:
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)
居然找不到包?問(wèn)題出在:NavUtils.getParentActivityName
>
還是被谷歌擺了一道,查原因啊,進(jìn)去 NavUtils.getParentActivityName()去看看:
看來(lái)就是這里報(bào)的錯(cuò),繼續(xù):找到可疑點(diǎn):可能就是這里拋出的異常,繼續(xù):然而,它是一個(gè)接口,那么就找它的實(shí)現(xiàn)類(注意,如果這個(gè)接口涉及到隱藏@hide的類,你用ctrl+T是不能找到的,不過(guò)也有辦法,回到NavUtil.java):哦,原來(lái) pm對(duì)象是來(lái)自 context,既然提到了 context這個(gè)抽象類,它的很多抽象方法的實(shí)現(xiàn)都在 ContextImpl,手動(dòng)進(jìn)入 ContextImpl:找這個(gè)方法:這個(gè) pm對(duì)象原來(lái)是來(lái)自 ActivityThread,然后進(jìn)行了一次封裝,最后返回出去的是一個(gè) ApplicationPackageManager對(duì)象.
那就進(jìn)入主線程咯.看看 IPackageManager的內(nèi)容:它是 AIDL動(dòng)態(tài)生成的接口,用 androidStudio是看不到接口內(nèi)容的,只能到源碼官網(wǎng),查到的接口如下:
interface IPackageManager {
...
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
}
Ok,看到 IBinder,就知道應(yīng)該無(wú)法繼續(xù)往下追查了,已經(jīng)跨進(jìn)程了. 前面提到了,從主線程拿到的 pm,被封裝成了 ApplicationPackageManager,那么,進(jìn)入它里面去找: getActivityInfo方法:原來(lái)異常是這里拋出的,當(dāng) mPm.getActivityInfo為空的時(shí)候,才會(huì)拋出. OK,就查到這里,得出結(jié)論: 源碼,其實(shí)對(duì) Activity的合法性進(jìn)行了兩次檢測(cè),一次是在 AMS,一次是在這里的 PMS,前面的 AMS,我們用一個(gè)已有的 Activity偽裝了一下,通過(guò)了驗(yàn)證,那么這里的 PMS,我們也可以采用同樣的方式. 注:上圖的參數(shù) ComponentNameclassName,其實(shí),他就是!Intent的 ComponentName成員:懂了吧··這里對(duì)intent又進(jìn)行了一次檢查,檢查的就是這個(gè) ComponentName.
接下來(lái)用同樣的方式對(duì) PMS的檢測(cè)進(jìn)行 hook,讓它不再報(bào)異常. 此次 hook的參照的源碼是:
hook核心代碼如下(對(duì) sPackageManager進(jìn)行代理替換,讓代理類檢查的永遠(yuǎn)是合法的 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居然是來(lái)自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);//破費(fèi),一定是這樣
}
return method.invoke(base, args);
}
}
四.最終效果
ok,見(jiàn)證奇跡的時(shí)候到了,準(zhǔn)備好 SDK28-android9.0虛擬機(jī),運(yùn)行demo:上圖中有3個(gè)Activity跳轉(zhuǎn),但是我們看demo的清單文件,只有一個(gè) launchActivity:
結(jié)語(yǔ)
又是歷時(shí)3天,無(wú)清單注冊(cè),啟動(dòng) Activity也完成。結(jié)果是美好的,然而,過(guò)程,是糾結(jié)的,但是也是值得的. 完成了3篇博文,自己對(duì) Hook技術(shù)也有了更深層次的認(rèn)識(shí):所謂 hook,套路是簡(jiǎn)單的,就是:
1.找到hook點(diǎn)(比如上面,3次hook,一次是系統(tǒng)的AMS,一次是 ActivityThread的mH,還有一次是,ActivityThread的sPackageManager,注意,這里的sPackageManager的hook,我只針對(duì)了SDK28-9.0設(shè)備進(jìn)行了hook,在其他版本的設(shè)備上運(yùn)行可能會(huì)出現(xiàn)其他問(wèn)題,比如,Intent中的參數(shù)傳遞不正常等.)
2.用合適的方式創(chuàng)建代理對(duì)象,通常 要hook一個(gè)系統(tǒng)類,就用繼承的方式,重寫某方法。hook一個(gè)系統(tǒng)接口的實(shí)現(xiàn)類,那就用JDK的Proxy動(dòng)態(tài)代理
3.最后用代理對(duì)象,反射set,替換被hook的對(duì)象.
*套路并不難,掌握好 反射,以及 代理模式,就行了. 真正難的是哪里? 是 源碼的閱讀能力,還有寫出兼容性Hook核心代碼的能力!androidSDK 有很多版本迭代,現(xiàn)在最新的是 SDK28,我們要保證我們的hook代碼能夠兼容所有的系統(tǒng)版本,就需要大量閱讀源碼,確保萬(wàn)無(wú)一失,比如上面,如果不是在 SDK28-android9.0的模擬器上運(yùn)行發(fā)現(xiàn)報(bào)異常,我根本就不會(huì)去做最后一次的 hook.
往期hook技術(shù)文章回顧:Hook技術(shù)入門篇Hook——Activity啟動(dòng)流程(1)Hook——Activity啟動(dòng)流程(2)我知道你 “在看”
總結(jié)
以上是生活随笔為你收集整理的android 全局hook_【Hook】实现无清单启动Activity的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql原生库_Mysql数据库的一些
- 下一篇: 影像科dsa为什么必须买维修保险_了解什