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