Android Native Hook工具實踐
參考文章
Android Native Hook 工具實踐 https://paper.seebug.org/650/
ARM64下的Android Native Hook工具實踐 - Galaxy Lab http://galaxylab.org/arm64下的android-native-hook工具實踐/
它的這個工具實際是借鑑的諾神的inlinehook
0x01 inlinehook的原理
器對應的實現的文章如下:https://gtoad.github.io/2018/07/13/Android-Inline-Hook-Fix/
1. Arm模式與Thumb模式的區別
Arm指令為4位元組對齊,每條指令長度均為32位;
Thumb指令為2位元組對齊,又分為Thumb16、Thumb32,其中Thumb16指令長度為16位,Thumb32指令長度為32位。
// 設定bit[0]的值為1
#define SET_BIT0(addr) (addr | 1)
// 設定bit[0]的值為0
#define CLEAR_BIT0(addr) (addr & 0xFFFFFFFE)
// 測試bit[0]的值,若為1則返回真,若為0則返回假
#define TEST_BIT0(addr) (addr & 1)
2. 跳轉指令的構造
跳轉指令主要分為以下兩種: B系列指令:B、BL、BX、BLX 直接寫PC暫存器
Arm的B系列指令跳轉範圍只有4M,Thumb的B系列指令跳轉範圍只有256位元組,然而大多數情況下跳轉範圍都會大於4M,故我們採用LDR PC, [PC, ?]構造跳轉指令。
3. PC相關指令的修正
不論是Arm指令集還是Thumb指令集,都存在很多的與PC值相關的指令,例如:B系列指令、literal系列指令等。原有函式的前幾個被跳轉指令替換的指令將會被搬移到trampoline_instructions中,此時PC值已經變動,所以需要對PC相關指令進行修正(所謂修正即為計算出實際地址,並使用其他指令完成同樣的功能)。相關修正程式碼位於relocate.c檔案中。其中INSTRUCTION_TYPE描述了需要修正的指令,限於篇幅,這裡僅闡述Arm指令的修正過程,對應的程式碼為relocateInstructionInArm函式。
函式原型如下:
/*
target_addr: 待Hook的目標函式地址,即為當前PC值,用於修正指令
orig_instructions:存放原有指令的首地址,用於修正指令和後續對原有指令的恢復
length:存放的原有指令的長度,Arm指令為8位元組;Thumb指令為12位元組
trampoline_instructions:存放修正後指令的首地址,用於呼叫原函式
orig_boundaries:存放原有指令的指令邊界(所謂邊界即為該條指令與起始地址的偏移量),用於後續執行緒處理中,對PC的遷移
trampoline_boundaries:存放修正後指令的指令邊界,用途與上相同
count:處理的指令項數,用途與上相同
*/
static void relocateInstructionInArm(uint32_t target_addr, uint32_t *orig_instructions, int length, uint32_t *trampoline_instructions, int *orig_boundaries, int *trampoline_boundaries, int *count);
具體實現中,首先通過函式getTypeInArm判斷當前指令的型別,本函式通過型別,共分為4個處理分支:
BLX_ARM、BL_ARM、B_ARM、BX_ARM
ADD_ARM
ADR1_ARM、ADR2_ARM、LDR_ARM、MOV_ARM
其他指令
4. 執行緒處理
http://ele7enxxh.com/Analysis-Of-Backtrace-And-Inline-Hook-Thread-Safety-On-The-ARM-Platform.html
通過backtrace來判斷,
一個完善的Inline Hook方案必須要考慮多執行緒環境,即要考慮執行緒恰好執行到被修改指令的位置。在Window下,使用GetThreadContext和SetThreadContext列舉所有執行緒,遷移context到搬遷後的指令中。然而在Linux+Arm環境下,並沒有直接提供相同功能的API,不過可以使用ptrace完成,主要流程如下:
解析/proc/self/task目錄,獲取所有執行緒id
建立子程序,父程序等待。子程序列舉所有執行緒,PTRACE_ATTACH執行緒,遷移執行緒PC暫存器,列舉完畢後,子程序給自己發SIGSTOP訊號,等待父程序喚醒
父程序檢測到子程序已經SIGSTOP,完成Inline Hook工作,向子程序傳送SIGCONT訊號,同時等待子程序退出
子程序列舉所有執行緒,PTRACE_DETACH執行緒,列舉完畢後,子程序退出
父程序繼續其他工作
這裡使用子程序完成執行緒處理工作,實際上是迫不得已的。因為,如果直接使用本程序PTRACE_ATTACH執行緒,會出現operation not permitted,即使賦予root許可權也是同樣的錯誤,具體原因不得而知。
具體程式碼請參考freeze與unFreeze兩個函式。
5. 其他一些細節
頁保護
頁面大小為4096位元組,使用mprotect函式修改頁面屬性,修改為PROT_READ | PROT_WRITE | PROT_EXEC。
重新整理快取
對於ARM處理器來說,快取機制作用明顯,記憶體中的指令已經改變,但是cache中的指令可能仍為原有指令,所以需要手動重新整理cache中的內容。採用cacheflush即可實現。
一個已知的BUG
雖然本庫已經把大部分工作放在了registerInlineHook函式中,但是在inlineHook、inlineUnHook函式中還是不可避免的使用了部分libc庫的API函式,例如:mprotect、memcpy、munmap、free、cacheflush等。如果使用本庫對上述API函式進行Hook,可能會失敗甚至崩潰,這是因為此時原函式的指令已經被破壞,或者其邏輯已經改變。解決這個Bug有兩個方案,第一是採用其他Hook技術;第二將本庫中的這些API函式全部採用內部實現,即不依賴於libc庫,可採用靜態連結libc庫,或者使用匯編直接調相應的系統呼叫號。