Android中的HOOK技術(shù)是什么
1. 什么是 Hook
Hook 英文翻譯過(guò)來(lái)就是「鉤子」的意思,那我們?cè)谑裁磿r(shí)候使用這個(gè)「鉤子」呢?在 Android 操作系統(tǒng)中系統(tǒng)維護(hù)著自己的一套事件分發(fā)機(jī)制。應(yīng)用程序,包括應(yīng)用觸發(fā)事件和后臺(tái)邏輯處理,也是根據(jù)事件流程一步步地向下執(zhí)行。而「鉤子」的意思,就是在事件傳送到終點(diǎn)前截獲并監(jiān)控事件的傳輸,像個(gè)鉤子鉤上事件一樣,并且能夠在鉤上事件時(shí),處理一些自己特定的事件。
Hook 的這個(gè)本領(lǐng),使它能夠?qū)⒆陨淼拇a「融入」被勾?。℉ook)的程序的進(jìn)程中,成為目標(biāo)進(jìn)程的一個(gè)部分。API Hook 技術(shù)是一種用于改變 API 執(zhí)行結(jié)果的技術(shù),能夠?qū)⑾到y(tǒng)的 API 函數(shù)執(zhí)行重定向。在 Android 系統(tǒng)中使用了沙箱機(jī)制,普通用戶程序的進(jìn)程空間都是獨(dú)立的,程序的運(yùn)行互不干擾。這就使我們希望通過(guò)一個(gè)程序改變其他程序的某些行為的想法不能直接實(shí)現(xiàn),但是 Hook 的出現(xiàn)給我們開(kāi)拓了解決此類問(wèn)題的道路。當(dāng)然,根據(jù) Hook 對(duì)象與 Hook 后處理的事件方式不同,Hook 還分為不同的種類,比如消息 Hook、API Hook 等。
2. Hook的應(yīng)用場(chǎng)景
Hook的應(yīng)用非常廣泛,不僅開(kāi)發(fā)人員會(huì)用到,攻擊者也會(huì)用到。
開(kāi)發(fā)有:對(duì)程序的執(zhí)行記錄日志、防止應(yīng)用重復(fù)啟動(dòng)等。
攻擊有:使用hook攔截用戶輸入信息,獲取鍵盤(pán)數(shù)據(jù)等。
3. Hook的技術(shù)方式或框架
- inline hook方式:目標(biāo)函數(shù)執(zhí)行指令中插入Jump跳轉(zhuǎn)指令實(shí)現(xiàn)重定向
- 動(dòng)態(tài)代理方式:思路應(yīng)該是類似于設(shè)計(jì)模式中的代理模式,代理原本的函數(shù)的執(zhí)行
- Method Swizzle方式:動(dòng)態(tài)改變SEL(方法編號(hào))與IMP(方法實(shí)現(xiàn))的對(duì)應(yīng)關(guān)系
- Cydia Substrate方式:適用于iOS和andriod,定義了一系列的函數(shù)和宏,底層調(diào)用了objc的runtime和fishHook來(lái)替代目標(biāo)函數(shù)或者系統(tǒng)方法
- fishHook方式:是Facebook提供一種動(dòng)態(tài)修改鏈接Mach-O文件的工具。此利用Mach-O文件加載原理,通過(guò)修改非懶加載和懶加載兩個(gè)表的指針達(dá)到C函數(shù)的Hook的目的
- Xposed框架:目標(biāo)函數(shù)為native,利用JNI hook重定向表中的函數(shù)指針
- Legend框架:Android 免 Root 環(huán)境下的一個(gè) Apk Hook 框架,該框架代碼設(shè)計(jì)簡(jiǎn)潔,通用性高,適合逆向工程時(shí)一些 Hook 場(chǎng)景。大部分的功能都放到了 Java 層,兼容性非常好。原理是直接構(gòu)造出新舊方法對(duì)應(yīng)的虛擬機(jī)數(shù)據(jù)結(jié)構(gòu),然后替換信息寫(xiě)到內(nèi)存中即可
4. Hook的一般步驟和技巧
- 尋找 Hook 點(diǎn)。原則是盡可能是靜態(tài)變量或者單例對(duì)象,因?yàn)樗鼈內(nèi)菀锥ㄎ?,其次是盡量 Hook public 的對(duì)象和方法。
- 選擇適當(dāng)?shù)膆ook方式或框架。
- 將hook代碼注入到目標(biāo)程序的運(yùn)行內(nèi)存中。
實(shí)戰(zhàn)
我們自己的代碼里面,給一個(gè)view設(shè)置了點(diǎn)擊事件,現(xiàn)在要求在不改動(dòng)這個(gè)點(diǎn)擊事件的情況下,添加額外的點(diǎn)擊事件邏輯.
View v = findViewById(R.id.tv); v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "別點(diǎn)啦,再點(diǎn)我咬你了...", Toast.LENGTH_SHORT).show(); } });
這是view的點(diǎn)擊事件,toast了一段話,現(xiàn)在要求,不允許改動(dòng)這個(gè)OnClickListener,要在toast之前添加日志打印 Log.d(…).
按照上面的思路來(lái):
第一步:根據(jù)需求 確定要hook的對(duì)象;
我們的目的是在OnClickListener中,插入自己的邏輯.所以要hook的是v.setOnClickListener()方法的實(shí)參。
第二步:尋找要hook的對(duì)象的持有者,拿到要hook的對(duì)象
進(jìn)入v.setOnClickListener源碼:發(fā)現(xiàn)我們創(chuàng)建的OnClickListener對(duì)象被賦值給了getListenerInfo().mOnClickListener
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
繼續(xù)索引:getListenerInfo() 是個(gè)什么玩意?繼續(xù)追查:
ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; } mListenerInfo = new ListenerInfo(); return mListenerInfo; }
結(jié)果發(fā)現(xiàn)這個(gè)其實(shí)是一個(gè)偽單例,一個(gè)View對(duì)象中只存在一個(gè)ListenerInfo對(duì)象. 進(jìn)入ListenerInfo內(nèi)部:發(fā)現(xiàn)OnClickListener對(duì)象 被ListenerInfo所持有.
static class ListenerInfo { ... public OnClickListener mOnClickListener; ... }
到這里為止,完成第二步,找到了點(diǎn)擊事件的實(shí)際持有者:ListenerInfo .
第三步:定義“要hook的對(duì)象”的代理類,并且創(chuàng)建該類的對(duì)象
我們要hook的是View.OnClickListener對(duì)象,所以,創(chuàng)建一個(gè)類 實(shí)現(xiàn)View.OnClickListener接口.
static class ProxyOnClickListener implements View.OnClickListener { View.OnClickListener oriLis; public ProxyOnClickListener(View.OnClickListener oriLis) { this.oriLis = oriLis; } @Override public void onClick(View v) { Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了"); if (oriLis != null) { oriLis.onClick(v); } } }
然后,創(chuàng)建出一個(gè)代理對(duì)象
ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
可以看到,這里傳入了一個(gè)View.OnClickListener對(duì)象,它存在的目的,是讓我們可以有選擇地使用到原先的點(diǎn)擊事件邏輯。一般hook,都會(huì)保留原有的源碼邏輯.
另外提一句:當(dāng)我們要?jiǎng)?chuàng)建的代理類,是被接口所約束的時(shí)候,比如現(xiàn)在,我們創(chuàng)建的ProxyOnClickListener implements View.OnClickListener,只實(shí)現(xiàn)了一個(gè)接口,則可以使用JDK提供的Proxy類來(lái)創(chuàng)建代理對(duì)象
Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]>>{View.OnClickListener.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了");//加入自己的邏輯 return method.invoke(onClickListenerInstance, args);//執(zhí)行被代理的對(duì)象的邏輯 } });
到這里為止,第三步:定義“要hook的對(duì)象”的代理類,并且創(chuàng)建該類的對(duì)象 完成。
第四步:使用上一步創(chuàng)建出來(lái)的對(duì)象,替換掉要hook的對(duì)象,達(dá)成 偷梁換柱的最終目的. 利用反射,將我們創(chuàng)建的代理點(diǎn)擊事件對(duì)象,傳給這個(gè)view field.set(mListenerInfo, proxyOnClickListener);
這里,貼出最終代碼:
輔助類
/** * hook的輔助類 * hook的動(dòng)作放在這里 */ public class HookSetOnClickListenerHelper { /** * hook的核心代碼 * 這個(gè)方法的唯一目的:用自己的點(diǎn)擊事件,替換掉 View原來(lái)的點(diǎn)擊事件 * * @param v hook的范圍僅限于這個(gè)view */ public static void hook(Context context, final View v) {// try { // 反射執(zhí)行View類的getListenerInfo()方法,拿到v的mListenerInfo對(duì)象,這個(gè)對(duì)象就是點(diǎn)擊事件的持有者 Method method = View.class.getDeclaredMethod("getListenerInfo"); method.setAccessible(true);//由于getListenerInfo()方法并不是public的,所以要加這個(gè)代碼來(lái)保證訪問(wèn)權(quán)限 Object mListenerInfo = method.invoke(v);//這里拿到的就是mListenerInfo對(duì)象,也就是點(diǎn)擊事件的持有者 //要從這里面拿到當(dāng)前的點(diǎn)擊事件對(duì)象 Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 這是內(nèi)部類的表示方法 Field field = listenerInfoClz.getDeclaredField("mOnClickListener"); final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真實(shí)的mOnClickListener對(duì)象 //2. 創(chuàng)建我們自己的點(diǎn)擊事件代理類 // 方式1:自己創(chuàng)建代理類 // ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance); // 方式2:由于View.OnClickListener是一個(gè)接口,所以可以直接用動(dòng)態(tài)代理模式 // Proxy.newProxyInstance的3個(gè)參數(shù)依次分別是: // 本地的類加載器; // 代理類的對(duì)象所繼承的接口(用Class數(shù)組表示,支持多個(gè)接口) // 代理類的實(shí)際邏輯,封裝在new出來(lái)的InvocationHandler內(nèi) Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了");//加入自己的邏輯 return method.invoke(onClickListenerInstance, args);//執(zhí)行被代理的對(duì)象的邏輯 } }); //3. 用我們自己的點(diǎn)擊事件代理類,設(shè)置到"持有者"中 field.set(mListenerInfo, proxyOnClickListener); //完成 } catch (Exception e) { e.printStackTrace(); } } // 還真是這樣,自定義代理類 static class ProxyOnClickListener implements View.OnClickListener { View.OnClickListener oriLis; public ProxyOnClickListener(View.OnClickListener oriLis) { this.oriLis = oriLis; } @Override public void onClick(View v) { Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了"); if (oriLis != null) { oriLis.onClick(v); } } } }
具體調(diào)用
v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "別點(diǎn)啦,再點(diǎn)我咬你了...", Toast.LENGTH_SHORT).show(); } }); HookSetOnClickListenerHelper.hook(this, v);//這個(gè)hook的作用,是 用我們自己創(chuàng)建的點(diǎn)擊事件代理對(duì)象,替換掉之前的點(diǎn)擊事件。
ok,目的達(dá)成v.setOnClickListener已經(jīng)被hook.
文末
關(guān)于 Android 中的 Hook 機(jī)制,大致有兩個(gè)方式:
- 要 root 權(quán)限,直接 Hook 系統(tǒng),可以干掉所有的 App。
- 免 root 權(quán)限,但是只能 Hook 自身,對(duì)系統(tǒng)其它 App 無(wú)能為力。
到此這篇關(guān)于Android中的HOOK技術(shù)是什么的文章就介紹到這了,更多相關(guān)Android HOOK內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android編程實(shí)現(xiàn)WebView添加進(jìn)度條的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)WebView添加進(jìn)度條的方法,涉及Android WebView界面及控件功能相關(guān)操作技巧,需要的朋友可以參考下2017-02-02android基礎(chǔ)教程之a(chǎn)ndroid的listview與edittext沖突解決方法
這篇文章主要介紹了android的listview與edittext沖突解決方法,需要的朋友可以參考下2014-02-02Kotlin協(xié)程Job生命周期結(jié)構(gòu)化并發(fā)詳解
這篇文章主要為大家介紹了Kotlin協(xié)程Job生命周期結(jié)構(gòu)化并發(fā)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12android 實(shí)現(xiàn)控件左右或上下抖動(dòng)教程
這篇文章主要介紹了android 實(shí)現(xiàn)控件左右或上下抖動(dòng)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03解決Error:All flavors must now belong to a named flavor dimens
這篇文章主要介紹了解決Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com,需要的朋友可以參考下2017-11-11Android開(kāi)發(fā)重寫(xiě)Animation實(shí)現(xiàn)下拉圖片后彈射回去效果示例
這篇文章主要介紹了Android開(kāi)發(fā)重寫(xiě)Animation實(shí)現(xiàn)下拉圖片后彈射回去效果,結(jié)合實(shí)例形式分析了Android自定義類繼承Animation實(shí)現(xiàn)圖片彈射效果的相關(guān)操作技巧,需要的朋友可以參考下2017-10-10Android RecyclerView實(shí)現(xiàn)點(diǎn)擊條目刪除
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView實(shí)現(xiàn)點(diǎn)擊條目刪除,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11Android Studio如何為Activity添加自定義注解信息
好久沒(méi)用寫(xiě)文章了,今天給大家分享Android Studio如何為Activity添加自定義注解信息,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-06-06Android網(wǎng)絡(luò)編程之獲取網(wǎng)絡(luò)上的Json數(shù)據(jù)實(shí)例
這篇文章主要介紹了Android網(wǎng)絡(luò)編程之獲取網(wǎng)絡(luò)上的Json數(shù)據(jù)實(shí)例,本文用完整的代碼實(shí)例講解了在Android中讀取網(wǎng)絡(luò)中Json數(shù)據(jù)的方法,需要的朋友可以參考下2014-10-10