1. 程式人生 > >簡單概括Xposed hook原理

簡單概括Xposed hook原理

轉載自:https://www.jianshu.com/p/b29a21a162ad

這塊知識本身是挺多的,網上有對應的原始碼分析,本文儘量從不分析程式碼的角度來把原理闡述清楚。

Xposed是一個在andoid平臺上比較成熟的hook框架,可以完美的在dalvik虛擬機器上做到hook任意java方法。在art虛擬機器上仍然處在beta階段,相信以後也會穩定支援。

Xposed在dalvik上的hook原理值得好好學習,這樣才能改造它,或者開發類似的hook框架。

Dalvik虛擬機器的程式碼分析參考這篇文章:
http://blog.csdn.net/innost/article/details/50377905


dex檔案格式參考這篇文章:
http://www.blogfshare.com/dex-format.html
以上兩篇文章寫得非常好,當你對著原始碼來學習就會發現。

這裡做一個總結,順便說一些這些文章裡面沒有寫的內容:

java原始碼經過編譯後,得到很多個class檔案, 考慮到手機的記憶體較小,google改進了位元組碼的組織形式,將一個app中的所有class檔案合到了一起構成dex檔案,當然並不是簡單的拼接在一起,而是遵從dex的格式(參見上)重新組織。

dex檔案最終會和資原始檔等一起打包成為apk,簽名後安裝到手機上。

PackageManager在安裝apk的時候,做了一件事:優化dex檔案為odex,存放在/data/dalvik-cache目錄下。

dex檔案是遵從於dalvik虛擬機器標準的檔案,它具有跨dalvik虛擬機器的特點,而odex是在特定dalvik虛擬機器上優化得到的,通常不能跨dalvik虛擬機器執行。

程式執行體現在方法的執行上,因為我們重點關注下方法的組織形式。

在dex檔案中,方法體裡面的內容最終儲存在classData區域,方法體裡面儲存的是二進位制的位元組碼。

在說位元組碼之前,先來說說什麼是dalvik虛擬機器,dalvik虛擬機器說白了就是用c/c++寫的一套複雜的程式,它定義了一堆的smali指令(256個),這些位元組碼指令高度抽象,組合這些指令可以完成我們想要的功能。你可以等同於彙編指令理解,但它們在語言級別要高於彙編(在虛擬機器裡面執行的,虛擬機器又是c/c++寫的,自然看出高於彙編)。

位元組碼的定義參考:
http://code.metager.de/source/xref/android/4.0.3/dalvik/libdex/DexOpcodes.h
位元組碼是用簡單的二進位制數字表示的,與可閱讀的smali指令存在對應關係,具體對應方法參考google的官方文件:
http://source.android.com/devices/tech/dalvik/instruction-formats.htmlhttp://source.android.com/devices/tech/dalvik/dalvik-bytecode.html

我們只要知道有這個對應關係就好了, 已經有人寫了工具來做這個轉化:smali/baksmali,這個工具可以分析dex檔案,解析位元組碼為對應的smali語法(反彙編),同時也可以將smali語法的檔案重新轉換為位元組碼生成dex檔案(彙編)。

dex優化過程,其實是將一些位元組碼替換為dalvik相關的, 優化後的等價位元組碼。smali/baksmali同時也支援將odex轉化為dex,不過你需要從odex產生的手機上dump出對應的檔案(framework.jar等),傳遞給它,才能正確轉化。

虛擬機器又是如何執行這些位元組碼的呢?
答案在AOSP中(開源就是好),原來虛擬機器對於每一個位元組碼,都寫了一段程式碼來解釋執行(你可以等價理解為API呼叫一樣,呼叫某個API,後面一堆邏輯來實現這個API),只是不同cpu結構,實現方式不一樣,比如arm使用了arm彙編來實現,x86使用了x86的彙編來實現,google提供了一個c/c++的實現方式來相容一些未知的cpu結構,見:
https://android.googlesource.com/platform/dalvik2/+/master/vm/mterp/out/InterpC-portable.cpp

虛擬機器在載入了odex(虛擬機器總是使用odex檔案,第一次使用時會先生成odex), 會將整個odex檔案的內容mmap到記憶體中,之後就和odex檔案沒有關係了。

虛擬機器在load一個Class的時候(參見DexClassLoader原始碼),根據類的描述符,在記憶體中的odex區域,查詢到對應的資料,構建出ClassObject物件,以及這個ClassObject關聯的Method。

Method分為兩種,dalvik虛擬機器在處理的時候有區別,一種是directMethod,即Java世界裡面實現的方法,一種是nativeMethod,即在c/c++裡面實現的方法。

ClassObject裡面有兩個集合,分別存放了這個Class下定義的directMethods和nativeMethods。

Method中,有兩個非常重要的指標:

const u2* insns; 
DalvikBridgeFunc nativeFunc;

對於directMethod,insns存放了該方法的位元組碼指標(還記得odex被mmap到記憶體中了麼,這個指標就是這段記憶體裡面指向code區域的開始處的指標)。
虛擬機器在呼叫directMethod時,在構建好方法棧以後,pc指標指向了insns,於是可以從記憶體中取得位元組碼,然後解釋執行。

一些apk加固廠商就是在這塊做的手腳,這裡衍生開來說一下:
梆梆加固的實現方式為:將原始dex中的內容加密處理,在app執行時,解密出dex,mmap到記憶體,還原了記憶體結構。
愛加密的方法則是,將方法體裡面的位元組碼從dex中摳出來,加密到了自己的so中,在app執行時,從so中解密出方法體,然後修改mmap對應的記憶體,還原記憶體結構。

這些加固方法說白了就是將原始dex做加固處理,在執行時還原記憶體結構。所以光從靜態分析反編譯加固後的dex檔案,將得不到有用資訊。

但有一個基於xposed的zjdroid脫殼工具,可以在執行時dump出記憶體(odex結構的記憶體),儲存為本地odex檔案,再利用smali/baksmali還原出原始dex檔案。

虛擬機器在處理native方法時,走的是另外一套邏輯。

我們在使用native方法時,首先得使用System.loadLibrary對so進行載入,其最終是使用dlopen函式載入了指定的so檔案。

之後在我們呼叫nativeMehtod的時候,會根據方法描述符,通過特定的對映關係(是否主動進行了註冊會有不同)得到一個native層的函式名,再從之前dlopen獲得的控制代碼中使用dlsys去查詢對應的函式,得到了函式指標後,將這個指標賦值給 insns。在nativeFunc這個橋接函式中,將insns解析為函式指標,然後進行呼叫。

Xposed hook原理

有前面這些知識後,再理解Xposed的hook原理就不難了。
前面已經知道,一個java方法在虛擬機器裡面對應的Method為directMethod,其insns指向了位元組碼位置。

Xposed在對java方法進行hook時,先將虛擬機器裡面這個方法的Method改為nativeMethod(其實就是一個標識欄位),然後將該方法的nativeFunc指向自己實現的一個native方法,這樣方法在呼叫時,就會呼叫到這個native方法,接管了控制權。

在這個native方法中,xposed直接呼叫了一個java方法,這個java方法裡面對原方法進行了呼叫,並在呼叫前後插入了鉤子,於是就hook住了這個方法。

Xposed的hook原理就是這麼簡單,但它有其他的問題要解決:如何將hook的程式碼注入到目標app的程序中?

Xposed的實現是依賴與root,重寫android的zygote程式碼,加入自身的載入邏輯。zygote是android 系統最最初執行的程式,之後的程序都是通過它fork(你把它理解為複製吧)出來的。 於是zygote中載入的程式碼,在所有fork出來的子程序都含有(app程序也是fork出來的)。 所以xposed是一個可以hook android系統中任意一個java方法的 hook框架。

而淘寶根據xposed改造出來的dexposed,僅僅是注入hook程式碼的方式不同而已,hook邏輯完全一致。
dexposed不依賴與root,但需要開發者主動整合進來(我們集合了別人的廣告sdk,其實也是讓別人的程式跑到我們的程序裡面,所以得小心點,給我一個入口,我也能hook住你的任何方法)。所以其hook的範圍僅僅是被整合的應用(對於淘寶的AOP框架定義,這個效果剛剛好)。