#### **前言**
在**Android 上進行Hook 需要跨進程操作(暫不知這句話是否正確,待議)**,我們知道在Linux 上的跨進程操作需要Root權限所以目前Hook 技術廣泛地應用在安全類軟件的主動防御上,所見到的Hook 類病毒并不多。
Android 系統在開發中會存在兩種模式,一個是Linux 的Native 模式,而另一個則是建立在虛擬機上的Java 模式。所以,我們在討論Hook 的時候,可想而知**在Android 平臺上的Hook 分為兩種**。一種是**Java 層級的Hoo**k ,另一種則是**Native 層級的Hook**。兩種模式下,我們通常能夠通過使用JNI 機制來進行調用。但我們知道,在Java 中我們能夠使用native 關鍵字對C/C++代碼進行調用,但是在C/C++中卻很難調用Java 中的代碼。所以,我們**能夠在Java 層級完成的事基本也不會在Native 層去完成**。
#### **什么是Hook 技術**
Hook是“鉤子”的意思,在Android 操作系統中系統維護著自己的一套事件分發機制。應用程序,包括應用觸發事件和后臺邏輯處理,也是根據事件流程一步步地向下執行。而“鉤子”的意思,就是在事件傳送到終點前截獲井監控事件的傳輸,像個鉤子鉤上事件一樣,并且能夠在鉤上事件時,處理一些自己特定的事件。較為形象的流程如下圖所示。
:-: 
* Hook 的這個本領,使它能夠將自身的代碼“融入”被勾住( Hook )的程序的進程中,成為目標進程的一個部分。
* 在Android 系統中使用了沙箱機制,普通用戶程序的進程空間都是獨立的,程序的運行彼此間都不受干擾。
* 根據Hook 對象與Hook 后處理的事件方式不同, Hook 還分為不同的種類,如消息Hook 、APIHook 等。
#### **Hook 原理**
Hook技術無論對安全軟件還是惡意軟件都是十分關鍵的一項技術,其**本質就是劫持函數調用**。但是由于處于Linux 用戶態,每個進程都有自己獨立的進程空間,所以必須先注入到所要Hook 的進程空間,修改其內存中的進程代碼,替換其過程表的符號地址。在Android 中一般是通過ptrace函數附加進程,然后向遠程進程注人so 庫,從而達到監控以及遠程進程關鍵函數掛鉤。
**ptrace 函數**
為方便應用軟件的開發和調試,從 Unix 的早期版本開始就提供了一種對運行中的進程進行跟蹤和控制的手段,那就是系統調用 ptrace()。通過 ptrace(),一個進程可以動態地讀/寫另一個進程的內存和寄存器,包括其指令空間、數據空間、堆棧以及所有的寄存器。與信號機制(以及其它手段)相結合, 就可以實現讓一個進程在另一個進程的控制和跟蹤下運行的目的。
GNU 的調試工具gdb 就是一個典型的實例。通過gdb,軟件開發人員可以使一個應用程序在gdb的“監視”和操縱下受控地運行。對于受gdb 控制的進程,可以通過在其程序中設置斷點,檢查其堆校以確定函數調用路線,檢查并改變局部變 量或全局變量的內容等等方法,來進行調試。顯然,所有這些手段從概念上說都確實屬于進程間“通信”的范疇,但是必須指出,這只是為軟件調試而設計和設立的,**不應該用于一般的進程間通信**。**一 般而言,通信是要由雙方都介入且互相協調才能完成的**。就拿“管道”來說,雖然管道是單向的,但 一定得由一方寫,另一方讀才能達到目的。再拿信號來說,雖然信號是異步的,也就是接收信號的一 方并不知道信號會在什么時候到來,因而在應用程序中并不主動有意地去檢查有否信號到達。但是從 總體而言,接收方知道信號可能會到來,并且為此在應用程序中作出了安排。而當信號真的到來時, 接收方也“知道”其到來,并根據事先的安排作出反應。然而,**由 ptrace()所實現的“通信”卻完全是單方面的,被跟蹤的進程甚至并不知道(從應用程序的角度而言)自己是在受到控制和監視的條件下運行。從這個角度講,ptrace()其實又不屬于“進程間通信”**。
ptrace 提供了一種**使父進程得以監視和控制其他進程的方式,它還能夠改變子進程巾的寄存器和內核映像**,因而可以實現斷點調試和系統調用的跟蹤。使用ptrace ,你可以在用戶層攔截和修改系統調用(這個和 Hook 所要達到的目的類似),父進程還可以便子進程繼續執行,并選擇是再忽略引起終止的信號。
* ptrace 函數定義如下所示:
~~~
int ptrace(int request, int pid, int addr, int data);
~~~
參數含義如下:
* request 是請求ptrace 執行的操作,
* pid 是口標進程的ID 。進程號,指明了操作的對象
* addr 是目標進程的地址值
* data 是作用的數據。
而 request,則是具體的操作,文件`include/linux/ptrace.h`中定義了所有可能的操作碼:
我們查看ptrace.h 源碼會發現有很多路徑下都有這個頭文件,但是里面的參數隨著內核版本的升級,會有所不同。
~~~
#define PTRACE_TRACEME 0
#define PTRACE_PEEKTEXT 1
#define PTRACE_PEEKDATA 2
#define PTRACE_PEEKUSR 3
#define PTRACE_POKETEXT 4
#define PTRACE_POKEDATA 5
#define PTRACE_POKEUSR 6
#define PTRACE_CONT 7
#define PTRACE_KILL 8
#define PTRACE_SINGLESTEP 9
#define PTRACE_ATTACH 0x10
#define PTRACE_DETACH 0x11
#define PTRACE_SYSCALL 24
~~~
跟蹤者(如gdb)先要通過 PTRACE_ATTACH 與被跟蹤進程建立起關系,或者說“ Attach,到被 跟蹤進程。然后,就可以通過各種 PEEK 和 POKE 操作來讀/寫被跟蹤進程的指令空間、數據空間或 者各個寄存器,每次都是一個長字,由 addr 指明其地址:或者,也可以通過 PTRACE_SINGLESTEP、PTRACE_KILL,PTRACE_SYSCALL 和 PTRACE_CONT 等操作來控制被跟蹤進程的運行。最后,通 過 PTRACE_DETACH跟被跟蹤進程脫離關系。所有這些操作都是單方面的,被跟蹤進程既不能拒絕, 也無需“合作”。惟一例外是 PTRACE_TRACEME,用來主動接受跟蹤。
對于ptrace 來說,它的第一個參數決定ptrace 會執行什么操作常用的有跟蹤指定的進程(PTRACE ATTACH)、結束跟蹤指定進程(PTRACE DETACH)等。詳細的參數與使用方式如下圖所示:
:-: 


關于ptrace函數可以參考以下文章:
* [linux 分析 ptrace()](http://blog.sina.com.cn/s/blog_4ac74e9a0100n7w1.html)
* [ptrace運行原理及使用詳解](http://blog.csdn.net/edonlii/article/details/8717029)
* [使用ptrace向已運行進程中注入.so并執行相關函數](http://blog.csdn.net/MyArrow/article/details/9630377)
* [玩轉ptrace(利用ptrace下斷、單步調試、修改代碼執行、代碼注入等)](http://blog.csdn.net/beyond702/article/details/50856559)
* [Android ptrace簡介](http://blog.csdn.net/myarrow/article/details/9617673)
* [Android平臺的 Ptrace, 注入, Hook 全攻略](http://blog.csdn.net/heikefangxian23/article/details/51579857)
* * * * *
* Android 系統本身就提供給了我們兩種開發模式,基于AndroidSDK的Java 語言開發,基于AndroidNDK 的Native C/C++語言開發。
* 對于Native 層來說Hook 的難點其實是在理解ELF(Executable and Linking Format) 文件與學習ELF文件上,特別是對ELF 文件不太了解的讀者來說;
* 對于Java 層來說, Hook 就需要了解虛擬機的特性與Java 上反射的使用。
**ELF文件(目標文件)格式主要三種**:
* **可重定向文件**:文件保存著代碼和適當的數據,用來和其他的目標文件一起來創建一個可執行文件或者是一個共享目標文件。(目標文件或者靜態庫文件,即linux通常后綴為.a和.o的文件)
* **可執行文件**:文件保存著一個用來執行的程序。(例如bash,gcc等)
* **共享目標文件**:**共享庫**。文件保存著代碼和合適的數據,用來被下連接編輯器和動態鏈接器鏈接。(linux下后綴為.so的文件)
一般的 ELF 文件包括三個索引表:
* **ELF header**:在文件的開始,保存了路線圖,描述了該文件的組織情況。
* **Program header table**:告訴系統如何創建進程映像。用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表。
* **Section header table** :包含了描述文件節區的信息,每個節區在表中都有一項,每一項給出諸如節區名稱、節區大小這類信息。用于鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。
關于ELF文件格式可以參考以下文章:
* [Linux內核分析——ELF文件格式分析](https://www.cnblogs.com/20135223heweiqin/p/5554922.html)
* [Android平臺ELF文件格式](http://mp.weixin.qq.com/s?src=11×tamp=1516065448&ver=639&signature=8LqsNqGYgpH7immQVbs1UEPftiU1a5baT-UlDOur5DQcdDE97lY7Vt0SW1zAkaeqq-22i4XhXiJaMYnuFSixRFgui2JoOKZndJ0ThQ3MmhY0ci7obpNVsUP5W*kie1zo&new=1)
* [可執行文件(ELF)格式的理解](http://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.html)
* [ELF文件格式詳解](http://blog.csdn.net/tenfyguo/article/details/5631561)
* [Android逆向之旅---SO(ELF)文件格式詳解](http://blog.csdn.net/jiangwei0910410003/article/details/49336613)
* [Elf文件格式](http://download.csdn.net/download/jiangwei0910410003/9204051#)
* [elf文件格式分析](http://blog.csdn.net/wu5795175/article/details/7657580)
#### **Hook 工作流程**
Hook 的原理就是改變目標函數的指向,原理看起來并不復雜,但是實現起來卻不是那么的簡單。
這里我們將問題細分為兩個
* 如何注入代碼?
* 需要注入的代碼我們存放在哪里?
* 如何注入代碼?
* 如何注人動態鏈接庫?
* 我們不能只在自己的進程載入動態鏈接庫,如何使進程附著上目標進程?
* 如何讓目標進程調用我們的動態鏈接庫函數?
對于**進程附著**, Android 的內核中有一個函數叫ptrace,它能夠動態地attach (跟蹤一個目標進程)、detach (結束跟蹤一個目標進程)、peektext (獲取內存字節)、poketext (向內存寫入地址)等,它能夠滿足我們的需求。而Android 巾的另一個內核函數dlopen ,能夠以指定模式打開指定的動態鏈接庫文件。對于程序的指向流程,我們可以調用ptrace 讓PC 指向LR 堆找。 最后調用,對目標進程調用diopen 則能夠將我們希望注入的動態庫注入至目標進程中。
對于**代碼的注入**( Hook API ),我們可以使用mmap 函數分配一段臨時的內存來完成代碼的存放。
>[info] 對于目標進程中的mmap 函數地址的尋找與Hook API 函數地址的尋找都需要通過目標進程的虛擬地址空間解析與ELF 文件解析來完成,具體算法如下。
> * 通過讀取`/proc/<PID>/maps` 文件找到鏈接庫的基地址。
> * 讀取動態庫.解析ELF 文件,找到符號(需要對ELF 文件格式的深入理解)。
> * 計算目標函數的絕對地址。
**目標進程函數絕對地址=函數地址+動態庫基地址**
向目標進程中注入代碼總結后的步驟分為以下幾步。
* ( I )用ptrace 函數attach 上目標進程。
* ( 2 )發現裝載共享庫so 函數。
* ( 3 )裝載指定的 .so。
* ( 4 )讓目標進程的執行流程跳轉到注入的代碼執行。
* ( 5 )使用ptrace 函數的detach 釋放目標進程。
對應的工作原理流程如下圖3 所示。
:-: 
#### **Hook 的種類**
Hook ,也就是平時我們所說的**函數掛鉤、函數注入、函數劫持等操作**。
* 針對Android 操作系統,根據API Hook 對應API 不一樣我們可以分為
* 使用Android SDK 開發環境的Java API Hook
* 使用Android NDK 開發環境的Native API Hook 。
* 對于Android 中so 庫文件的函數Hook ,根據ELF 文件的特性能分為
* Got 表Hook
* Sym 表Hook
* inline Hook 等。
* 根據Hook 方式的應用范圍我們在Android這樣一個特殊的環境中還能分別出
* 全局Hook
* 單個應用程序Hook。
* 對于Hook 程序的運行環境不同,還可以分為
* 用戶級API Hook
用戶級APIHook 主要是針對在操作系統上為用戶所提供的API 函數方法進行重定向修改
* 內核級API Hook
內核級API Hook 則是針對Android 內核Linux 系統提供的內核驅動模式造成的函數重定向,多數是應用在Rootkit 中。
**Java 層API Hook**
**通過對Android 平臺的虛擬機注入與Java 反射的方式,來改變Android 虛擬機調用函數的方式(ClassLoader),從而達到Java 函數重定向的目的**。這里我們將此類操作稱為Java API Hook。
>[warning] **注意**:因為是根據Java 中的發射機制來重定向函數的,那么很多Java 中反射出現的問題也會在此出現,如無法反射調用關鍵字為native 的方法函數(開q 實現的函數),基本類型的靜態常量無法反射修改等。
**Native 層So 庫Hook**
**主要是針對使用NDK開發出來的so 庫文件的函數重定向,其中也包括對Android 操作系統底層的Linux 函數重定向**,如使用so 庫文件( ELF 格式文件)中的全局偏移表GOT 表或符號表SYM 表進行修改從而達到的函數重定向,我們有可以對其稱為GOT Hook 和SYM Hook。針對其中的inline 函數(內聯函數)的Hook 稱為inline Hook。
**全局Hook**
在Android 系統中,應用程序進程都是由Zygote 進程孵化出來的,而Zygote 進程是由init 進程啟動的。Zygote 進程在啟動時會創建一個Dalvik 虛擬機實例,每當它孵化一個新的應用程序進程時,都會將這個Dalvik 虛擬機實例復制到新的應用程序進程里面去從而使每一個應用程序進程都有一個獨立的Dalvik 虛擬機實例。所以**如果選擇對Zygote 進程Hook ,則能夠達到針對系統上所有的應用程序進程Hook ,即一個全局Hook**。
:-: 
而**對應的app_process 正是zygote 進程啟動一個應用程序的入口**,常見的Hook 框架Xposed與Cydiasubstrate 也是**通過替換app_process 來完成全局Hook** 的。
#### **Hook 的危害**
API Hook 技術是一種用于改變API 執行結果的技術,能夠將系統的API 函數執行重定向。一個應用程序調用的函數方法被第三方Hook 重定向后,其程序執行流程與執行結果是無法確認的,更別提程序的安全性了。而Hook 技術的出現并不是為病毒和惡意程序服務的, Hook 技術更多的是應用在安全管理軟件上面。但是無論怎么說,**已經被Hook 后的應用程序,就毫無安全可言了**。
#### **參考文章:**
[《Android安全技術揭秘與防范》—第8章8.節什么是Hook技術](https://yq.aliyun.com/articles/99809?spm=5176.100239.blogcont99909.17.6fd61614wfPkj8#)
- 前言
- Android系統的體系結構
- Dalvik VM 和 JVM 的比較
- Android 打包應用程序并安裝的過程
- Android ADB工具
- Android應用開發
- Android UI相關知識總結
- Android 中window 、view、 Activity的關系
- Android應用界面
- Android中的drawable和bitmap
- AndroidUI組件adapterView及其子類和Adapter的關系
- Android四大組件
- Android 數據存儲
- SharedPreference
- Android應用的資源
- 數組資源
- 使用Drawable資源
- Material Design
- Android 進程和線程
- 進程
- 線程
- Android Application類的介紹
- 意圖(Intent)
- Intent 和 Intent 過濾器(Google官網介紹)
- Android中關于任務棧的總結
- 任務和返回棧(官網譯文)
- 總結
- Android應用安全現狀與解決方案
- Android 安全開發
- HTTPS
- 安卓 代碼混淆與打包
- 動態注入技術(hook技術)
- 一、什么是hook技術
- 二、常用的Hook 工具
- Xposed源碼剖析——概述
- Xposed源碼剖析——app_process作用詳解
- Xposed源碼剖析——Xposed初始化
- Xposed源碼剖析——hook具體實現
- 無需Root也能Hook?——Depoxsed框架演示
- 三、HookAndroid應用
- 四、Hook原生應用程序
- 五、Hook 檢測/修復
- Android 應用的逆向與加固保護技術
- OpenCV在Android中的開發
- Android高級開發進階
- 高級UI
- UI繪制流程及原理
- Android新布局ConstraintLayout約束布局
- 關鍵幀動畫
- 幀動畫共享元素變換
- Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解
- Android中為什么主線程不會因為Looper.loop()里的死循環卡死?
- 為什么 Android 要采用 Binder 作為 IPC 機制?
- JVM 中一個線程的 Java 棧和寄存器中分別放的是什么?
- Android源碼的Binder權限是如何控制?
- 如何詳解 Activity 的生命周期?
- 為什么Android的Handler采用管道而不使用Binder?
- ThreadLocal,你真的懂了嗎?
- Android屏幕刷新機制