[TOC]
## GOT/PLT Hook

GOT/PLT Hook 主要是用于替換某個 SO 的[外部調用](),通過將外部函數調用跳轉成我們的目標函數。GOT/PLT Hook 可以說是一個非常經典的 Hook 方法,它非常穩定,可以達到部署到生產環境的標準。
### ELF 格式
ELF(Executableand Linking Format)是可執行和鏈接格式,它是一個開放標準,各種 UNIX 系統的可執行文件大多采用 ELF 格式。雖然 ELF 文件本身就支持三種不同的類型(重定位、執行、共享),不同的視圖下格式稍微不同,不過它有一個統一的結構,這個結構如下圖所示。

網上介紹 ELF 格式的文章非常多,你可以參考《ELF 文件格式解析》。顧名思義,對于 GOT/PLT Hook 來說,我們主要關心“.plt”和“.got”這兩個節區:
* .plt。該節保存過程鏈接表(Procedure Linkage Table)。
* .got。該節保存著全局的偏移量表。
我們也可以使用readelf -S來查看 ELF 文件的具體信息。
### 鏈接過程
接下來我們再來看看動態鏈接的過程,當需要使用一個 Native 庫(.so 文件)的時候,我們需要調用dlopen("libname.so")來加載這個庫。
在我們調用了dlopen("libname.so")之后,系統首先會檢查緩存中已加載的 ELF 文件列表。如果未加載則執行加載過程,如果已加載則計數加一,忽略該調用。然后系統會用從 libname.so 的dynamic節區中讀取其所依賴的庫,按照相同的加載邏輯,把未在緩存中的庫加入加載列表。
你可以使用下面這個命令來查看一個庫的依賴:
~~~text
readelf -d <library> | grep NEEDED
~~~
下面我們大概了解一下系統是如何加載的 ELF 文件的。
* 讀 ELF 的程序頭部表,把所有 PT\_LOAD 的節區 mmap 到內存中。
* 從“.dynamic”中讀取各信息項,計算并保存所有節區的虛擬地址,然后執行重定位操作。
* 最后 ELF 加載成功,引用計數加一。
但是這里有一個關鍵點,在 ELF 文件格式中我們只有函數的絕對地址。如果想在系統中運行,這里需要經過重定位。這其實是一個比較復雜的問題,因為不同機器的 CPU 架構、加載順序不同,導致我們只能在運行時計算出這個值。不過還好動態加載器(/system/bin/linker)會幫助我們解決這個問題。
如果你理解了動態鏈接的過程,我們再回頭來思考一下“.got”和“.plt”它們的具體含義。
* **The Global Offset Table (GOT)**。簡單來說就是在數據段的地址表,假定我們有一些代碼段的指令引用一些地址變量,編譯器會引用 GOT 表來替代直接引用絕對地址,因為絕對地址在編譯期是無法知道的,只有重定位后才會得到 ,GOT 自己本身將會包含函數引用的絕對地址。
* **The Procedure Linkage Table (PLT)**。PLT 不同于 GOT,它位于代碼段,動態庫的每一個外部函數都會在 PLT 中有一條記錄,每一條 PLT 記錄都是一小段可執行代碼。一般來說,外部代碼都是在調用 PLT 表里的記錄,然后 PLT 的相應記錄會負責調用實際的函數。我們一般把這種設定叫作“蹦床”(Trampoline)。
PLT 和 GOT 記錄是一一對應的,并且 GOT 表第一次解析后會包含調用函數的實際地址。既然這樣,那 PLT 的意義究竟是什么呢?PLT 從某種意義上賦予我們一種懶加載的能力。當動態庫首次被加載時,所有的函數地址并沒有被解析。下面讓我們結合圖來具體分析一下首次函數調用,請注意圖中黑色箭頭為跳轉,紫色為指針。

* 我們在代碼中調用 func,編譯器會把這個轉化為 func@plt,并在 PLT 表插入一條記錄。
* PLT 表中第一條(或者說第 0 條)PLT\[0\] 是一條特殊記錄,它是用來幫助我們解析地址的。通常在類 Linux 系統,這個的實現會位于動態加載器,就是專欄前面文章提到的 /system/bin/linker。
* 其余的 PLT 記錄都均包含以下信息:
\-- 跳轉 GOT 表的指令(jmp \*GOT\[n\])。-- 為上面提到的第 0 條解析地址函數準備參數。-- 調用 PLT\[0\],這里 resovler 的實際地址是存儲在 GOT\[2\] 。
在解析前 GOT\[n\] 會直接指向 jmp \*GOT\[n\] 的下一條指令。在解析完成后,我們就得到了 func 的實際地址,動態加載器會將這個地址填入 GOT\[n\],然后調用 func。
如果你對上面的這個調用流程還有疑問,你可以參考《GOT 表和 PLT 表》這篇文章,它里面有一張圖非常清晰。

當第一次調用發生后,之后再調用函數 func 就高效簡單很多。首先調用 PLT\[n\],然后執行 jmp \*GOT\[n\]。GOT\[n\] 直接指向 func,這樣就高效的完成了函數調用。

總結一下,因為很多函數可能在程序執行完時都不會被用到,比如錯誤處理函數或一些用戶很少用到的功能模塊等,那么一開始把所有函數都鏈接好實際就是一種浪費。為了提升動態鏈接的性能,我們可以使用 PLT 來實現延遲綁定的功能。
對于函數運行的實際地址,我們依然需要通過 GOT 表得到,整個簡化過程如下:

看到這里,相信你已經有了如何 Hack 這一過程的初步想法。這里業界通常會根據修改 PLT 記錄或者 GOT 記錄區分為 GOT Hook 和 PLT Hook,但其本質原理十分接近。
### 成熟方案
GOT/PLT Hook 看似簡單,但是實現起來也是有一些坑的,需要考慮兼容性的情況。一般來說,推薦使用如下業界的成熟方案。
微信 Matrix 開源庫的ELF Hook,它使用的是 GOT Hook,主要使用它來做性能監控。愛奇藝開源的的xHook,它使用的也是 GOT Hook。Facebook 的PLT Hook。
如果不想深入它內部的原理,我們只需要直接使用這些開源的優秀方案就可以了。因為這種 Hook 方式非常成熟穩定,除了 Hook 線程的創建,我們還有很多其他的使用范例。
* “I/O 優化”中使用matrix-io-canary Hook 文件的操作。
* “網絡優化”中使用 Hook 了 Socket 的相關操作。
這種 Hook 方法也不是萬能的,因為它只能替換導入函數的方式。有時候我們不一定可以找到這樣的外部調用函數。如果想 Hook 函數的內部調用,這個時候就需要用到我們的 Trap Hook 或者 Inline Hook 了。
,并且無法Hook 未導出的私有函數,而且只存在安裝與卸載 2 種狀態,一旦安裝就會 Hook 所有函數調用。
## Trap Hook
Trap Hook 最為穩定,但由于需要切換運行模式(R0/R3),且依賴內核的信號機制,導致性能很差。
## Inline Hook
Inline Hook 是一個非常激進的方案,有很好的性能,并且也沒有 PLT 作用域的限制,可以說是一個非常靈活、完美的方案。但其實現難度極高,我至今也沒有看到可以部署在生產環境的 Inline Hook 方案,因為涉及指令修復,需要編譯器的各種優化。
但是需要注意,無論是哪一種 Hook 都只能 Hook 到應用自身的進程,我們無法替換系統或其他應用進程的函數執行。
# 參考資料
[Android Native Hook技術你知道多少?](https://zhuanlan.zhihu.com/p/132699875)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺