Android inline hook手记
2019獨角獸企業重金招聘Python工程師標準>>>
許久沒搞安全方面的東西了。最近有些時間看了看android下面的開發,看去看來總是想往內深入,結果又往Native和內核挖過去了……
研究了一下ARM架構下面的inline實現,網上搜了搜似乎沒有找到多少資料。想了想還是寫出來吧,當作筆記。由于時間不多,也不是為工作而看, 僅僅是做了粗略的實現和研究,并沒有深入下去。這篇手記的目的不在于詳細描述整個HOOK實現的過程,而僅僅對一些關鍵點作一些描述。
說到Inline hook,了解這個詞的同志們都應該知道,無非是修改目標函數處的指令,跳轉到自己的函數,并且提供調用原函數的stub,即可完成整個流程。但是在 ARM下面情況和我們熟悉的x86有所不同。ARM芯片的運行狀態分為arm和thumb兩種模式,分別有不同的指令集,arm指令為定長32 位,thumb指令為定長16位(thumb-2中進行了擴展,可以使用32位thumb指令)。同一段代碼中可以混用兩套指令集,通過一些帶有 interworking功能的跳轉或者load指令可以在兩種模式間切換。做ARM下的inline,首先遇到的就是指令模式的問題。另外,ARM架構 下,CPU也具有分開的指令緩存和數據緩存,類似x86下的DTLB和ITLB。但是在實現過程中發現,arm的緩存作用非常明顯,而且刷新機制不太確 定,因此自修改代碼需要經常主動控制緩存的刷新。這一點,可以通過NDK的API cacheflush實現。
下面簡單說說一些主要問題:
- 關于頁保護
這一點對于熟悉Linux編程的同志們應該不是問題,mprotect修改為PROT_READ | PROT_WRITE | PROT_EXEC即可。頁面大小可以通過包含ndk下面<asm/page.h>文件,里面定義的一系列宏用于獲得頁面大小和進行對齊運 算。
- 關于模式轉換和跳轉
Arm下主要的分支指令如BX,BLX等,都可以切換指令模式。詳見arm的用戶手冊。這里主要討論模式的選擇和切換時機。只有一個問題需要注 意,arm處理器執行時,由于流水線的關系,會預取兩條指令,因此當前指令取到的pc值,始終是之后第三條指令的地址。比如當前指令地址是0×8000, 那么當前pc的值,在thumb下面是0×8000 + 2 * 2, 在arm下面是0×8000 + 4 * 2。
由于運行時我沒有找到簡便的辦法能夠確切知道被hook的目標函數指令集,所以這個問題留給了hook的使用者來決定。Hook之前應通過逆向工具獲知所有目標函數是arm還是thumb指令。
如果要根據目標函數指令集的不同而對hook函數采用不同的 編譯選項,顯然是一件麻煩的事情。而arm模式的指令由于單條指令包含的語義更多,是我們的首選。因此可以考慮主僅使用arm指令編譯hook函數,而在跳轉的同時切換到arm模式。
關于跳轉插入的指令方面,由于arm指令帶立即數的跳轉范圍只有4M,thumb的跳轉范圍只有256字節。所以首選ldr pc,xxxx指令來實現。對于arm指令的目標,這個指令很容易選擇。如下:
ldr pc, [pc,#-4]
32位跳轉絕對地址
指令為單個32位數字:0xE51FF004。
但是thumb模式下的16位ldr指令沒有辦法向pc中load,選擇就很成問題。如果單純使用16位thumb指令的話,跳轉部分需要占用大量 字節數,而因為arm下面編譯器常常使用pc的值作為基址來計算地址,被搬動過的指令中就極有可能存在這種指令。搬動過后的代碼中就必須對這部分指令進行 修正,而又由于thumb所能夠支持的立即數很小,跳轉范圍也很小,這種修正往往非常麻煩,需要用幾條同等指令來替換一條指令。經過考慮,還是決定放棄對 ARMv5的支持,直接使用ARMv6T2之后支持的thumb-2指令集。thumb-2支持32位thumb指令,也支持ldr以pc為目標寄存器:
ldr.w pc,[pc,#0]
32位跳轉絕對地址
指令為單個32位數字:0x00F0DFF8
所有需要跳轉的地址,需要注意的是bit0的處理。如果bit0為1,跳轉后會切換到thumb指令模式,如果bit0為0,會切換至arm模式。 當目標為arm的時候,我們不需要特殊處理,編譯器會處理地址的計算。但是當目標為thumb的時候,從hook指令跳轉到hook函數,以及調用原始函 數的時候,都需要注意地址bit0的處理。
- 關于搬出來的原始指令
按照win32下Detours庫的實現方式,被HOOK函數的前面幾條指令,會搬到一個trampoline中,并在這些指令后添加跳轉至原代碼 后續部分的指令。在搬動過程中,需要對被移動的指令進行地址修正。在處理ARM平臺inline的過程中也需要作這樣的工作。但是實際上在處理的時候會發 現,要做到這一點是非常困難的。ARM下常常會生成下面這種將pc作為地址參照的指令塊
而由于arm平臺尋址范圍較小,編譯器通常選擇將數據和指令在內存中的存放混雜在一起。thumb模式下,由于指令中能包含的立即數非常小,這種問 題會表現得異常突出,修正的時候也常常一條指令被拉長為數條。因此代碼修正會有非常大的工作量。這部分問題由于太消耗時間,我也僅僅是對arm下的 inline進行研究性實現,也就沒有管這個問題了。實際項目如果要用到hook,這個部分花費的時間應該比單純hook跳轉的實現要大得多。在不考慮并 發和效率的情況下,當hook函數中要調用原函數時,可以考慮臨時恢復hook,并在調用完成后再次hook來解決。但是始終是相當不優雅的實現。
- 關于線程處理
修改hook目標的指令時,和x86平臺下一樣,也需要注意有可能某些線程剛好執行到被修改的指令中的問題。Win32下可以枚舉線程并修改 context到被搬遷的指令中去。但是Linux內核系統下很難進行線程的控制。估計可以采用接管信號處理,并向進程內所有線程調用 pthread_kill來實現。
轉載于:https://my.oschina.net/zhuzihasablog/blog/141872
總結
以上是生活随笔為你收集整理的Android inline hook手记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求最值(最大值和最小值)
- 下一篇: 实验13 简单FTP 程序设计