百度百科解釋:hook技術(shù)是一種改變api執(zhí)行結(jié)果的技術(shù)垃僚,Microsoft 自身也在Windows操作系統(tǒng)里面使用了這個技術(shù),如Windows兼容模式等规辱。 API HOOK 技術(shù)并不是計算機(jī)病毒專有技術(shù)谆棺,但是計算機(jī)病毒經(jīng)常使用這個技術(shù)來達(dá)到隱藏自己的目的。
通俗的講罕袋,hook其實(shí)就是一個鉤子函數(shù)改淑,在Android中碍岔,所有事件分發(fā)運(yùn)行都有自己的一套機(jī)制,包括應(yīng)用觸發(fā)事件和后臺邏輯處理溅固,也是根據(jù)事件流程一步步走下去付秕。而鉤子的意思就是在事件傳送到終點(diǎn)前截取事件并監(jiān)控事件的傳輸,就像鉤子勾上事件一樣侍郭,并且能在勾上事件時候询吴。處理一些自己需要額外自定義的事件。
hook的這個本領(lǐng)亮元,使它能夠?qū)⒆陨淼拇a融入到被執(zhí)行進(jìn)程中猛计,并成為其中一部分。
hook原理:hook技術(shù)無論是對于安全軟件還是惡意軟件都是一項(xiàng)非常關(guān)鍵的技術(shù)爆捞,其本質(zhì)就是劫持函數(shù)調(diào)用奉瘤,但是由于處于linux用戶態(tài),每個進(jìn)程都有自己獨(dú)立的進(jìn)程空間煮甥,所以必須先注入到所要的hook進(jìn)程空間盗温,修改其內(nèi)存中的進(jìn)程代碼,替換其過程表的符號地址成肘。
Hook技術(shù)的難點(diǎn)卖局,并不在于Hook技術(shù),初學(xué)者借助于資料“照葫蘆畫瓢”能夠很容易就掌握Hook的基本使用方法双霍。如何找到函數(shù)的入口點(diǎn)砚偶、替換函數(shù),這就涉及了理解函數(shù)的連接與加載機(jī)制洒闸。
hook技術(shù)的三要素:
1>現(xiàn)有功能
2>目標(biāo)功能
3>技術(shù)替換
實(shí)現(xiàn)hook技術(shù)的途徑:
1>利用系統(tǒng)內(nèi)部提供的接口染坯,通過實(shí)現(xiàn)該接口,然后注入進(jìn)系統(tǒng)丘逸。(并不通用)
2>動態(tài)代理单鹿。(所有場景)
好了,今天我們是想要通過hook技術(shù)實(shí)現(xiàn)清單免注冊activity跳轉(zhuǎn)深纲。
我們知道安卓中activity的跳轉(zhuǎn)是通過startActivity(intent);這個函數(shù)仲锄,所以我們可以肯定的是這個函數(shù)肯定是作為hook的切入點(diǎn)進(jìn)行事件的攔截的,所以現(xiàn)在我們就來看一下startActivity();的源碼囤萤,尋找hook攔截的方法昼窗。
//ContextWrapper.java
public class ContextWrapper extends Context {
Context mBase;
...省略其他代碼
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
...省略其他代碼
}
可以看到,startActivity()調(diào)用的是ContextWrapper里面的statactivity方法涛舍,可以看到里面有個mBase屬性對象澄惊。在調(diào)用startActivity的時候會調(diào)用mBase的startActivity方法。這個mBase就是ContextImpl.java
現(xiàn)在來分析ContextImpl.java
我們看看這個類的startActivity的方法的實(shí)現(xiàn)
//ContextImpl.java
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
直接看startActivity(intent, null);方法,前一個方法是用于進(jìn)程檢測的掸驱。
@Override
public void startActivity(Intent intent, Bundle options) {
//...省略代碼 只看方法的重點(diǎn)
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
可以看到此方法調(diào)用 mMainThread.getInstrumentation()獲取一個對象然后調(diào)用對象的方法啊execStartActivity肛搬。其中 mMainThread對象的聲明為final ActivityThread mMainThread;
mMainThread.getInstrumentation()查看實(shí)現(xiàn)如下,Instrumentation類提供的各種流程控制方法毕贼,它的作用可以把測試包和目標(biāo)測試應(yīng)用加載到同一個進(jìn)程中運(yùn)行温赔,不過它不是我們今天hook技術(shù)實(shí)現(xiàn)的關(guān)鍵點(diǎn),這里不做討論鬼癣。
//ActivtyThread.java
public Instrumentation getInstrumentation()
{
return mInstrumentation;
}
所以我們繼續(xù)查看Instrumentation 對象的execStartActivity方法
//Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
//...省略方法其他方法
int result = ActivityManagerNative.getDefault().startActivity(一堆參數(shù));
//...
return null;
}
我們看到ActivityManagerNative.getDefault()獲取一個對象然后調(diào)用startActivity()陶贼,
ActivityManagerNative.getDefault()方法探究
//ActivityManagerNative.java
static public IActivityManager getDefault() {
return gDefault.get();
}
gDefault是ActivityManagerNative.java屬性對象,聲明如下:
//ActivityManagerNative.java
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
Singleton又是什么待秃?這個是一個單例工具類拜秧,當(dāng)你第一次調(diào)用此類的get()會回調(diào)使用則自己實(shí)現(xiàn)的抽象方法create()進(jìn)而進(jìn)行單例操作
//Singleton.java
package android.util;
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
從上面的例子可以看到,看到ActivityManagerNative.getDefault()返回了遠(yuǎn)程服務(wù)對象IActivityManager 接口章郁。
再回頭看看Instrumentation 對象的execStartActivity方法
//Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
//...省略方法其他方法
int result = ActivityManagerNative.getDefault().startActivity(一堆參數(shù));
//...
return null;
}
可以得出以下結(jié)論:獲取一個遠(yuǎn)程服務(wù)對象接口IActivityManager(并且是單例)然后調(diào)用IActivityManager的startActivity方法喻括。此時會回調(diào)到遠(yuǎn)程服務(wù)ActivityManagerServer的真正startActivity接口方法潮针。里面會檢查你傳入的Intent對象的Activity是否在清單內(nèi),如果你的activity不在清單內(nèi),就是一個異常.但是我們不需要再看ActivityManagerServer源碼祈远,因?yàn)樗橇硪粋€app進(jìn)程小泉,所以無法干涉症革。如果可以干涉的話此衅,Android系統(tǒng)就太不穩(wěn)定了误褪,所以我們無法hook到遠(yuǎn)程服務(wù),但是IActivityManager我們卻是可以用手腳的医舆。因?yàn)槟鞘欠?wù)端存在客戶端一個代理接口對象俘侠,這一塊需要大家簡單了解下AIDL的知識象缀。
也就是說我們可以通過hook技術(shù)對IActivityManager動一些手腳蔬将,在IActivityManager把我們intent傳遞給AMS檢測之前,我們攔截這個函數(shù)央星,并且把intent設(shè)置成一個中間合法的ProxyInent對象霞怀,然后在通過AMS檢測后,我們再把它替換回來我們自己非法的Intent莉给,實(shí)現(xiàn)偷梁換柱毙石。
如圖:
我在網(wǎng)上找到一個startActivity的加載時序圖,
我們仔細(xì)看上面的圖IActivityManager調(diào)用的時候會把要啟動的Activity的Intent去給ActivityManagerServer,假設(shè)我們此時用動態(tài)代理在IActivityManager調(diào)用遠(yuǎn)程服務(wù)之前颓遏,把在一個在清單文件注冊過的Activity的Intent替換不就通過校驗(yàn)了徐矩。
所以hook其實(shí)我們已經(jīng)找到了,就是IActivityManager里面的startActivity方法叁幢,在它把startActivity方法丟給ActivityManagerServer之前我們偷偷把非合法intent替換成合法intent滤灯,檢測通過后再偷偷換回來,事情就搞定了。
具體代碼如下鳞骤,這里面用到j(luò)ava反射機(jī)制和Proxy動態(tài)代理類窒百,
首先定義一個HoolUtil.java類
public void hookStartActivity(Context context) {
//還原 gDefault 成員變量 反射 調(diào)用一次
this.context = context;
try {
Class<?> ActivityManagerNativecls=Class.forName("android.app.ActivityManagerNative");
Field gDefault = ActivityManagerNativecls.getDeclaredField("gDefault");
gDefault.setAccessible(true);
//因?yàn)槭庆o態(tài)變量 所以獲取的到的是系統(tǒng)值 hook 偽hook
Object defaltValue=gDefault.get(null);
//mInstance對象
Class<?> SingletonClass=Class.forName("android.util.Singleton");
Field mInstance = SingletonClass.getDeclaredField("mInstance");
//還原 IActivityManager對象 系統(tǒng)對象
mInstance.setAccessible(true);
Object iActivityManagerObject=mInstance.get(defaltValue);
Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
startActivty startActivtyMethod = new startActivty(iActivityManagerObject);
//第二參數(shù) 是即將返回的對象 需要實(shí)現(xiàn)那些接口,其中這些接口包含OnClickListener,和IActivityManagerIntercept所實(shí)現(xiàn)的接口豫尽。
//也就是說IActivityManager和OnClickListener所實(shí)現(xiàn)的接口都動態(tài)替換成startActivtyMethod了
Object oldIactivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
, new Class[]{IActivityManagerIntercept, View.OnClickListener.class}
, startActivtyMethod);
//將系統(tǒng)的iActivityManager 替換成 自己通過動態(tài)代理實(shí)現(xiàn)的對象
//oldIactivityManager對象 實(shí)現(xiàn)了 IActivityManager這個接口的所有方法
mInstance.set(defaltValue, oldIactivityManager);
} catch (Exception e) {
e.printStackTrace();
}
}
class startActivty implements InvocationHandler {
private Object iActivityManagerObject;
public startActivty(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("INFO","invoke "+method.getName());
if ("startActivity".equals(method.getName())) {
Log.i("INFO","-----------------startActivity--------------------------");
//瞞天過海
//尋找傳進(jìn)來的intent
Intent intent = null;
int index=0;
for (int i=0;i<args.length;i++) {
//intent
Object arg = args[i];
if (arg instanceof Intent) {
intent = (Intent) args[i];
index = i;
}
}
//目的 ---載入acgtivity 將它還原
Intent newIntent = new Intent();
//ProxyActivity是合法意圖篙梢,這里用它通過AMS檢測
ComponentName componentName = new ComponentName(context, ProxyActivity.class);
newIntent.setComponent(componentName);
//真實(shí)的意圖 被我隱藏到了 鍵值對,等待待會繞過AMS后再通過ActivityMH取出來美旧。
newIntent.putExtra("oldIntent", intent);
args[index] = newIntent;
}
return method.invoke(iActivityManagerObject, args);
}
}
這兩個方法其實(shí)就實(shí)現(xiàn)了將ProxyActivity類推送給ActivityManagerServer去檢測渤滞,當(dāng)然這個ProxyActivity必須是在清單文件中注冊了的activity。做完后你每次啟動activity都會打開ProxyActivity榴嗅,到這里你已經(jīng)成功了一半蔼水,但是這并不是我們想要的,所以你還得分析源碼录肯,ActivityManagerServer檢測完intent后會做什么呢趴腋?
網(wǎng)上盜圖如下:
我們知道,安卓進(jìn)程中消息通信是通過handler實(shí)現(xiàn)的论咏,所有的消息都是存放在消息隊列(MessageQueue)中优炬,主線程中有一個Looper循環(huán)器,不斷從對應(yīng)線程MassgaeQueue中死循環(huán)拿取mesaage然后發(fā)送到對應(yīng)的handler厅贪。
handler有消息的會調(diào)用如下方法:
//Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//如果mCallback 不為空并且處理后返回true 那么不會調(diào)用我們自己寫handlerMessage處理消息蠢护。
handleMessage(msg);
}
}
我們后面會用此ActivityMH 就行再次替換Handler.Callback
在java中程序的啟動是在Main方法中,其實(shí)在android中也有一個main方法养涮,只不過系統(tǒng)把它隱藏了我們沒看到而已葵硕,這個main方法在ActivityThread.java中,它是安卓進(jìn)程啟動的入口
//ActivityThread.java
public static void main(String[] args) {
//...代碼省略
//Looper類一些初始化贯吓,會從ThreadLocal對象拿取當(dāng)前線程的Looper
//關(guān)于ThreadLocal不做過多講述懈凹,簡單就是用線程資源拷貝。
//假設(shè)你有個變量a 你可以拷貝三份到ThreadLocal中不同的線程
Looper.prepareMainLooper();
//...代碼省略
ActivityThread thread = new ActivityThread();
//開啟一個子線程悄谐,又可以叫binder線程用于ams(ActivityManagerServer通AIDL來進(jìn)程間通信介评,如果有信息那么從子線程發(fā)送message給主線程handler進(jìn)行處理)
thread.attach(false);
//...代碼省略
//開啟一個死循環(huán)遍歷messageQueue(里面存放messager),如果有就爬舰,所以前面才開啟一個子線程们陆,不然怎么跟AMS通信?
Looper.loop();
//...代碼省略
}
可以看到這里面開啟了一個線程情屹,這個線程就是安卓的主線程坪仇,這個主線程在
包含在一個Looper中間,這個loop就是一個死循環(huán)不停在消息隊列中取出消息或者任務(wù)交給主線程做垃你,比如UI刷新就是主線程中一個定期定時任務(wù)椅文,所以我一直認(rèn)為其實(shí)主線程和子線程的區(qū)別就是在與它里面存在一個looper而已颈墅,讓它不停工作永葆活力,之前我看到過在某個子線程中我們自定給它綁定一個looper雾袱,那么這個子線程也是可以刷新UI操作的恤筛,但是最好不要這樣做,如果在沒有線程鎖的情況下會存在很大的安全漏洞芹橡。
話歸正題:
通過IActivityManager發(fā)送startActivity方法到ActivityManagerServer中毒坛,然后我們知道知道在ActivityThread類中main方法死循環(huán)前開啟了個子線程,這個線程會接收ActivityManagerServer回饋的信息林说,信息封裝在Message中然后添加主線程MeassgeQueue中煎殷,當(dāng)主線程的Loop遍歷到有信息的時候交給主線程的Handler處理。
當(dāng)ActivityManagerServer收到startActivity信息的信息的時候腿箩,會發(fā)送一個message給Handler處理豪直。其中message.what=LAUNCH_ACTIVITY,LAUNCH_ACTIVITY=100
我們看看Activity中Handler:
public class AcitvityThread{
final H mH = new H();
private class H extends Handler {
//珠移。弓乙。。代碼省略
}
}
//ActivityThread.java
class H extends Handler{
//.....
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
//從msg拿出ActivityClientRecord 對象钧惧,里面包含啟動activity的Intent
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
//帶著ActivityClientRecord 對象啟動activity
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
}
}
}
大致可簡單的理解從msg獲取ActivityClientRecord 對象暇韧,內(nèi)包含啟動Intent啟動意圖等信息,還記得我們前面保存一個Intent到一個新的Intent上嗎浓瞪?這時候其實(shí)你可以從這個Intent取出來了懈玻。調(diào)用handleLaunchActivity去啟動。
繞過AMS檢測這只是第一步乾颁,下面我們還得通過hook技術(shù)實(shí)現(xiàn)將我們真實(shí)意圖取出來涂乌,跳轉(zhuǎn)到我們自己真正要跳轉(zhuǎn)的頁面。
其實(shí)就是通過反射機(jī)制取出ActivityThread中mH英岭,并動態(tài)替換成我們自己的方法湾盒。
代碼如下:
public void hookHookMh(Context context) {
this.context = context;
try {
Class<?> forName = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = forName.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
//還原系統(tǒng)的ActivityTread mH
Object activityThreadObj=currentActivityThreadField.get(null);
Field handlerField = forName.getDeclaredField("mH");
handlerField.setAccessible(true);
//hook點(diǎn)找到了
Handler mH= (Handler) handlerField.get(activityThreadObj);
Field callbackField = Handler.class.getDeclaredField("mCallback");
callbackField.setAccessible(true);
callbackField.set(mH,new ActivityMH(mH));
} catch (Exception e) {
e.printStackTrace();
}
}
class ActivityMH implements Handler.Callback{
private Handler mH;
public ActivityMH(Handler mH) {
this.mH = mH;
}
@Override
public boolean handleMessage(Message msg) {
//LAUNCH_ACTIVITY ==100 即將要加載一個activity了,這里是系統(tǒng)的規(guī)范定義的
if (msg.what == 100) {
//加工 --完 一定丟給系統(tǒng)真實(shí)intent -hook->proxyActivity---hook->secondeActivtiy
handleLuachActivity(msg);
}
//做了真正的跳轉(zhuǎn)
mH.handleMessage(msg);
return true;
}
class ActivityMH implements Handler.Callback{
private Handler mH;
public ActivityMH(Handler mH) {
this.mH = mH;
}
@Override
public boolean handleMessage(Message msg) {
//LAUNCH_ACTIVITY ==100 即將要加載一個activity了,這里是系統(tǒng)的規(guī)范定義的
if (msg.what == 100) {
//加工 --完 一定丟給系統(tǒng)真實(shí)intent -hook->proxyActivity---hook->secondeActivtiy
handleLuachActivity(msg);
}
//做了真正的跳轉(zhuǎn)
mH.handleMessage(msg);
return true;
}
private void handleLuachActivity(Message msg) {
//還原
Object obj = msg.obj;
try {
Field intentField=obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// ProxyActivity 2
Intent realyIntent = (Intent) intentField.get(obj);
// 到這里后,其實(shí)已經(jīng)通過AMS檢測了巴席,這里將我們存入的oldIntent取出來历涝,然后用它做真實(shí)跳轉(zhuǎn)诅需。
Intent oldIntent = realyIntent.getParcelableExtra("oldIntent");
// 登錄 還原 把原有的意圖 放到realyIntent
realyIntent.setComponent(oldIntent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}
}
到這里我們算是把這個工具類寫完了漾唉,當(dāng)然我們需要定義一個Application類,在這個類里面去注冊這兩個hook函數(shù)堰塌,
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookUtil hookUtil = new HookUtil();
hookUtil.hookStartActivity(this);
hookUtil.hookHookMh(this);
}
}
這就是基本完成了hook技術(shù)實(shí)現(xiàn)免注冊activity跳轉(zhuǎn)赵刑,至于應(yīng)用場景可以在插件化架構(gòu)設(shè)計中用到,還比如你在工程所有activity跳轉(zhuǎn)集中監(jiān)測判斷條件時候也可以用场刑,加入你頁面中每個activity跳轉(zhuǎn)都需要監(jiān)測是否登錄了般此,沒登陸就先跳轉(zhuǎn)登錄頁面蚪战,是不是也可以在這里稍微改動就可以實(shí)現(xiàn)呢?
private void handleLuachActivity(Message msg) {
//還原
Object obj = msg.obj;
try {
Field intentField=obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// ProxyActivity 2
Intent realyIntent = (Intent) intentField.get(obj);
// 到這里后铐懊,其實(shí)已經(jīng)通過AMS檢測了邀桑,這里將我們存入的oldIntent取出來,然后用它做真實(shí)跳轉(zhuǎn)科乎。
Intent oldIntent = realyIntent.getParcelableExtra("oldIntent");
if (oldIntent != null) {
//集中式登錄
SharedPreferences share = context.getSharedPreferences("dcw", Context.MODE_PRIVATE);
//oldIntent.getComponent().getClassName().equals(SceondActivity.class.getName())
if (share.getBoolean("login",false)) {
// 登錄 還原 把原有的意圖 放到realyIntent
realyIntent.setComponent(oldIntent.getComponent());
}else {
ComponentName componentName = new ComponentName(context,LoginActivity.class);
realyIntent.putExtra("extraIntent", oldIntent.getComponent().getClassName());
realyIntent.setComponent(componentName);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}