一、什么是 Hook 技術(shù)
??Hook 技術(shù)又叫做鉤子函數(shù)迁客,在系統(tǒng)沒有調(diào)用該函數(shù)之前檀何,鉤子程序就先捕獲該消息,鉤子函數(shù)先得到控制權(quán)践付,這時(shí)鉤子函數(shù)既可以加工處理(改變)該函數(shù)的執(zhí)行行為秦士,還可以強(qiáng)制結(jié)束消息的傳遞。簡(jiǎn)單來說永高,就是把系統(tǒng)的程序拉出來變成我們自己執(zhí)行代碼片段伍宦。
??要實(shí)現(xiàn)鉤子函數(shù),有兩個(gè)步驟:
??1. 利用系統(tǒng)內(nèi)部提供的接口乏梁,通過實(shí)現(xiàn)該接口,然后注入進(jìn)系統(tǒng)(特定場(chǎng)景下使用)
??2.動(dòng)態(tài)代理(使用所有場(chǎng)景)
二关贵、Hook 技術(shù)實(shí)現(xiàn)的步驟
??Hook 技術(shù)實(shí)現(xiàn)的步驟也分為兩步
??1.找到 hook 點(diǎn)(Java 層)遇骑,該 hook 點(diǎn)必須滿足以下的條件:需要 hook 的方法,所屬的對(duì)象必須是靜態(tài)的揖曾,因?yàn)槲覀兪峭ㄟ^反射來獲取對(duì)象的落萎,我們獲取的是系統(tǒng)的對(duì)象,所以不能夠 new 一個(gè)新的對(duì)象炭剪,必須用系統(tǒng)創(chuàng)建的那個(gè)對(duì)象练链,所以只有靜態(tài)的才能保證和系統(tǒng)的對(duì)象一致。
??2.將 hook 方法放到系統(tǒng)之外執(zhí)行(放入我們自己的邏輯)
三奴拦、使用 hook 技術(shù)實(shí)現(xiàn)免注冊(cè)式跳轉(zhuǎn)
??解釋一下上面的步驟媒鼓,我們有一個(gè) MainActivity,四個(gè)按鈕,前三個(gè)是打開不同的 Activity绿鸣,最后一個(gè)是退出登錄疚沐,這三個(gè) Activity 其中界面2是不需要登錄的,界面3和界面4都是需要登錄才能看到的潮模。
??那么既然要在打開 Activity 之前就判斷是否登錄了亮蛔,而且要使用 hook 技術(shù),那么我們下看一下 startActivity 的源碼擎厢,因?yàn)槲覀冎牢覀冃枰?hook 的就是 startActivity 方法究流。
找 Hook 點(diǎn)
注意:上述源碼只有部分源碼,為了方便截屏动遭,并不是全部的源碼芬探,如果想看全部源碼,請(qǐng)自行查看沽损。
??看到這灯节,我們明白了,其實(shí)是 ActivityManager.getService() 最終調(diào)用了 startActivity() 方法绵估,我們看 ActivityManager.getService() 方法炎疆。
??解釋一下上面的源碼,ActivityManager.getService()方法調(diào)用的是 IActivityManagerSingleton.get()方法国裳,而這個(gè)IActivityManagerSingleton是 Singleton(android.util)形入,所以 IActivityManagerSingleton.get()就是調(diào)用了 Singleton 里面的 get 方法,進(jìn)到 Singleton 類缝左,發(fā)現(xiàn) get() 方法里面會(huì)通過 create() 抽象方法方法給 mInstance 屬性賦值亿遂,回到剛才的地方,我們發(fā)現(xiàn)渺杉,create() 方法返回了一個(gè) IAcivityManager 對(duì)象蛇数。最終結(jié)果:其實(shí)最終是 IActivityManager 調(diào)用了 startActivity() 方法。
??所以我們真正想 hook 的點(diǎn)是 IActivityManager 對(duì)象是越,那么如何拿到這個(gè)靜態(tài)對(duì)象呢耳舅?其實(shí)聰明的帥哥和美女肯定都發(fā)現(xiàn)了,這個(gè) IActivityManagerSingleton 其實(shí)就是一個(gè)靜態(tài)的倚评,而且我們拿到該系統(tǒng)對(duì)象后就獲取到該對(duì)象的 mInstance 屬性浦徊,即 IActivityManager,那么我們就把 IActivityManagerSingleton 當(dāng)做一個(gè)偽 hook 點(diǎn)天梧。
??hook 點(diǎn)已經(jīng)找到了盔性,第一步已經(jīng)完成,接下來就該第二步了呢岗,那么如何將系統(tǒng)執(zhí)行的 startActivity() 拉到系統(tǒng)外執(zhí)行冕香,給其添加一些自己的邏輯呢蛹尝?這里我們使用動(dòng)態(tài)代理來實(shí)現(xiàn)。
??這里大概說一下項(xiàng)目暂筝,肯定有五個(gè) Activity箩言,一個(gè) MainActivity 是用來展示四個(gè)按鈕的,一個(gè) LoginActivity焕襟,還有其他三個(gè)是測(cè)試的展示頁面陨收,其實(shí)還有一個(gè) ProxyActivity,并且鸵赖,在清單文件中务漩,我們除了 MainActivity 是啟動(dòng)頁,ProxyActivity 進(jìn)行了注冊(cè)它褪,其他的 Activity 都沒有在清單文件中注冊(cè)饵骨,沒錯(cuò),你沒有看錯(cuò)茫打,就是沒有注冊(cè)居触,那運(yùn)行會(huì)崩潰嗎?空口無憑老赤,我們先看一下代碼轮洋,然后看運(yùn)行結(jié)果。
package com.radish.android.hookframeworktest;
import android.content.Context;
import android.content.Intent;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HookUtils {
private Context context;
public void hookStartActivity(Context context) {
this.context = context;
try {
/**
* 這里注意一下抬旺,用我們分析的源碼運(yùn)行不了弊予,所以稍微改了一下,
* 思路什么都一樣开财,只是源碼的屬性名做了修改
*/
// Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
Class<?> activityManagerClass = Class.forName("android.app.ActivityManagerNative");
//拿到 IActivityManagerSingleton 屬性
// Field field = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
Field field = activityManagerClass.getDeclaredField("gDefault");
field.setAccessible(true);
//獲取到是 Singleton 對(duì)象汉柒,也就是 field 對(duì)應(yīng)的類型
Object singletonObj = field.get(null);
//然后獲取 Singletone 的 mInstance 屬性
Class<?> singtonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singtonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//真正的 hook 點(diǎn)
Object iActivityManagerObj = mInstanceField.get(singletonObj);
//hook 第二步,動(dòng)態(tài)代理
Class<?> iActivityManagerIntercept = Class.forName("android.app.IActivityManager");
StartActivityHandler startActivityHandler = new StartActivityHandler(iActivityManagerObj);
Object proxyIActivityManager = Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{iActivityManagerIntercept}, startActivityHandler);
//在這我們將系統(tǒng)的對(duì)象更換成我們生成的動(dòng)態(tài)代理對(duì)象责鳍,為了是調(diào)用動(dòng)態(tài)代理的 invoke 方法碾褂,不更換不執(zhí)行
mInstanceField.set(singletonObj, proxyIActivityManager);
} catch (Exception e) {
e.printStackTrace();
}
}
class StartActivityHandler implements InvocationHandler {
//系統(tǒng)真正的對(duì)象
private Object trueIActivityManager;
public StartActivityHandler(Object trueIActivityManager) {
this.trueIActivityManager = trueIActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
System.out.println("abc : --------------------- startActivity ---------------------");
Intent intent = null;
int index = -1;
for (int i = 0; i < args.length; i++) {
Object obj = args[i];
if (obj instanceof Intent) {
//找到 startActivity 傳遞進(jìn)來的 Intent
intent = (Intent) obj;
index = i;
}
}
//瞞天過海,獲取想要跳轉(zhuǎn)的意圖历葛,進(jìn)行篡改
Intent newIntent = new Intent(context, ProxyActivity.class);
//我們將真實(shí)的意圖封裝在假意圖當(dāng)中
newIntent.putExtra("oldIntent", intent);
args[index] = newIntent;
}
return method.invoke(trueIActivityManager, args);
}
}
}
??然后就是如何使用了
??總結(jié)下正塌,目前我們實(shí)現(xiàn)的功能是,不管你跳轉(zhuǎn)任何的 Activity啃洋,我們都跳轉(zhuǎn)到 ProxyActivity,所以我們只需要在清單文件中注冊(cè)一個(gè) ProxyActivity 而不用注冊(cè)其他的 Activity 也不會(huì)崩潰屎鳍,是如何實(shí)現(xiàn)的呢宏娄?我們是通過使用 hook 技術(shù)篡改 Intent,并將你真正的意圖存放到我們新的 Intent 中逮壁。這時(shí)候孵坚,應(yīng)該有些人要打我了,我明明想去東京和巴黎,你卻帶我去了浪漫的土耳其~~~~~
??別急卖宠,還沒完呢巍杈。