動態注入技術

咱們在討論動態注入技術的時候,APIHook的技術由來已久,在操做系統未能提供所需功能的狀況下,利用APIHook的手段來實現某種必需的功能也算是一種不得已的辦法。在Windows平臺下開發電子詞典的光標取詞功能,這項功能就是利用Hook API的技術把系統的字符串輸出函數替換成了電子詞典中的函數,從而能獲得屏幕上任何位置的字符串。不管是16位的Windows95,仍是32位的Windws NT,都有辦法向整個系統或特定的目標進程中「注入」DLL動態庫,並替換掉其中的函數。 android

可是在Android上進行Hook須要跨進程操做,咱們知道在Linux上的跨進程操做須要Root權限。因此目前Hook技術普遍地應用在安全類軟件的主動防護上,所見到的Hook類病毒並很少。 git

Android系統在開發中會存在兩種模式,一個是Linux的Native模式,而另外一個則是創建在虛擬機上的Java模式。因此,咱們在討論Hook的時候,可想而知在Android平臺上的Hook分爲兩種。一種是Java層級的Hook,另外一種則是Native層級的Hook。兩種模式下,咱們一般可以經過使用JNI機制來進行調用。但咱們知道,在Java中咱們可以使用native關鍵字對C/C++代碼進行調用,可是在C/C++中卻很難調用Java中的代碼。因此,咱們可以在Java層級完成的事基本也不會在Native層去完成。 github

8.1 什麼是Hook技術

尚未接觸過Hook技術讀者必定會對Hook一詞感受到特別的陌生,Hook英文翻譯過來就是「鉤子」的意思,那咱們在何時使用這個「鉤子」呢?咱們知道,在Android操做系統中系統維護着本身的一套事件分發機制。應用程序,包括應用觸發事件和後臺邏輯處理,也是根據事件流程一步步地向下執行。而「鉤子」的意思,就是在事件傳送到終點前截獲並監控事件的傳輸,像個鉤子鉤上事件同樣,而且可以在鉤上事件時,處理一些本身特定的事件。較爲形象的流程如圖8-1所示。 算法

Hook的這個本領,使它可以將自身的代碼「融入」被勾住(Hook)的程序的進程中,成爲目標進程的一個部分。咱們也知道,在Android系統中使用了沙箱機制,普通用戶程序的進程空間都是獨立的,程序的運行彼此間都不受干擾。這就使咱們但願經過一個程序改變其餘程序的某些行爲的想法不能直接實現,可是Hook的出現給咱們開拓瞭解決此類問題的道路。固然,根據Hook對象與Hook後處理的事件方式不一樣,Hook還分爲不一樣的種類,如消息Hook、API Hook等。 shell

圖8-1 Hook原理圖 api

8.1.1 Hook原理

Hook技術不管對安全軟件仍是惡意軟件都是十分關鍵的一項技術,其本質就是劫持函數調用。可是因爲處於Linux用戶態,每一個進程都有本身獨立的進程空間,因此必須先注入到所要Hook的進程空間,修改其內存中的進程代碼,替換其過程表的符號地址。在Android中通常是經過ptrace函數附加進程,而後向遠程進程注入so庫,從而達到監控以及遠程進程關鍵函數掛鉤。 瀏覽器

Hook技術的難點,並不在於Hook技術,初學者藉助於資料「照葫蘆畫瓢」可以很容易就掌握Hook的基本使用方法。如何找到函數的入口點、替換函數,這就涉及了理解函數的鏈接與加載機制。 安全

從Android的開發來講,Android系統自己就提供給了咱們兩種開發模式,基於Android SDK的Java語言開發,基於AndroidNDK的Native C/C++語言開發。因此,咱們在討論Hook的時候就必須在兩個層面上來討論。對於Native層來講Hook的難點實際上是在理解ELF文件與學習ELF文件上,特別是對ELF文件不太瞭解的讀者來講;對於Java層來講,Hook就須要瞭解虛擬機的特性與Java上反射的使用。 服務器

8.1.1.1 Hook工做流程

以前咱們介紹過Hook的原理就是改變目標函數的指向,原理看起來並不複雜,可是實現起來卻不是那麼的簡單。這裏咱們將問題細分爲兩個,一個是如何注入代碼,另外一個是如何注入動態連接庫。 網絡

注入代碼咱們就須要解決兩個問題。

  • 須要注入的代碼咱們存放在哪裏?
  • 如何注入代碼?

注入動態共享庫咱們也須要解決兩個問題:

  • 咱們不能只在本身的進程載入動態連接庫,如何使進程附着上目標進程?
  • 如何讓目標進程調用咱們的動態連接庫函數?

這裏我也不賣關子了,說一下目前對上述問題的解決方案吧。對於進程附着,Android的內核中有一個函數叫ptrace,它可以動態地attach(跟蹤一個目標進程)、detach(結束跟蹤一個目標進程)、peektext(獲取內存字節)、poketext(向內存寫入地址)等,它可以知足咱們的需求。而Android中的另外一個內核函數dlopen,可以以指定模式打開指定的動態連接庫文件。對於程序的指向流程,咱們能夠調用ptrace讓PC指向LR堆棧。最後調用,對目標進程調用dlopen則可以將咱們但願注入的動態庫注入至目標進程中。

對於代碼的注入(Hook API),咱們可使用mmap函數分配一段臨時的內存來完成代碼的存放。對於目標進程中的mmap函數地址的尋找與Hook API函數地址的尋找都須要經過目標進程的虛擬地址空間解析與ELF文件解析來完成,具體算法以下。

  • 經過讀取 /proc/<PID>/maps文件找到連接庫的基地址。
  • 讀取動態庫,解析ELF文件,找到符號(須要對ELF文件格式的深刻理解)。
  • 計算目標函數的絕對地址。

目標進程函數絕對地址= 函數地址 + 動態庫基地址

上面說了這麼多,向目標進程中注入代碼總結後的步驟分爲如下幾步。

(1)用ptrace函數attach上目標進程。

(2)發現裝載共享庫so函數。

(3)裝載指定的.so。

(4)讓目標進程的執行流程跳轉到注入的代碼執行。

(5)使用ptrace函數的detach釋放目標進程。

對應的工做原理流程如圖8-2所示。

圖像說明文字

圖8-2 基於Ptrace的Hook工做流程

8.1.1.2 ptrace函數

說到了Hook咱們就不能不說一下ptrace函數,ptrace提供了一種使父進程得以監視和控制其餘進程的方式,它還可以改變子進程中的寄存器和內核映像,於是能夠實現斷點調試和系統調用的跟蹤。使用ptrace,你能夠在用戶層攔截和修改系統調用(這個和Hook所要達到的目的相似),父進程還可使子進程繼續執行,並選擇是否忽略引發終止的信號。

ptrace函數定義以下所示:

int ptrace(int request, int pid, int addr, int data);
  • request是請求ptrace執行的操做。
  • pid是目標進程的ID。
  • addr是目標進程的地址值。
  • data是做用的數據。

對於ptrace來講,它的第一個參數決定ptrace會執行什麼操做。經常使用的有跟蹤指定的進程(PTRACE_ATTACH)、結束跟蹤指定進程(PTRACE_DETACH)等。詳細的參數與使用方式如表8-1所示。

表8-1 ptrace函數使用詳情表

參數與形式

說明

ptrace(PTRACE_TRACEME,0 ,0 ,0)

本進程被其父進程所跟蹤。其父進程應該但願跟蹤子進程

ptrace(PTRACE_PEEKTEXT, pid, addr, data) ptrace(PTRACE_PEEKDATA, pid, addr, data)

從內存地址中讀取一個字節,pid表示被跟蹤的子進程,內存地址由addr給出,data爲用戶變量地址用於返回讀到的數據

ptrace(PTRACE_POKETEXT, pid, addr, data) ptrace(PTRACE_POKEDATA, pid, addr, data)

往內存地址中寫入一個字節。pid表示被跟蹤的子進程,內存地址由addr給出,data爲所要寫入的數據

ptrace(PTRACE_PEEKUSR, pid, addr, data)

從USER區域中讀取一個字節,pid表示被跟蹤的子進程,USER區域地址由addr給出,data爲用戶變量地址用於返回讀到的數據。USER結構爲core文件的前面一部分,它描述了進程停止時的一些狀態,如寄存器值,代碼、數據段大小,代碼、數據段開始地址等

ptrace(PTRACE_POKEUSR, pid, addr, data)

往USER區域中寫入一個字節,pid表示被跟蹤的子進程,USER區域地址由addr給出,data爲需寫入的數據

ptrace(PTRACE_CONT, pid, 0, signal)

繼續執行。pid表示被跟蹤的子進程,signal爲0則忽略引發調試進程停止的信號,若不爲0則繼續處理信號signal

ptrace(PTRACE_SYS, pid, 0, signal)

繼續執行。pid表示被跟蹤的子進程,signal爲0則忽略引發調試進程終止的信號,若不爲0則繼續處理信號signal。與PTRACE_CONT不一樣的是進行系統調用跟蹤。在被跟蹤進程繼續運行直到調用系統調用開始或結束時,被跟蹤進程被終止,並通知父進程

ptrace(PTRACE_KILL,pid)

殺掉子進程,使它退出。pid表示被跟蹤的子進程

ptrace(PTRACE_KILL, pid, 0, signle)

設置單步執行標誌,單步執行一條指令。pid表示被跟蹤的子進程。signal爲0則忽略引發調試進程停止的信號,若不爲0則繼續處理信號signal。當被跟蹤進程單步執行完一個指令後,被跟蹤進程被終止,並通知父進程

ptrace(PTRACE_ATTACH,pid)

跟蹤指定pid 進程。pid表示被跟蹤進程。被跟蹤進程將成爲當前進程的子進程,並進入終止狀態

ptrace(PTRACE_DETACH,pid)

結束跟蹤。pid表示被跟蹤的子進程。結束跟蹤後被跟蹤進程將繼續執行

ptrace(PTRACE_GETREGS, pid, 0, data)

讀取寄存器值,pid表示被跟蹤的子進程,data爲用戶變量地址用於返回讀到的數據。此功能將讀取全部17個基本寄存器的值

ptrace(PTRACE_SETREGS, pid, 0, data)

設置寄存器值,pid表示被跟蹤的子進程,data爲用戶數據地址。此功能將設置全部17個基本寄存器的值

ptrace(PTRACE_GETFPREGS, pid, 0, data)

讀取浮點寄存器值,pid表示被跟蹤的子進程,data爲用戶變量地址用於返回讀到的數據。此功能將讀取全部浮點協處理器387的全部寄存器的值

ptrace(PTRACE_SETREGS, pid, 0, data)

設置浮點寄存器值,pid表示被跟蹤的子進程,data爲用戶數據地址。此功能將設置全部浮點協處理器387的全部寄存器的值

8.1.2 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的原理以及這些Hook方式給咱們使用Hook帶來的便利性。

TIPS

 對於Hook程序的運行環境不一樣,還能夠分爲用戶級API Hook與內核級API Hook。用戶級API Hook主要是針對在操做系統上爲用戶所提供的API函數方法進行重定向修改。而內核級API Hook則是針對Android內核Linux系統提供的內核驅動模式形成的函數重定向,多數是應用在Rootkit中。

8.1.2.1 Java層API Hook

經過對Android平臺的虛擬機注入與Java反射的方式,來改變Android虛擬機調用函數的方式(ClassLoader),從而達到Java函數重定向的目的。這裏咱們將此類操做稱爲Java API Hook。由於是根據Java中的發射機制來重定向函數的,那麼不少Java中反射出現的問題也會在此出現,如沒法反射調用關鍵字爲native的方法函數(JNI實現的函數),基本類型的靜態常量沒法反射修改等。

8.1.2.2 Native層So庫Hook

主要是針對使用NDK開發出來的so庫文件的函數重定向,其中也包括對Android操做系統底層的Linux函數重定向,如使用so庫文件(ELF格式文件)中的全局偏移表GOT表或符號表SYM表進行修改從而達到的函數重定向,咱們有能夠對其稱爲GOT Hook和SYM Hook。針對其中的inline函數(內聯函數)的Hook稱爲inline Hook。

8.1.2.3 全局Hook

針對Hook的不一樣進程來講又能夠分爲全局Hook與單個應用程序進程Hook,咱們知道在Android系統中,應用程序進程都是由Zygote進程孵化出來的,而Zygote進程是由Init進程啓動的。Zygote進程在啓動時會建立一個Dalvik虛擬機實例,每當它孵化一個新的應用程序進程時,都會將這個Dalvik虛擬機實例複製到新的應用程序進程裏面去,從而使每個應用程序進程都有一個獨立的Dalvik虛擬機實例。因此若是選擇對Zygote進程Hook,則可以達到針對系統上全部的應用程序進程Hook,即一個全局Hook。對比效果如圖8-3所示。

圖像說明文字

圖8-3 Hook前和Hook後的對比

而對應的app_process正是zygote進程啓動一個應用程序的入口,常見的Hook框架Xposed與Cydiasubstrate也是經過替換app_process來完成全局Hook的。

8.1.3 Hook的危害

API Hook技術是一種用於改變API執行結果的技術,可以將系統的API函數執行重定向。一個應用程序調用的函數方法被第三方 Hook 重定向後,其程序執行流程與執行結果是沒法確認的,更別提程序的安全性了。而Hook技術的出現並非爲病毒和惡意程序服務的,Hook技術更多的是應用在安全管理軟件上面。可是不管怎麼說,已經被Hook後的應用程序,就毫無安全可言了。

8.2 經常使用的Hook工具

在平常工做學習中,咱們但願使用Hook技術來完成某功能實際上是至關煩瑣的,但也並非不可能的。咱們這裏沒有手動地從新書寫一個Hook工具,而是使用到了第三方提供的框架來作演示。Android的Hook技術雖然發展不久,可是也出現了不少的Hook框架工具。本節咱們就具體介紹一下目前經常使用到的Hook框架。

8.2.1 Xposed框架

Xposed框架是一款能夠在不修改APK的狀況下影響程序運行(修改系統)的框架服務,經過替換/system/bin/app_process 程序控制 zygote 進程,使 app_process 在啓動過程當中加載XposedBridge.jar 這個jar包,從而完成對Zygote進程及其建立的Dalvik虛擬機的劫持。基於Xposed框架能夠製做出許多功能強大的模塊,且在功能不衝突的狀況下同時運做。此外,Xposed框架中的每個庫還能夠單獨下載使用,如Per App Setting(爲每一個應用設置單獨的dpi或修改權限)、Cydia、XPrivacy(防止隱私泄露)、BootManager(開啓自啓動程序管理應用),對原生Launcher替換圖標等應用或功能均基於此框架。

官網地址:http://repo.xposed.info/

源碼地址:https://github.com/rovo89

Xposed框架是基於一個Android的本地服務應用XposedInstaller與一個提供API的jar文件來完成的。因此,安裝使用Xposed框架咱們須要完成如下幾個步驟。

● 安裝本地服務XposedInstaller

須要安裝XposedInstall.apk本地服務應用,咱們可以在其官網的framework欄目中找到,下載並安裝。地址爲:

http://repo.xposed.info/module/de.robv.android.xposed.installer

安裝好後進入XposedInstaller應用程序,會出現須要激活框架的界面,如圖8-5所示。這裏咱們點擊「安裝/更新」就能完成框架的激活了。部分設備若是不支持直接寫入的話,能夠選擇「安裝方式」,修改成在Recovery模式下自動安裝便可。

Home

圖8-4 Xposed框架Logo

圖片 1946

圖8-5 XposedInstall應用激活界面

由於安裝時會須要Root權限,安裝後會啓動Xposed的app_process,因此安裝過程當中會存在設備屢次從新啓動。

TIPS

 因爲國內的部分ROM對Xposed不兼容,若是安裝Xposed不成功的話,強制使用Recovery寫入可能會形成設備反覆重啓而沒法正常啓動。

● 下載使用API庫

其 API 庫XposedBridgeApi-<version>.jar(version 是 XposedAPI 的版本號,如咱們這裏是XposedBridgeApi-54.jar)文件,咱們可以在Xposed的官方支持xda論壇找到,其地址爲:

http://forum.xda-developers.com/xposed/xposed-api-changelog-developer-news-t2714067

下載完畢後咱們須要將 Xposed Library 複製到 lib目錄(注意是 lib 目錄,不是Android提供的 libs 目錄),而後將這個 jar 包添加到 Build PATH 中,效果如圖8-6所示。

圖片 1949

圖8-6 Android項目中的lib與libs目錄截圖

若是直接將jar包放置到了libs目錄下,極可能會產生錯誤「IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation」。估計Xposed做者在其框架內部也引用了BridgeApi,這樣操做能夠避免重複引用。

8.2.2 CydiaSubstrate框架

若是使用過蘋果手機的用戶應該對Cydiasubstrate框架一點都不會陌生,由於Cydiasubstrate框架爲蘋果用戶提供了越獄相關的服務框架。Cydiasubstrate原名MobileSubstrate(類庫中都是以MS開頭的),做者爲大名鼎鼎的Jay Freeman(saurik)。固然Cydiasubstrate 也推出了Android版。Cydia Substrate是一個代碼修改平臺。它能夠修改任何主進程的代碼,無論是用Java仍是C/C++(native代碼)編寫的。而Xposed只支持HOOK app_process中的 Java 函數,所以 Cydiasubstrate 是一款強大而實用的 HOOK工具。

http://www.cydiasubstrate.com/logo.png

圖8-7 CydiaSubstrate框架Logo

官網地址:http://www.cydiasubstrate.com/

與使用Xposed框架相似,使用Cydiasubstrate框架以前咱們須要配置它的使用環境,對於強大的Cydiasubstrate框架使用其實只須要配置兩個地方。安裝Cydiastrate框架Android本地服務,下載使用Cydiastrate提供的API。

● 安裝Cydiastrate框架Android本地服務

一個就是在Android設備中安裝Cydiasubstrate框架的本地服務應用substrate.apk,咱們能夠在其官網下載到。

官方下載地址爲:http://www.cydiasubstrate.com/download/com.saurik.substrate.apk

固然,咱們安裝substrate後,須要「Link Substrate Files」(鏈接本地的Substrate服務文件),這一步是須要Root權限的,鏈接後還須要重啓設備纔可以生效。Substrate服務設置應用如圖8-8所示。

C:\Users\zhoushengtao\Documents\Tencent Files\520186243\Image\C2C\72DBA5F247D4C1AB522DB16815A2460C.jpg

圖8-8 Substrate應用Link後界面

● 下載使用Cydiasubstrate庫

Cydiasubstrate官方建議以在Android SDK Manager中添加它們插件地址的方式進行更新下載,如圖8-9所示,在用戶自定義網址中添加http://asdk.cydiasubstrate.com/addon.xml

http://www.cydiasubstrate.com/id/73e45fe5-4525-4de7-ac14-6016652cc1b8/added.png

圖8-9 在Android SDK Manager中添加Cydiasubstate地址

經過使用Android SDK Manager工具下載完Cydiasubstrate框架後,其存儲於目錄${ANDROID_ HOME}\sdk\extras\saurikit\cydia_substrate下。可是,因爲Android SDK Manager在國內使用起來存在不少的限制,下載的時候也不是很是穩定,因此仍是建議你們直接去官網下載開發庫。

官方下載地址爲:http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zip

下載完成後,將獲得的全部文件(不少的jar包與so庫),都複製到Android項目下的libs文件夾中,就能夠直接使用了。效果如圖8-10所示。

圖片 151

圖8-10 Android工程中的libs目錄截圖

其中的substrate.h頭文件與lib文件夾下的so文件是提供在使用NDK進行原生Hook程序開發中的函數支持庫。

TIPS

 CydiaSubstrate框架對於inline Hook的操做目前還存在一些bug,使用的時候可能會出現崩潰的現象,部分使用了國內定製的ROM的設備在使用CydiaSubstrate框架時會出現設備沒法從新啓動或沒法Hook的現象。

8.2.3 ADBI/DDI框架

ADBI(全稱爲:Android Dynamic Binary Instrumentation Toolkit)即Android的動態二進制指令工具包,兼容Android中的ARM與Thmub指令,提供動態庫注入與函數Hook(包括inline Hook)。固然,其也提供了Java層的相似功能,即DDI(Dynamic Dalvik Instrumentation Toolkit)框架。

ADBI/DDI框架與Xposed和CydiaSubstrate框架最大的區別是,它是一個命令行工具,使用起來更加的簡單方便。咱們能夠在Github上找到其源碼,地址爲:

ADBI:https://github.com/crmulliner/adbi

DDI:https://github.com/crmulliner/ddi

8.3 HookAndroid應用

前面咱們介紹過Cydiasubstrate框架提供在Java層Hook的能力,其中主要是提供了三個比較重要的方法,MS.hookClassLoad、MS.hookMethod、MS.moveUnderClassLoader。三個方法的具體介紹如表8-2所示。

表8-2 CydiaSubstrate中經常使用到的Java Hook方法

方法名

說明

MS.hookClassLoad

拿到指定Class載入時的通知

MS.hookMethod

使用一個Java方法去替換另外一個Java方法

MS.moveUnderClassLoader

使用不一樣的ClassLoder重載對象

幾個方法的具體參數與返回值,咱們能夠看以下的方法具體定義。

 * Hook一個指定的Class * * @paramname Class的包名+類名,如android.content.res.Resources * @paramhook 成功Hook一個Class後的回調 */ voidhookClassLoad(String name, MS.ClassLoadHook hook); /** * Hook一個指定的方法,並替換方法中的代碼 * * @param_class Hook的calss * @parammember Hook class的方法參數 * @paramhook 成功Hook方法後的回調 * @paramold Hook前方法,相似C中的方法指針 */ voidhookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old); /** * Hook一個指定的方法,並替換方法中的代碼 * * @param_class Hook的calss * @parammember Hook class的方法參數 * @paramalteration */ voidhookMethod(Class _class, Member member, MS.MethodAlteration alteration); /** * 使用一個ClassLoader重載一個對象 * * @paramloader 使用的ClassLoader * @paramobject 待重載的對象 * @return重載後的對象 */ <T>TmoveUnderClassLoader(ClassLoader loader, T object); 

8.3.1 嘗試Hook系統API

說了這麼多咱們下面實戰一下,如咱們但願Hook Android系統中的Resources類,並將系統中的顏色都改成紫羅蘭色。思路很簡單,咱們只須要拿到系統中Resources類的getColor方法,將其返回值作修改便可。

使用substrate來實現分爲如下幾步。

1.在AndroidManifest.xml文件中配置主入口

須要在AndroidManifest.xml中聲明cydia.permission.SUBSTRATE權限,聲明substrate的主入口。具體代碼以下所示。

<!-- 加入substrate權限 --> <uses-permission android:name="cydia.permission.SUBSTRATE" /> <application     android:allowBackup="true"     android:icon="@drawable/ic_launcher"     android:label="@string/app_name"     android:theme="@style/AppTheme" > <!-- 聲明substrate的注入口味Main類 --> <meta-data        android:name="com.saurik.substrate.main"        android:value=".Main" /> </application>

2.新建立主入口Main.Java類

上一步中已經聲明瞭主入口爲Main類,因此咱們須要在對應的目錄下新建一個Main類,且須要實現其initialize方法。具體實現以下:

publicclass {    /**    * substrate 初始化後的入口    */ staticvoidinitialize() {   } }

3.Hook系統的Resources,Hook其getColor方法,修改成紫羅蘭

使用MS.hookClassLoad方法Hook系統的Resources類,並使用MS.hookMethod方法hook其getColor方法,替換其方法。具體實現以下所示。

importJava.lang.reflect.Method; importcom.saurik.substrate.MS; publicclass {    /**    * substrate 初始化後的入口    */ staticvoidinitialize() {     // hook 系統的 Resources類     MS.hookClassLoad("android.content.res.Resources", newMS.ClassLoadHook() {       // 成功hook resources類 publicvoidclassLoaded(Class<?> resources) {         // 獲取 Resources類中的 getColor方法         Method getColor; try{           getColor = resources.getMethod("getColor", Integer.TYPE);         } catch(NoSuchMethodException e) {           getColor = null;         } if(getColor != null) {           // Hook前的原方法 finalMS.MethodPointer old = newMS.MethodPointer();           // hook Resources類中的getColor方法           MS.hookMethod(resources, getColor, newMS.MethodHook() { publicObject invoked(Object resources, Object...args) throwsThrowable { intcolor = (Integer) old.invoke(resources, args);               // 將全部綠色修改爲了紫羅蘭色 returncolor & ~0x0000ff00 | 0x00ff0000;             }           }, old);         }       }     });   } }

4.安裝、重啓、驗證

由於咱們的應用是沒有Activity,只存在substrate的,因此安裝後substrate就會自動地執行了。重啓後,咱們打開瀏覽器引用,發現顏色已經改變了,如圖8-11所示。

閱讀了本例以後,讀者們是否是發現使用了CydiaSubstrate框架後咱們Hook系統中的一些Java API並非什麼難事?上面的例子咱們只是簡單地修改了Resources中的getColor方法,並無涉及到系統與應用的安全。可是,若是開發者直接Hook系統安全方面比較敏感的方法,如TelephonyManager 類中getDeviceId方法、短信相關的方法或一些關鍵的系統服務中的方法,那麼後果是不堪想象的。

C:\Users\zhoushengtao\Desktop\未命名-1.png

圖8-11 Hook系統Resources的瀏覽器先後界面截圖

8.3.2 Hook指定應用注入廣告

從上面的例子咱們能夠看出來,使用Cydiasubstrate框架咱們可以任意地Hook系統中的Java API,固然其中也用到了不少的反射機制,那麼除了系統中給開發者提供的API之外,咱們可否也Hook應用程序中的一些方法呢?答案是確定的。下面咱們就以一個實際的例子講解一下如何Hook一個應用程序。

下面咱們針對Android操做系統的瀏覽器應用,Hook其首頁Activity的onCreate方法(其餘方法不必定存在,可是onCreate方法必定會有),並在其中注入咱們的廣告。根據上面對Cydiasubstrate的介紹,咱們有了一個簡單的思路。

首先,咱們根據某廣告平臺的規定,在咱們的AndroidManifest.xml文件中填入一些廣告相關的ID,而且在AndroidManifest.xml文件中填寫一些使用Cydiasubstrate相關的配置與權限。固然,咱們還會聲明一個廣告的Activity,並設置此Activity爲背景透明的Activity,爲何設置爲透明背景的Activity,原理如圖8-12所示。

圖8-12 注入廣告Activity原理圖

其AndroidManifest.xml文件的部份內容以下所示。

<!-- 廣告相關的權限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.GET_TASKS" /> <!-- 加入substrate權限 --> <uses-permission android:name="cydia.permission.SUBSTRATE" />  <application   android:allowBackup="true"   android:icon="@drawable/ic_launcher"   android:label="@string/app_name"   android:theme="@style/AppTheme" >  <!-- 廣告相關參數 --> <meta-data     android:name="App_ID"     android:value="c62bd976138fa4f2ec853bb408bb38af" /> <meta-data     android:name="App_PID"     android:value="DEFAULT" /> <!-- 聲明substrate的注入口爲Main類 --> <meta-data     android:name="com.saurik.substrate.main"     android:value="com.example.hookad.Main" />  <!-- 透明無動畫的廣告Activity --> <activity     android:name="com.example.hookad.MainActivity"     android:theme="@android:style/Theme.Translucent.NoTitleBar" > <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <!-- 廣告的action --> <action android:name="com.example.hook.AD" /> </intent-filter> </activity> </application>

對於Cydiasubstrate的主入口Main類,依照以前的步驟新建一個包含有initialize方法的Main類。這個時候咱們但願使用MS.hookClassLoad方式找到瀏覽器主頁的Activity名稱,這裏咱們在adb shell下使用dumpsys activity命令找到瀏覽器主頁的Activity名稱爲com.android.browser.BrowserActivity,如圖8-13所示。

圖片 1925

圖8-13 使用dumpsys activity查看當前activity名稱

使用MS.hookClassLoad方法獲取了BrowserActivity以後再hook其onCreate方法,在其中啓動一個含有廣告的Activity。Main類的代碼以下所示。

publicclass {    /**    * substrate 初始化後的入口    */ staticvoidinitialize() {      //Hook 瀏覽器的主Activity,BrowserActivity     MS.hookClassLoad("com.android.browser.BrowserActivity", newMS. ClassLoadHook() { publicvoidclassLoaded(Class<?> resources) {          Log.e("test", "com.android.browser.BrowserActivity");         // 獲取BrowserActivity的onCreate方法         Method onCreate; try{           onCreate = resources.getMethod("onCreate", Bundle.class);         } catch(NoSuchMethodException e) {           onCreate = null;         }  if(onCreate != null) { finalMS.MethodPointer old = newMS.MethodPointer();           // hook onCreate方法           MS.hookMethod(resources, onCreate, newMS.MethodHook() { publicObject invoked(Object object, Object...args) throwsThrowable {               Log.e("test", "show ad"); // 執行Hook前的onCreate方法,保證瀏覽器正常啓動               Object result = old.invoke(object, args); // 沒有Context               //執行一個shell啓動咱們的廣告Activity               CMD.run("am start -a com.example.hook.AD");  returnresult;             }           }, old);         }       }     });   } }

對於啓動的廣告MainActivity,在其中會彈出一個插屏廣告,固然也能夠是其餘形式的廣告或者浮層,內容比較簡單這裏不作演示了。對整個項目進行編譯,運行。這個時候咱們從新啓動Android自帶的瀏覽器的時候發現,瀏覽器會彈出一個廣告彈框,如圖8-14所示。

從上面的圖片咱們能夠看出來了,以前咱們設置插屏廣告MainActivity爲無標題透明(Theme.Translucent.NoTitleBar)就是爲了使彈出來的廣告與瀏覽器融爲一體,讓用戶感受是瀏覽器彈出的廣告。這也是惡意廣告程序爲了防止自身被卸載掉的一些通用隱藏手段。

這裏演示的注入廣告是經過Hook指定的Activity中的onCreate方法來啓動一個廣告Activity的。固然,這裏咱們演示的Activity只是簡單地彈出了一個廣告。若是啓動的Activity帶有惡意性,如將Activity作成與原Activity如出一轍的釣魚Activity,那麼對於移動設備用戶來講是極具欺騙性的。

圖片 1924

圖8-14 Hook瀏覽器彈出廣告

8.3.3 App登陸劫持

看了上面的兩個Hook例子,不少讀者應該都可以瞭解了Hook所帶來的巨大危害性,特別是針對一些有目的性的Hook。例如咱們常見的登陸劫持,就是使用到了Hook技術來完成的。那麼這個登陸劫持是如何完成的呢?下面咱們就具體來看看,一個咱們在開發中常見到的登陸例子。首先咱們看看一個常見的登陸界面是什麼樣子的,圖8-15所示是一個常見的登陸頁面。

8-15

圖8-15 一個登陸界面demo

其對應的登陸流程代碼以下所示。

// 登陸按鈕的onClick事件 mLoginButton.setOnClickListener(newOnClickListener() {    @Override   publicvoidonClick(View v) {   // 獲取用戶名     String username = mUserEditText.getText() + ""; //獲取密碼     String password = mPasswordEditText.getText() + "";  if(isCorrectInfo(username, password)) {       Toast.makeText(MainActivity.this, "登陸成功!", Toast.LENGTH_LONG).show();     } else{       Toast.makeText(MainActivity.this, "登陸失敗!", Toast.LENGTH_LONG).show();     }   } });

咱們會發現,登陸界面上面的用戶信息都存儲在EditText控件上,而後經過用戶手動點擊「登陸」按鈕纔會將上面的信息發送至服務器端去驗證帳號與密碼是否正確。這樣就很簡單了,黑客們只須要找到開發者在使用EditText控件的getText方法後進行網絡驗證的方法,Hook該方法,就能劫持到用戶的帳戶與密碼了。具體流程如圖8-16所示。

8-16

圖8-16 App登陸劫持流程

TIPS

 固然,咱們也能夠仿照上一個例子,作一個如出一轍的Activity,再劫持原Activity優先彈出來,達到欺騙用戶獲取密碼的目的。

明白了原理下面咱們就實際地操做一次,這裏咱們選擇使用Xposed框架來操做。使用Xposed進行Hook操做主要就是使用到了Xposed中的兩個比較重要的方法,handleLoadPackage獲取包加載時的回調並拿到其對應的classLoader,findAndHookMethod對指定類的方法進行Hook。它們的詳細定義以下所示。

/**   * 包加載時的回調    */   publicvoidhandleLoadPackage(finalLoadPackageParam lpparam)    /**    * Xposed提供的Hook方法    *    * @paramclassName 待Hook的Class    * @paramclassLoader classLoader    * @parammethodName 待Hook的Method    * @paramparameterTypesAndCallback hook回調    * @return    */ Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) 

固然,咱們使用Xposed進行Hook也分爲以下幾個步驟。

1.在AndroidManifest.xml文件中配置插件名稱與Api版本號

<application     android:allowBackup="true"     android:icon="@drawable/ic_launcher"     android:label="@string/app_name"     android:theme="@style/AppTheme" >  <meta-data       android:name="xposedmodule"       android:value="true" /> <!-- 模塊描述 --> <meta-data       android:name="xposeddescription"       android:value="一個登陸劫持的樣例" /> <!-- 最低版本號 --> <meta-data       android:name="xposedminversion"       android:value="30" /> </application>

2.新建立一個入口類繼承並實現IXposedHookLoadPackage接口

以下操做,咱們新建了一個com.example.loginhook.Main的類,並實現IXposedHookLoadPackage接口中的handleLoadPackage方法,將非com.example.login包名的應用過濾掉,即咱們只操做包名爲com.example.login的應用,以下所示。

publicclass implementsIXposedHookLoadPackage {    /**    * 包加載時的回調    */ publicvoidhandleLoadPackage(finalLoadPackageParam lpparam) throwsThrowable {     // 將包名不是 com.example.login 的應用剔除掉 if(!lpparam.packageName.equals("com.example.login")) return;     XposedBridge.log("Loaded app: " + lpparam.packageName);   } }

3.聲明主入口路徑

須要在assets文件夾中新建一個xposed_init文件,並在其中聲明主入口類。如這裏咱們的主入口類爲com.example.loginhook.Main,查看其內容截圖如圖8-17所示。

圖片 1950

圖8-17 xposed_init內容截圖

4.使用findAndHookMethod方法Hook劫持登陸信息

這是最重要的一步,咱們以前所分析的都須要到這一步進行操做。如咱們以前所分析的登陸程序,咱們須要劫持就是須要Hook其com.example.login.MainActivity中的isCorrectInfo方法。咱們使用Xposed提供的findAndHookMethod直接進行MethodHook操做(與Cydia很相似)。在其Hook回調中使用XposedBridge.log方法,將登陸的帳號密碼信息打印至Xposed的日誌中。具體操做以下所示。

importstaticde.robv.android.xposed.XposedHelpers.findAndHookMethod; publicclass implementsIXposedHookLoadPackage {    /**    * 包加載時的回調    */ publicvoidhandleLoadPackage(finalLoadPackageParam lpparam) throwsThrowable {      // 將包名不是 com.example.login 的應用剔除掉 if(!lpparam.packageName.equals("com.example.login")) return;     XposedBridge.log("Loaded app: " + lpparam.packageName);      // Hook MainActivity中的isCorrectInfo(String,String)方法 findAndHookMethod("com.example.login.MainActivity", lpparam.classLoader, "isCorrectInfo", String.class,         String.class, newXC_MethodHook() {            @Override protectedvoidbeforeHookedMethod(MethodHookParam param) throwsThrowable {             XposedBridge.log("開始劫持了~");             XposedBridge.log("參數1 = " + param.args[0]);             XposedBridge.log("參數2 = " + param.args[1]);           }            @Override protectedvoidafterHookedMethod(MethodHookParam param) throwsThrowable {             XposedBridge.log("劫持結束了~");             XposedBridge.log("參數1 = " + param.args[0]);             XposedBridge.log("參數2 = " + param.args[1]);           }         });   } }

5.在XposedInstaller中啓動咱們自定義的模塊

編譯後安裝在Android設備上的模塊應用程序不會當即生效,咱們須要在XpasedInstaller模塊選項中勾選待啓用的模塊才能讓其正常地生效,如圖8-18所示。

6.重啓驗證

重啓Android設備,進入XposedInstaller查看日誌模塊,由於咱們以前使用的是XposedBridge.log方法打印log,因此log都會顯示在此處。如圖8-19所示,咱們發現咱們須要劫持的帳號密碼都顯示在此處。

8-18

圖8-18 Xposed框架加載模塊界面

圖8-19 XPosed框架日誌界面

TIPS

 這裏咱們是經過逆向分析該登陸頁面的登陸判斷調用函數來完成Hook與劫持工做的。有些讀者應該想出來了,咱們能不能直接對系統中提供給咱們的控件EditText(輸入框控件)中的getText()方法進行Hook呢?這樣咱們就可以對系統中全部的輸入進行監控劫持了。這裏留給你們一個思考,感興趣的讀者能夠嘗試一下。

8.4 Hook原生應用程序

以前咱們演示過了如何在Java層Hook系統的API方法,可是咱們都知道不少安全級別較高的操做咱們都不會在Java層來完成,並且Java層不少的API都是經過JNI的方式在Native層完成的,因此對Java層的API方法Hook意義不是很大。本節咱們就具體來講說在Android中如何使用CydiaSubstrate框架完成Native層的Hook操做。

8.4.1 CydiaSubstrate框架針對Native層Hook的支持

對於CydiaSubstrate框架來講,其給咱們提供了相似在Java中的API方法,如在Native層的MSJavaHookClassLoad函數(相似Java中的hookClassLoad方法)、MSJavaHookMethod函數(相似Java中的hookMethod)。做者的意圖就是爲了讓咱們可以在Native層使用JNI完成Java函數的Hook。其中兩個函數的具體定義以下:

/** * 經過JNI Hook Java中的ClassLoad * * @jni jni指針 * @name 待Hook的類,字符串形式 * @callback Hook後的回調 * @data 自定義參數數據 */ voidMSJavaHookClassLoad(JNIEnv *jni, constchar*name, void(*callback)(JNIEnv *, jclass, void*), void*data);  /** * 經過JNI Hook Java中的指定方法 * * @jni jni指針 * @_class jclass * @methodId 待Hook方法ID * @hook Hook後待替換的函數 * @old Hook前原函數的指針 */ voidMSJavaHookMethod(JNIEnv *jni, jclass _class, jmethodID methodId, void*hook, void**old); 

上述的兩個函數確實比較有用,可是卻不是咱們最想要的結果。在Native層Hook咱們仍是但願針對原生函數進行Hook操做。其實針對Native層的Hook原理,咱們在本章的開頭已經給各位讀者介紹了。CydiaSubstrate只是針對其作了一個良好的封裝操做,讓咱們更方便地使用。下面是CydiaSubstrate框架提供的Hook函數方法。

 * 根據具體的地址路徑加載動態庫 * 相似於dlopen * * @return 動態庫ImageRef */ MSImageRef MSGetImageByName(constchar*file);  /** * 根據指定庫找到其中函數的偏移量 * 相似於dlsym * * @image 指定的動態庫 * @name 指定函數的名稱 * @return 指定函數的指針(兼容ARM/Thumb)找不到返回NULL */ void*MSFindSymbol(MSImageRef image, constchar*name);  /** * Hook Native層中的指定函數 * * @symbol 待Hook函數指針 * @hook Hook後待替換的函數指針 * @old Hook前函數指針 */ voidMSHookFunction(void*symbol, void*hook, void**old); 

看到上面的函數說明估計讀者們都躍躍欲試了,並且相信不少讀者已經可以猜出如何使用CydiaSubstrate框架了。下面咱們仍是詳細地說明一下,除了瞭解其提供的API函數以外,使用CydiaSubstrate框架還須要注意的一些注意事項。

  • 基於CydiaSubstrate框架的動態庫必須以「.cy.so」(系統默認是「.so」)做爲後綴。
  • 在AndroidManifest.xml文件中須要聲明cydia.permission.SUBSTRATE權限。
  • 須要在Native C/CPP方法前添加Config配置。
MSConfig(MSFilterExecutable, "/system/bin/app_process")

好了介紹完畢,下面咱們具體地操做一次。

8.4.2 經過JNI改變系統顏色

與以前的嘗試Hook系統API小節同樣,如咱們但願完成Hook Android系統中的Resources類,並將系統中的顏色都改成紫羅蘭色。思路也是同樣的,咱們只須要拿到系統中Resources類的getColor方法,將其返回值作修改便可。那麼咱們使用原生方法實現,須要完成如下幾個步驟。

1.在AndroidManifest.xml中聲明權限與安裝方式

由於是系統組件代碼,咱們須要設置其安裝方式是internalOnly與hasCode=「false」,這樣可以方便CydiaSubstrate框架獲取咱們的邏輯。固然還須要聲明SUBSTRATE權限,具體的操做以下AndroidManifest.xml內容所示。

 <?xml version="1.0" encoding="utf-8"?>  <!-- internalOnly 系統內部安裝,禁止安裝到sd卡 --> <manifest xmlns:android="http://schemas.android.com/apk/res/android"   package="com.example.hooknative"   android:installLocation="internalOnly"   android:versionCode="1"   android:versionName="1.0" >  <!-- 聲明Substrate權限 --> <uses-permission android:name="cydia.permission.SUBSTRATE" /> <uses-sdk     android:minSdkVersion="8"     android:targetSdkVersion="21" />  <!-- hasCode=false,系統組件,不運行APP中的邏輯 --> <application android:hasCode="false" > </application>  </manifest>

2.新建立項目的cpp文件,導入所需的庫

這裏咱們新建立一個原生代碼文件HookNative.cy.cpp(後綴必須爲.cy.cpp,編譯後則會出現.cy.so文件),並將CydiaSubstrate的庫文件libsubstrate.so、libsubstrate-dvm.so、substrate.h一塊兒複製到jni目錄下(這裏須要根據不一樣平臺選擇,咱們這裏選擇的是ARM平臺的庫),jni目錄如圖8-20所示。

圖片 2057

圖8-20 項目中JNI目錄截圖

固然,咱們還須要編寫Makefile文件Android.mk,指定Substrate庫參與編譯,並引入一些必要的庫。內容以下所示。

LOCAL_PATH := $(call my-dir)  # substrate-dvm 庫 include $(CLEAR_VARS) LOCAL_MODULE:= substrate-dvm LOCAL_SRC_FILES := libsubstrate-dvm.so include $(PREBUILT_SHARED_LIBRARY)  # substrate 庫 include $(CLEAR_VARS) LOCAL_MODULE:= substrate LOCAL_SRC_FILES := libsubstrate.so include $(PREBUILT_SHARED_LIBRARY)  include $(CLEAR_VARS)  LOCAL_MODULE  := HookNative.cy LOCAL_SRC_FILES := HookNative.cy.cpp  LOCAL_LDLIBS+= -L$(SYSROOT)/usr/lib -llog LOCAL_LDLIBS+= -L$(LOCAL_PATH) -lsubstrate-dvm -lsubstrate include $(BUILD_SHARED_LIBRARY) 

3.載入配置文件與CydiaSubstrate入口

在HookNative.cy.cpp代碼文件中,使用CydiaSubstrate框架的API,還須要在其中聲明一些東西,如MSConfig配置app_process的路徑,聲明MSInitialize做爲一個CydiaSubstrate插件的入口。咱們還會應用一些開發中必要的頭文件與LOG聲明,如這裏咱們的HookNative.cy.cpp內容爲:

#include<android/log.h> #include<substrate.h>  #defineLOG_TAG "native_hook" #defineLOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  // 載入配置文件 MSConfig(MSFilterExecutable, "/system/bin/app_process")  // Cydia初始化入口 MSInitialize { }

4.Hook並替換其方法

其修改方法與上一節的Hook Java中的方法相似,咱們只須要修改相關的函數完成Hook便可。如這裏咱們使用MSJavaHookClassLoad方法Hook系統的Resources類,並使用MSJavaHookMethod方法Hook其getColor方法,替換其方法。具體實現以下所示。

#include<android/log.h> #include<substrate.h>  #defineLOG_TAG "native_hook"  #defineLOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  // 載入配置文件 MSConfig(MSFilterExecutable, "/system/bin/app_process")  // getColor方法Hook前原函數指針 staticjint (*_Resources$getColor)(JNIEnv *jni, jobject _this, ...);  // getColor方法Hook後被替換的函數 staticjint $Resources$getColor(JNIEnv *jni, jobject _this, jint rid) {   jint color = _Resources$getColor(jni, _this, rid); returncolor & ~0x0000ff00 | 0x00ff0000; }  // Hook住Resources class的回調 staticvoidOnResources(JNIEnv *jni, jclass resources, void*data) {   // hook其對應的getColor方法   jmethodID method = jni->GetMethodID(resources, "getColor", "(I)I"); if(method != NULL)     MSJavaHookMethod(jni, resources, method, &$Resources$getColor, &_Resources$getColor); }  // Cydia初始化入口 MSInitialize {   // Hook Java中的Resources   MSJavaHookClassLoad(NULL, "android/content/res/Resources", &OnResources); }

5.編譯、安裝、重啓驗證

一樣,由於CydiaSubstrate是Hook Zygote進程,並且咱們Hook的又是系統的Resources方法,因此咱們但願驗證都須要重啓一下設備。咱們能夠選擇CydiaSubstrate中的軟重啓,這裏咱們對系統的設置頁面Hook先後都作了一個截圖,對比截圖如圖8-21所示。

圖片 2088

圖片 69

圖8-21 Hook先後系統設置界面截圖對比

本例中咱們繼續以前Java中Hook的思想,完成了在原生代碼中使用JNI針對Java中的API進行Hook操做。由於,CydiaSubstrate框架中的hookClassLoad方法、hookMethod方法底層實現也是如此,因此咱們使用起來很相似。

8.4.3 Hook後替換指定應用中的原生方法

討論了過久的Java層面的API Hook工做,也舉了不少例子,本節中咱們就看看如何使用CydiaSubstrate框架完成原生函數的Hook。

例如,如今咱們有一個應用程序(包名爲:com.example. testndklib),其主要功能就是按下界面上的「test」按鈕後,經過JNI調用Native的test函數,在系統的Log中輸入一個當前我有多少錢的整數值。界面如圖8-22所示。

C:\Users\zhoushengtao\Desktop\Screenshot_2015-04-21-15-58-51.png

圖8-22 testndklib應用界面

使用JNI調用的test函數,寫在NDK庫testNDKlib中,會調用一個名叫getMoney的函數,顯示我當前有多少錢。固然,這裏咱們直接硬編碼了,返回值爲100的整數。中間,咱們還將 getMoney 函數的地址經過 Log 打印出來。testNDKlib.cpp內容以下所示。

#include<stdio.h> #include<jni.h> #include<android/log.h> extern"C" { #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "cydia_native", __VA_ARGS__)  /** * 測試函數getMoney,返回一個整數 */ intgetMoney(void) {   // 打印方法函數地址   LOGI(" getMoney() function address in : %p\n", &getMoney);   return100; }  // 一個JNI的test函數 jstring Java_com_example_testndklib_MainActivity_test(JNIEnv* env,     jobject thiz) {   LOGI(" I have %d money.\n", getMoney());   return0; } }

運行一下程序,單擊「test」按鈕,拿到了系統輸出的 Log,與咱們輸出的預期同樣。筆者將DDMS上輸出的Log截圖,如圖8-23所示。

圖片 2091

圖8-23 test函數執行後產生的Log

如今咱們但願Hook此so文件,找到其中的getMoney函數,替換它讓它給咱們返回整數值999999(相似一個遊戲修改金幣的外掛)。針對以前咱們討論的Hook的原理,咱們須要作以下幾步操做。

(1)加載原生庫,libtestNDKlib.so。

(2)找到對應的函數符號地址,MSFindSymbol。

(3)替換函數,MSHookFunction。

這裏咱們在完成一、2步驟的時候,咱們同時也用dlopen與dlsym方式實現給你們演示一下。以前的環境配置邏輯以及權限聲明邏輯與上一個例子相似,這裏咱們不作贅述,直接看一下cpp文件中的內容,具體以下:

#include<android/log.h> #include<substrate.h> #include<stdio.h>  #defineLOG_TAG "cydia_native"  #defineLOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  // 初始化CydiaSubstrate MSConfig(MSFilterExecutable, "/system/bin/app_process")  // 原函數指針 int(*original_getMoney)(void);  /** * 替換後的函數 */ intreplaced_getMoney(void) {    LOGI(" replaced_getMoney() function address in : %p\n", &replaced_getMoney);   return999999; }  /** * * 找到指定連接庫中的函數地址 * * @libraryname 連接庫地址 * @symbolname 函數名 * @return 對應的函數地址 */ void* lookup_symbol(char* libraryname, char* symbolname) {   // dlopen打開指定庫,得到句柄   void*imagehandle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);   if(imagehandle != NULL) {     // 得到具體函數     void* sym = dlsym(imagehandle, symbolname);     if(sym != NULL) {       returnsym;     } else{       LOGI("(lookup_symbol) dlsym didn't work");       returnNULL;     }   } else{     LOGI("(lookup_symbol) dlerror: %s", dlerror());     returnNULL;   } }  //初始化 MSInitialize {    // 得到libtestNDKlib.so動態庫中getMoney函數的地址   MSImageRef image;   image = MSGetImageByName(       "/data/data/com.example.testndklib/lib/libtestNDKlib.so");   void* getAgeSym = MSFindSymbol(image, "getMoney");    // MSImageRef與 MSFindSymbol 也能夠寫爲以下所示找到getMoney函數的地址   //   // void * getAgeSym = lookup_symbol(   //     "/data/data/com.example.testndklib/lib/libtestNDKlib.so",   //     "getMoney");    // 將getMoney函數替換爲 replaced_getMoney函數   MSHookFunction(getAgeSym, (void*) &replaced_getMoney,       (void**) &original_getMoney);  }

編譯後安裝到已經安裝了CydiaSubstrate框架的系統中,重啓Android設備。若是在整個系統編譯與配置沒有什麼錯誤的狀況下,咱們發現CydiaSubstrate框架會打出Log說Loding什麼什麼 so 文件了。這裏咱們看見,LodinglibnativeHook.cy.so 說明咱們以前開發的 Hook 其中的getMoney方法已經生效了,如圖8-24所示。

C:\Users\zhoushengtao\Desktop\QQ截圖20150326202439.png

圖8-24 CydiaSubstrate框架加載日誌

這個時候咱們繼續運行程序,進入咱們剛纔的test應用程序。單擊「test」按鈕,獲取調用JNI中的test函數打印我有多少錢。咱們可以在DDMS中清楚地看到,getMoney函數已經被一個名爲「replace_getMoney」的函數替換了,其地址也已經被替換了。咱們也看到使用替換後的值輸出爲「I have 999999 money」,如圖8-25所示。

C:\Users\zhoushengtao\Desktop\QQ截圖20150326203717.png

圖8-25 函數替換後打印的Log

8.4.4 使用Hook進行廣告攔截

對於Android操做系統咱們知道,Java層都是創建在原生C/C++語言上的,特別是針對一些系統級別的API函數。上面咱們演示瞭如何對用戶自定義函數進行Hook,下面咱們演示一下如何對Native層的系統API進行Hook。

如這裏咱們但願對系統中的網絡請求API進行Hook,而後過濾掉一些廣告相關的請求,完成廣告攔截功能,其具體的流程如圖8-26所示。

這裏咱們查看Android操做系統的POSIX定義的源碼Poxis.Java文件,發現其針對網絡請求函數的定義是在native完成的,如圖8-27所示。

圖8-26 廣告攔截流程圖

C:\Users\zhoushengtao\Desktop\Posix.png

圖8-27 Posix.Java文件部份內容截圖

對於此類的問題,咱們就不得不在native完成Hook與函數替換工做了。因此咱們仍是使用CydiaSubstrate框架,對系統的API函數connect進行Hook與替換。以下代碼所示,咱們使用MSHookFunction對native層中的connect函數進行Hook替換至newConnect函數。

#defineLOG_TAG "cydia_native" #defineLOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  // 初始化CydiaSubstrate MSConfig(MSFilterExecutable, "/system/bin/app_process")  // 原connect函數指針 int*(*oldConnect)(int,  constsockaddr *, socklen_t); int*newConnect(intsocket, constsockaddr *address, socklen_t length) {    charip[128] = { 0 };   intport = -1;   if(address->sa_family == AF_INET) {     sockaddr_in *address_in = (sockaddr_in*) address;     // 獲取 ip     inet_ntop(AF_INET, (void*) (structsockaddr*) &address_in->sin_addr, ip,         128);     // 獲取端口     port = ntohs(address_in->sin_port);      // 過濾掉172.22.156.129的請求     if(strcmp(ip, "172.22.156.129") == 0) {        LOGI("發現廣告請求");       structsockaddr_in my_addr;       intmy_len = sizeof(structsockaddr_in);       bzero(&my_addr, sizeof(my_addr));       my_addr.sin_family = AF_INET;       my_addr.sin_port = htons(80);       my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");        returnoldConnect(socket, (constsockaddr*) &my_addr,           sizeof(my_addr));     }     returnoldConnect(socket, address, length);    } }  //初始化 MSInitialize{   MSHookFunction((void*) &connect, (void*) &newConnect,       (void**) &oldConnect); }

這裏咱們只是簡單地將IP爲172.22.156.129的請求重定向到本地127.0.0.1,即讓相似的廣告請求拿不到數據。此類方式的廣告過濾屬於比較原始暴力類型的過濾方法,但卻也簡單有效。熟悉connect的讀者應該會發現,若是已經可以替換掉connect函數,其實咱們能作到的事情遠遠比攔截一個廣告請求大得多,好比跳轉至釣魚網站,收集私密發送數據等。

8.5 Hook檢測/修復

Hook的目的是爲了對目標進程函數的替換和注入,Hook的危害是巨大的,Hook後的應用程序毫無安全可言。其實,自從PC時代起,Hook與反Hook一直就是一個曠日持久的戰爭。那麼對於剛發展不久的Android操做系統安全方向而言,Hook的檢測與修復無疑是給Android安全研究人員帶來了巨大的挑戰。本節咱們就具體地看看,就Android操做系統而言,如何檢測一個進程是否被Hook了,如何修復被Hook的進程消除其安全隱患。

8.5.1 Hook檢測

上面演示了不少的Hook例子,Hook後的應用程序注入與劫持危害是不可估量的。因此,如何識別應用程序被Hook了,如何去除Hook程序也成爲了難題。咱們先從Hook的原理上來分析,Hook就是在一個目標進程中經過改變函數方法的指向地址,加入一段自定義的代碼塊。那麼,加入一些非本進程的代碼邏輯,進程會不會產生一些改變?帶着疑問咱們直接使用本章以前在瀏覽器中注入廣告的例子查看一下。

1.Java層Hook檢測

首先咱們使用ps命令查看一下瀏覽器應用(包名爲:com.android.browser)的進程pid,在adb shell模式下輸入ps | busybox grep com.android.browser(busybox是一個擴展的命令工具),如圖8-28所示。

圖片 2102

圖8-28 查看瀏覽器的進程pid

咱們這裏看見瀏覽器應用對應的進程pid爲5425。

熟悉Android操做系統的朋友應該清楚,Android操做系統繼承了Linux操做系統的優勢,有一個虛擬文件系統也就是咱們常訪問的/proc目錄,經過它可使用一種新的方法在Android內核空間和用戶空間之間進行通訊,即咱們可以看到當前進程的一些狀態信息。而其中的maps文件又能查看進程的虛擬地址空間是如何使用的。

如今思路已經很清晰了,咱們使用命令:

cat /proc/5425/maps | busybox grep /data/dalvik-cache/data@app

查看地址空間中的對應的dex文件有哪些是非系統應用提供的(瀏覽器是系統應用),即過濾出/data@app(系統應用是/system@app)中的,如圖8-29所示。

圖8-29 查看5425進程的虛擬地址空間

對應地輸出了Dalvik虛擬機載入的非系統應用的dex文件,如圖8-30所示。

圖片 2099

圖8-30 5425進程被加載的用戶空間中的dex文件

在圖 8-30 中咱們清楚地看到,該進程確實被附加了不少非系統的 dex 文件,如 hookad-1. apk@classes.dex、loginhook-2.apk@classes.dex、substrate-1.apk@classes.dex等。若是沒有上面的Hook演示,這裏咱們很難肯定這些被附加的代碼邏輯是作什麼的,固然確定也不是作什麼好事。因此,咱們得出結論,此應用已經被Hook,存在安全隱患。

2.native層Hook檢測

上面演示瞭如何檢測Java層應用是否被Hook了,對於native層的Hook檢測其實原理也是同樣的。這裏咱們對以前的演示的Hook後替換指定應用中的原函數例子作檢測(包名爲:com.example.testndklib),咱們使用ps | busybox grep com.example.testndklib,如圖8-31所示。

圖片 2101

圖8-31 查看com.example.testndklib包進程的詳細信息

獲得其對應的進程pid爲15097,咱們直接查看15097進程中的虛擬地址空間加載了哪些第三方的庫文件,即過濾處/data/data目錄中的,具體命令如圖8-32所示。

圖8-32 查看15097進程的虛擬地址空間

獲得的輸出結果如圖8-33所示,發現其中多了不少的/com.saurik.substrate下面的動態庫,說明該進程也已經被其注入了。因此咱們判斷com.example.testndklib應用程序已經被Hook,存在不安全的隱患。

圖8-33 testndklib包下載入的第三方so庫文件

一樣的方式,咱們可以查看到Zygote進程的運行狀況,發現也是被注入了CydiaSubstrate框架的不少so庫文件,如圖8-34所示。

做爲應用程序對自身的檢測,也只須要讀取對應的進程的虛擬地址空間目錄/proc/pid/maps文件,判斷當前進程空間中載入的代碼庫文件是否存在於本身白名單中的,便可判斷自身程序是否被Hook。可是,對於zygote進程來講若是沒有Root權限,咱們是沒法訪問其maps文件的,那麼也就沒法判斷Hook與否了。

圖8-34 zygote進程的虛擬地址空間顯示載入的so庫文件

8.5.2 Hook修復

如何判斷一個進程是否被其餘第三方函數庫Hook,咱們已經知道了。爲了讓咱們的應用程序可以在一個安全可靠的環境中運行,那麼咱們就必須將這些不速之客從應用程序的進程中剝離出去。

如上面咱們演示的testndklib應用程序,咱們在adb shell命令模式下查看其進程pid爲30210,並根據進程pid查看其對應的進程虛擬地址空間。具體命令如圖8-35所示。

圖8-35 使用ps | busybox grep查看testndklib的pid

從系統返回的具體結果中咱們發現,已經被不少的第三方 Hook 庫所加載,這裏都是以/com.saurik.substrate開頭的substrate框架的動態庫,如圖8-36所示。

圖8-36 testndklib中的虛擬地址空間加載的so庫

固然,咱們但願除了自身包名(com.example.testndklib)下的其餘動態連接全都給刪除關閉,且關閉後的應用程序還可以正常地運行。由於全部的第三方庫都是經過dlopen後注入的方式附加到應用程序進程中的,這裏咱們很容易想到咱們直接使用 dlclose 將其中的第三方函數挨個卸載關閉便可。

這樣一個程序思路就來了,首先掃描/proc/<pid>/maps目錄下的全部so庫文件,並將自身的動態庫文件排除,對於非自身的動態連接庫咱們全都卸載關閉。對於Java咱們沒法使用dlclose,因此這裏咱們仍是採用了JNI的方式來完成,具體的操做函數以下所示。

  /**    * 根據包名與進程pid,刪除非包名下的動態庫    * @parampid 進程pid    * @parampkg 包名    * @return    */ publicList<String>removeHooks(intpid, String pkg) {     List<String> hookLibFile = newArrayList<>();     // 找到對應進程的虛擬地址空間文件     File file = newFile("/proc/" + pid + "/maps"); if(!file.exists()) { returnhookLibFile;     }  try{       BufferedReader bufferedReader = newBufferedReader(newInputStreamReader (newFileInputStream(file)));       String lineString = null; while((lineString = bufferedReader.readLine()) != null) {         String tempString = lineString.trim();         // 被hook注入的so動態庫 if(tempString.contains("/data/data") && !tempString.contains("/data/data/" + pkg)) { intindex = tempString.indexOf("/data/data");           String soPath = tempString.substring(index);           hookLibFile.add(soPath);           // 調用native方法刪除so動態庫           removeHookSo(soPath);         }       }       bufferedReader.close();     } catch(FileNotFoundException e) {       e.printStackTrace();     } catch(IOException e) {       e.printStackTrace();     } returnhookLibFile;   }    /**    * 卸載加載的so庫    * @paramsoPath so庫地址路徑    */ publicnativevoidremoveHookSo(String soPath);  // JNI中的removeHookSo卸載一個so的加載 voidJava_com_example_testndklib_MainActivity_removeHookSo(JNIEnv* env,     jobject thiz, jstring path) {   constchar* chars = env->GetStringUTFChars(path, 0);   void* handle = dlopen(chars, RTLD_NOW);   intcount = 4;   inti = 0;   for(i = 0; i < count; i++) {      if(NULL != handle) {        dlclose(handle);      }   } }

在須要卸載的應用程序中調用removeHooks(Process.myPid(), getPackageName())就可以輕鬆地完成上述的功能。那麼是否全部的動態庫都被卸載移除了?咱們從新查看該應用程序的虛擬地址空間,獲得結果如圖8-37所示。

圖8-37 dlclose後的testndklib虛擬地址空間中的so庫

比較後你們都會發現,雖然卸載掉了大部分的so動態連接庫,可是仍是殘餘了少量沒有被卸載乾淨,如咱們這裏剩餘的libAndroidBootstrap0.so庫仍是依然在加載中。對於dlclose函數讀者們應該都清楚,dlclose用於關閉指定句柄的動態連接庫 ,只有當此動態連接庫的使用計數爲0時,纔會真正被系統卸載。也就是說若是咱們手動卸載動態連接庫以前,系統已經保持對其的應用的話,咱們是沒法卸載的。

Hook框架的動態庫何時加載如何加載咱們都不可以得知,因此對非本包的動態連接庫卸載也須要實時監測去卸載。且就算卸載也不可以徹底地保證系統就沒有對相關函數的引用,達到卸載乾淨的目的。因此,咱們得出結論,對於Hook後的應用程序修復在目前來講是一項暫無解決方案的工做。

8.5.3 Hook檢測小結

說到如何識別一個應用程序是否被Hook、修復Hook,咱們會發現由於Android操做系統上沙箱(Sandbox)機制的存在,無論咱們採用何種方案手段都沒有辦法徹底避免程序被Hook。這個時候咱們就須要將咱們的目光轉到如何防止應用程序被Hook,預防於未然纔是主要的解決方案。

相關文章
相關標籤/搜索