網站首頁 編程語言 正文
1. 什么是 Hook
Hook 英文翻譯過來就是「鉤子」的意思,那我們在什么時候使用這個「鉤子」呢?在 Android 操作系統中系統維護著自己的一套事件分發機制。應用程序,包括應用觸發事件和后臺邏輯處理,也是根據事件流程一步步地向下執行。而「鉤子」的意思,就是在事件傳送到終點前截獲并監控事件的傳輸,像個鉤子鉤上事件一樣,并且能夠在鉤上事件時,處理一些自己特定的事件。
Hook 的這個本領,使它能夠將自身的代碼「融入」被勾住(Hook)的程序的進程中,成為目標進程的一個部分。API Hook 技術是一種用于改變 API 執行結果的技術,能夠將系統的 API 函數執行重定向。在 Android 系統中使用了沙箱機制,普通用戶程序的進程空間都是獨立的,程序的運行互不干擾。這就使我們希望通過一個程序改變其他程序的某些行為的想法不能直接實現,但是 Hook 的出現給我們開拓了解決此類問題的道路。當然,根據 Hook 對象與 Hook 后處理的事件方式不同,Hook 還分為不同的種類,比如消息 Hook、API Hook 等。
2. Hook的應用場景
Hook的應用非常廣泛,不僅開發人員會用到,攻擊者也會用到。
開發有:對程序的執行記錄日志、防止應用重復啟動等。
攻擊有:使用hook攔截用戶輸入信息,獲取鍵盤數據等。
3. Hook的技術方式或框架
- inline hook方式:目標函數執行指令中插入Jump跳轉指令實現重定向
- 動態代理方式:思路應該是類似于設計模式中的代理模式,代理原本的函數的執行
- Method Swizzle方式:動態改變SEL(方法編號)與IMP(方法實現)的對應關系
- Cydia Substrate方式:適用于iOS和andriod,定義了一系列的函數和宏,底層調用了objc的runtime和fishHook來替代目標函數或者系統方法
- fishHook方式:是Facebook提供一種動態修改鏈接Mach-O文件的工具。此利用Mach-O文件加載原理,通過修改非懶加載和懶加載兩個表的指針達到C函數的Hook的目的
- Xposed框架:目標函數為native,利用JNI hook重定向表中的函數指針
- Legend框架:Android 免 Root 環境下的一個 Apk Hook 框架,該框架代碼設計簡潔,通用性高,適合逆向工程時一些 Hook 場景。大部分的功能都放到了 Java 層,兼容性非常好。原理是直接構造出新舊方法對應的虛擬機數據結構,然后替換信息寫到內存中即可
4. Hook的一般步驟和技巧
- 尋找 Hook 點。原則是盡可能是靜態變量或者單例對象,因為它們容易定位,其次是盡量 Hook public 的對象和方法。
- 選擇適當的hook方式或框架。
- 將hook代碼注入到目標程序的運行內存中。
實戰
我們自己的代碼里面,給一個view設置了點擊事件,現在要求在不改動這個點擊事件的情況下,添加額外的點擊事件邏輯.
View v = findViewById(R.id.tv);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "別點啦,再點我咬你了...", Toast.LENGTH_SHORT).show();
}
});
這是view的點擊事件,toast了一段話,現在要求,不允許改動這個OnClickListener,要在toast之前添加日志打印 Log.d(…).
按照上面的思路來:
第一步:根據需求 確定要hook的對象;
我們的目的是在OnClickListener中,插入自己的邏輯.所以要hook的是v.setOnClickListener()方法的實參。
第二步:尋找要hook的對象的持有者,拿到要hook的對象
進入v.setOnClickListener源碼:發現我們創建的OnClickListener對象被賦值給了getListenerInfo().mOnClickListener
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
繼續索引:getListenerInfo() 是個什么玩意?繼續追查:
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
結果發現這個其實是一個偽單例,一個View對象中只存在一個ListenerInfo對象. 進入ListenerInfo內部:發現OnClickListener對象 被ListenerInfo所持有.
static class ListenerInfo {
...
public OnClickListener mOnClickListener;
...
}
到這里為止,完成第二步,找到了點擊事件的實際持有者:ListenerInfo .
第三步:定義“要hook的對象”的代理類,并且創建該類的對象
我們要hook的是View.OnClickListener對象,所以,創建一個類 實現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", "點擊事件被hook到了");
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
然后,創建出一個代理對象
ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
可以看到,這里傳入了一個View.OnClickListener對象,它存在的目的,是讓我們可以有選擇地使用到原先的點擊事件邏輯。一般hook,都會保留原有的源碼邏輯.
另外提一句:當我們要創建的代理類,是被接口所約束的時候,比如現在,我們創建的ProxyOnClickListener implements View.OnClickListener,只實現了一個接口,則可以使用JDK提供的Proxy類來創建代理對象
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", "點擊事件被hook到了");//加入自己的邏輯
return method.invoke(onClickListenerInstance, args);//執行被代理的對象的邏輯
}
});
到這里為止,第三步:定義“要hook的對象”的代理類,并且創建該類的對象 完成。
第四步:使用上一步創建出來的對象,替換掉要hook的對象,達成 偷梁換柱的最終目的. 利用反射,將我們創建的代理點擊事件對象,傳給這個view field.set(mListenerInfo, proxyOnClickListener);
這里,貼出最終代碼:
輔助類
/**
* hook的輔助類
* hook的動作放在這里
*/
public class HookSetOnClickListenerHelper {
/**
* hook的核心代碼
* 這個方法的唯一目的:用自己的點擊事件,替換掉 View原來的點擊事件
*
* @param v hook的范圍僅限于這個view
*/
public static void hook(Context context, final View v) {//
try {
// 反射執行View類的getListenerInfo()方法,拿到v的mListenerInfo對象,這個對象就是點擊事件的持有者
Method method = View.class.getDeclaredMethod("getListenerInfo");
method.setAccessible(true);//由于getListenerInfo()方法并不是public的,所以要加這個代碼來保證訪問權限
Object mListenerInfo = method.invoke(v);//這里拿到的就是mListenerInfo對象,也就是點擊事件的持有者
//要從這里面拿到當前的點擊事件對象
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 這是內部類的表示方法
Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真實的mOnClickListener對象
//2. 創建我們自己的點擊事件代理類
// 方式1:自己創建代理類
// ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
// 方式2:由于View.OnClickListener是一個接口,所以可以直接用動態代理模式
// Proxy.newProxyInstance的3個參數依次分別是:
// 本地的類加載器;
// 代理類的對象所繼承的接口(用Class數組表示,支持多個接口)
// 代理類的實際邏輯,封裝在new出來的InvocationHandler內
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", "點擊事件被hook到了");//加入自己的邏輯
return method.invoke(onClickListenerInstance, args);//執行被代理的對象的邏輯
}
});
//3. 用我們自己的點擊事件代理類,設置到"持有者"中
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", "點擊事件被hook到了");
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
}
具體調用
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "別點啦,再點我咬你了...", Toast.LENGTH_SHORT).show();
}
});
HookSetOnClickListenerHelper.hook(this, v);//這個hook的作用,是 用我們自己創建的點擊事件代理對象,替換掉之前的點擊事件。
ok,目的達成v.setOnClickListener已經被hook.
文末
關于 Android 中的 Hook 機制,大致有兩個方式:
- 要 root 權限,直接 Hook 系統,可以干掉所有的 App。
- 免 root 權限,但是只能 Hook 自身,對系統其它 App 無能為力。
原文鏈接:https://blog.csdn.net/Androidxiaofei/article/details/128868298
相關推薦
- 2022-10-19 Python?Pandas?修改表格數據類型?DataFrame?列的順序案例_python
- 2022-06-09 Python中re模塊的元字符使用小結_python
- 2022-05-24 C#中WPF顏色對話框控件的實現_C#教程
- 2023-01-17 Python使用鄰接矩陣實現圖及Dijkstra算法問題_python
- 2022-11-17 python?泛型函數--singledispatch的使用解讀_python
- 2022-10-29 設置html按鈕點擊事件失效
- 2022-05-17 NoSQL中的“BASE”特性
- 2022-10-14 element form表單數據未雙向綁定
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支