Android中熱修復框架AndFix原理解析及案例使用

一、前言

最近騰訊弄出一個Tinker熱修復框架,那麼本文先不介紹這個框架,先來介紹一下阿里的一個熱修復框架AndFix,這個框架出來已經很長時間了,但是看網上沒有太多非常詳細的講解,這裏就來做一次分析。正好項目中要使用到。首先這個框架是開源的,可以在github上搜:AndFix 。其實在最早的時候我已經分析了阿里的另外一個熱修復框架:Dexposed框架,還不瞭解的同學可以點擊這裏查看:Dexposed框架原理解析以及使用 當時介紹這個框架的時候發現他的實現原理很簡單:

他的思想完全來源於Xposed框架,完美詮釋了AOP編程,這裏用到最核心的知識點就是在native層獲取到指定方法的結構體,然後改變他的nativeFunc字段值,而這個值就是可以指定這個方法對應的native函數指針,所以先從Java層跳到native層,改變指定方法的nativeFunc值,然後在改變之後的函數中調用Java層的回調即可。實現了方法的攔截功能。

 

二、框架源碼分析

那麼本文介紹的AndFix框架相對於Dexposed框架來說又有什麼區別呢?其實區別就在於AndFix框架更加輕便好用,在進行熱修復的過程中更加方便了。當然這個優點在後面的Tinker框架也是能提現出來的。這個框架的原理是:直接在native層進行方法的結構體信息對換,從而實現完美的方法新舊替換,從而實現熱修復功能。下面通過分析他的源碼來看他的具體實現,下載完源碼之後導入工程:

這裏可以看到,因爲在native層需要替換新舊方法結構體信息,所以這裏肯定要做的工作就是虛擬機的兼容問題,這裏做了art和dalvik的分開處理邏輯,下面來看一下這個框架的基本使用規則:

用法還是很簡單的,這裏的修復包是直接放在本地的,在實際操作中會從網上去下載。下面就開始分析源碼,這裏有一個主要的類就是PatchManager:

 

第一、PatchManager類初始化

在這個類的構造方法中做了兩件事,一件事是初始化AndFixManager類,一件事是創建修復包存放的沙盒目錄。這裏先來看第二件事創建沙盒目錄:

可以看到這個目錄是:

/data/data/xxx/files/apatch/xxx.apatch

當我們從網上下載好修復包apatch文件之後,會調用addPatch方法,這時候會把修復包複製到這個地方,以後再次啓動時就會遍歷這個目錄加載apatch文件。

 

第二、AndFixManager的初始化

下面繼續來看AndFixManager初始化操作:

在這個初始化中也是幹了兩件事:一件事是判斷當前環境是否支持熱修復,一件事是初始化修復包安全校驗的工作,先來看一下判斷是否支持操作:

這裏支持的條件是:非YunOS系統,Android2.3-7.0系統版本,熱修復native層設置是否成功。這裏我們看到應該值得關心的是setup操作,這個操作其實是native層進行的,可以直接看一下具體代碼:

這裏主要做了一些初始化操作,獲取一些函數指針,準備後續的replaceMethod函數中使用:

1、在libdvm.so動態獲取dvmDecodeIndirectRef函數指針和獲取dvmThreadSelf函數指針。
2、調用dest的 Method.getDeclaringClass方法獲取method的類對象clazz。

 

繼續回到AndFixManager的初始化中的第二件事:修復包安全校驗工作,其實這裏只是做了初始化操作,而真正校驗的工作在後面,主要是通過比對應用的簽名和修復包的簽名信息。後面再看吧!

 

第三:PatchManager的初始化操作

這裏的初始化做了一件事:是判斷當前PatchManager的版本號是否發生變化,如果發生變化就清空本地所有的修復包。如果沒有變化,直接調用初始化修復包方法

這個方法其實就是我們上面提到的邏輯,會遍歷沙盒中修復包目錄中所有的修復包文件,然後把它添加到修復列表中。

 

第四:PatchManager的添加修復包操作

這裏提供了兩個添加修復包的方法,一個是接受文件樣式的參數:

這個方法就是上面的那個initPatchs方法中調用的地方,這裏會把目錄下所有的修復包文件加到列表中。這裏來看一下另外一個主要類Patch的初始化操作:

這個初始化的主要工作就是通過JarFile類解析修復包文件,讀取他的META-INF\PATCH.MF文件內容,獲取需要修復類的名稱,多個修復類之間用逗號分隔,類似於這樣的樣式:

這裏的Utils類就是我們需要修復的方法所屬的類名,但是這裏他又做了處理就是在每個類的後面加了後綴:_CF。這裏分析完了所有需要修復的類名之後保存到一個列表中,後面會通過修復包名稱獲取到他的修復類名稱列表。

 

還有一個添加修復包文件的方法,接受的是文件路徑參數:

這個方法接受的是修復包文件路徑,首先會把這個文件拷貝到上面提到的沙盒目錄中以便下次進行遍歷操作,拷貝之後繼續調用上面的addPatch方法添加到列表中,最後就在調用加載修復包操作了。

 

注意:

第一個方法接受文件樣式的方法,其實是需要結合上面的initPatchs方法一起使用,他調用的場景是:本地沙盒目錄中已經有了修復包文件,並且版本號沒有發生變化,這樣每次啓動程序的時候就會調用初始化操作,在這裏會遍歷沙盒目錄中所有的修復包文件,然後調用這個方法添加到全局文件列表中。

第二個方法接受的是文件路徑樣式,這個方法使用的場景是版本號發生變化,或者是本地沙盒中沒有修復包文件。比如第一次操作的時候,會從網絡上下載修復包文件,下載成功之後會把這個文件路徑通過這個方法調用即可,執行完之後也會主動調用加載修復包的操作了,比如這裏第一次在SD卡中放了一個修復包文件:

只要調用了這段代碼之後就可以走完了所有的流程了:拷貝修復包到沙盒目錄中,加載修復包文件。

 

第五:PatchManager的加載修復包操作

這個方法有兩個地方會調用到:一個是上面提到的那個接受修復包路徑的addPatch方法,一個是調用完接受修復文件類型的addPatch方法之後手動調用一次,類似於這樣:

這個方法內部主要是通過Patch類獲取修復包所有的修復類名稱,之前已經介紹了Patch類的初始化操作,在哪裏會解析修復包的MF文件信息,獲取到修復包需要修復的類名然後保存到列表中,這裏就通過getClasses方法來獲取指定修復包名稱對應的修復類名稱列表,然後在調用AndFixManager的fix方法即可,下面再來看一下AndFixManager的fix方法的實現邏輯:

這個方法有點長,而且內容也比較多,這裏主要做了這麼幾件事:

第一件事:使用上面初始化完成的校驗類進行修復包的校驗工作

這裏的校驗就是比對修復包的簽名和應用的簽名是否一致:

這個具體實現邏輯不用介紹了,大家可以下載源碼自己分析。

 

第二件事:使用DexFile和自定義類加載器來加載修復包文件

這個其實和使用DexClassLoader加載原理類似,而且DexClassLoader內部的加載邏輯也是使用了DexFile來進行操作的,而這裏爲什麼要進行加載操作呢?因爲我們需要獲取修復類中需要修復的方法名稱,而這個方法名稱是通過修復方法的註解來獲取到的,所以咋們得先進行類的加載然後獲取到他的方法信息,最後通過分析註解獲取方法名,這裏用的是反射機制來進行操作的。

這個加載完類之後就會繼續調用fixClass方法,再來看一下fixClass方法實現:

 

這裏主要是通過反射獲取指定類名需要修復類中的所有方法類型,然後在獲取到他的註解信息,上面已經分析了通過DexFile加載修復包文件,然後在加載上面Patch類中的getClasses方法獲取到的修復類名稱列表來進行類的加載,然後在用反射機制獲取類中所有的方法對應的註解信息,通過註解信息獲取指定修復的方法名稱,看一下這個註解的定義:

這裏提供了兩個方法,一個是獲取當前類名稱,一個是獲取當前方法名稱,可以看一下具體事例:

上面解析完註解信息之後獲取到了方法名稱,緊接着就調用了replaceMethod方法開始了方法的替換操作

這裏還會做一件事就是通過上面得到的修復新的方法信息以及需要修復的舊方法名稱來操作,不過這裏得先獲取到舊方法類型,可以看到修復的新舊方法的簽名必須一致,所謂簽名就是方法的名稱,參數個數,參數類型都必須一致,不然這裏就報錯的。進而也修復不了了。最後在調用了AndFix的addReplaceMethod方法進行native層的修復工作:

這裏會做虛擬機的區分處理,但是他們大致的處理邏輯都是一致的,這裏來看一下dalvik的處理機制:

這裏的操作也是非常簡單的,主要是通過上層傳遞過來的新舊方法類型對象,通過JNIEnv的FromReflectedMethod方法獲取對應的方法結構體信息,然後將其信息進行替換即可,這裏可以看到替換的信息也是非常多的,而且也看到了我們之前介紹Dexposed框架用到的一個字段值nativeFunc,這個就是指定這個Java方法對應的native方法。但是在這之前也會看到有一段代碼是用來獲取修復方法的類信息的,這裏主要是用來做修復方法的類初始化操作,在之前我們看setup方法的時候知道,那裏做了這麼兩件事:

  • 1、在libdvm.so動態獲取dvmDecodeIndirectRef函數指針和獲取dvmThreadSelf函數指針

  • 2、調用dest的 Method.getDeclaringClass方法獲取method的類對象clazz

然後在這裏就開始獲取修復方法對應的類信息,通過調用方法的getDeclaringClass獲取方法對應的類對象clazz,然後在調用dvm方法獲取到對應的類結構體信息ClassObject,最後在設置他的狀態信息標記這個類已經初始化完畢了。

 

注意:這裏可以看到通過一個類對象clazz類型可以獲取到對應的結構體信息ClassObject,這個操作也是非常實用的,因爲這個結構信息中有一個字段pDvmDex值,而這個值就是DvmDex結構體信息也就是底層對應的dex文件信息,所以說我們可以通過一個類信息得到他對應的dex文件信息

 

三、流程總結

到這裏就講解完了整個框架的所有技術點了,上面可能說的有點亂,下面在來大體總結一下,首先來看一張簡單的流程圖信息(圖片有點大,可以下載看高清大圖):

 

第一、Patch類負責解析每個修復包apatch文件信息,獲取所有需要修復的類名

這個類的初始化操作中會通過傳遞進來的修復包文件,使用JarFile類進行文件解析,讀取他的META-INF\PATCH.MF文件信息,主要通過讀取Patch-Classes字段值來獲取需要修復的類名稱,多個類名稱之間用逗號分隔,而且每個類名稱都有一個後綴:_CF。解析完成之後就會保存到一個用修復包名稱作爲key的HashMap中,後面會通過修復包名稱參數來調用getClasses方法獲取對應修復的類名稱列表。


第二、PatchManager負責管理多個Patch類也就是多個修復包信息

主要方法包括初始化,添加修復包,加載修復包,這個類是提供給外界調用的一個入口類,這裏有兩種方式調用:

一種方式是先調用init方法進行初始化,在這個初始化方法中會判斷當前的版本號,如果版本號發生變化就會清空本地所有的修復包文件,如果沒有變化就加在所有的本地修復包文件,而這個本地目錄就是沙盒中存放修復包文件的目錄:/data/data/xxx/apatch/xxx.apatch,然後在調用loadPatch方法進行修復包的加載工作。

一種方式是本地沒有修復包文件,也就是第一次操作的時候可能需要從服務器下載修復包文件,這時候會把下載下來的修復包文件路徑通過調用addPatch方法進行添加操作,而這裏的添加操作包括了:先把修復文件拷貝到上面的沙盒修復包目錄中,然後在調用loadPatch方法進行加載工作。


第三、AndFix類主要是和native層交互直接替換方法

這個類主要就是幾個native方法用來和底層進行交互的操作,而這些方法都是會被AndFixManager進行調用的。


第四、AndFixManager類主要是負責管理AndFix類

主要方法包括加載每個修復包中需要修復的類,解析出每個類的註解信息獲取該類需要修復的方法名稱,初始化的時候會進行修復包的校驗工作,主要通過對比修復包和應用的簽名信息。所以可以知道每個修復包是需要進行簽名操作的,然後他的fix方法會使用DexFile類進行加載修復包文件,調用Patch的getClasses方法獲取到所有需要修復的類名稱進行加載操作。然後在調用fixClass方法,在這個方法中主要通過遍歷修復類中所有指定MethodReplace註解信息的方法信息,然後在調用replaceMethod方法進行替換操作,而在這個方法中也會通過新方法的Method類型和註解信息中需要修復方法的名稱來得到舊方法的Method類型,最終調用AndFix的native方法replaceMethod進行替換操作,所以這裏可以看到替換的新舊方法的簽名信息必須一致,不然無效,也就是方法的名稱,參數個數,參數類型必須保持一致纔可以。

 

第五、Native層方法

在native層中會做art和dalvik虛擬機的區分處理工作,他們大致的邏輯都是一致的:

  • =>dalvik 模式下的Java hook
    1、在libdvm.so動態獲取dvmDecodeIndirectRef函數指針和獲取dvmThreadSelf函數指針。
    2、調用dest的 Method.getDeclaringClass方法獲取method的類對象clazz。
    3、調用dvmDecodeIndirectRef方法,獲取clazz的ClassObject*
    4、通關 env->FromReflectedMethod方法獲取dest的Method結構體函數的指針
    5、替換method結構體的成員數據

  • =>art模式下的java hook
    1、art模式中,我們直接通過 env->FromReflectedMethod獲取到ArtMethod函數指針。
    2、然後直接替換ArtMethod結構體的成員數據指針

 

四、框架使用案例

上面介紹完了原理,下面如果不用案例來做分析,那都是白扯淡,這裏我們就用一個簡單的案例來進行實際操作一下,而且在這個過程中會發現一個神奇的工具apatch,這裏的例子很簡單,本地定義一個獲取版本號的方法:

這時候我們得到這個值,然後顯示在界面上,然後開始出release包,這裏直接用eclipse構造簽名文件(這個簽名文件要記得保存好,後面會使用到)出包了。等包上線發佈之後,突然發現這個版本號錯了,應該是1.0.2,那麼這時候就需要進行熱修復了,操作很簡單:

第一步:修改這個方法返回值爲1.0.2

第二步:繼續使用上面的簽名文件進行簽名得到了一個修復之後的apk包

第三步:使用神器apatch進行線上發佈的release包和這次修復的fix包進行比對,獲取到修復文件apatch的命令:

java -jar apkpatch.jar -f app-release-fix.apk -t app-release-online.apk -o C:\Users\jiangwei1-g\Desktop\apkpatch-1.0.3 -k jiangwei.keystore -p 123456 -a jiangwei -e 123456

這裏在使用命令的時候需要用到簽名文件,因爲在前面分析代碼的時候知道會做修復包的簽名驗證。這裏得到了一個修復包文件如下:

而且會產生一個diff.dex文件和smali文件夾,這個就是修復類的文件對應的dex文件和smali代碼:

而我們用壓縮軟件可以打開apatch文件看看:

可以看到這裏的classes.dex文件其實就是上面的diff.dex文件,只是這裏更像是Android中的apk文件目錄格式,同樣有一個META-INF目錄,這裏存放了簽名文件以及需要修復類信息的PATCH.MF文件:

簽名文件就不多說了,來看一下PATCH_MF文件信息:

Patch_Classes字段包含了需要修復的類的名稱信息了。

 

第四步:這裏爲了演示方便,直接把上面的修復文件拷貝到sd卡中,然後調用PatchManager的addPatch方法:

第五步:運行程序

這時候可以發現版本號已經修復成了1.0.2了。

 

五、apatch工具原理解析

上面看到案例使用比較簡單,但是看到有一個比較牛逼的工具就是apatch,可以生成有方法變動的類所在的dex文件,那下面就來一起看看他的實現原理,沒找到源碼,直接使用jd-gui查看apatch.jar文件了:

這裏的核心代碼就是這部分,會把有方法變動的類信息列表對象DexBackedClassDef藉助baksmali類寫入到smali文件中,然後在藉助DexBuilder和SmaliMod類把smali類變成dex文件,也就是最終的diff.dex文件了。那麼下面在來看一下這個變動的DexBackedClassDef類列表信息如何得到的:

在這裏使用DexBackedDexFile類進行加載新舊的dex文件,然後開始比對具體方法實現變動情況,主要是方法:compareMethod的實現:

這裏會調用方法的getImplementation方法來判斷新舊方法的實現發生變動了,如果有就把當前的類對象加入變動列表中即可。所以從這裏可以看到這裏其實是完全藉助了第三方的功能:可以把dex變成smali文件的baksmali工具包、可以把smali變成dex文件的smali工具包。而這兩個工具包的源碼之前在介紹apktool反編譯工具的時候說到了,從這裏可以看到,我們後續再處理dex,smali等文件格式的時候這兩個工具包用的非常多。

 

六、框架技術總結

到此此次修復操作就完成了。我們的講解工作和案例演示工作也完成了,下面來總結一下這個框架的知識點,不過先來看一張大圖(可以點擊下載查看高清大圖):

第一、核心技術點

從上面可以看到AndFix框架的技術點主要包括:

  • 使用apatch工具生成修復包文件,主要藉助baksmali和smali工具包實現

  • Java層傳遞新舊方法類型對象,到native層獲取其對應的結構體信息實現完美替換新舊方法結構信息

第二、優點和侷限性

  • 優點:從上面可以看到這個框架的優點在於輕巧便捷,集成成本低,維護性強。

  • 侷限性:從上面的代碼分析可以看到這個框架的侷限性還是很多的,特別是他只能修復對應已經存在的方法,比如現在我想增加一個方法肯定不行的,如果想給修復方法增加參數信息也是不可以的,這個侷限性就非常大了。還有一個侷限性就是隻能進行代碼修復,資源是無法做到的。所以從這裏可以看到這個框架更偏重於方法的熱修復操作。

 

本文的目的只有一個就是學習更多的逆向技巧和思路,如果有人利用本文技術去進行非法商業獲取利益帶來的法律責任都是操作者自己承擔,和本文以及作者沒關係,本文涉及到的代碼項目可以去編碼美麗小密圈自取,歡迎加入小密圈一起學習探討技術

 

七、總結

在開發過程中現階段熱修復技術還是很火的,而一些大公司也相繼給出了一些熱修復的合理方案,每家都有各自的優點和缺點,而我就要做到每家熱修復框架的詳細原理解析,從中能夠學習到更多的技巧和知識,後續還會繼續分析美團的Robust和騰訊的Tinker框架,多謝支持啦啦!頂着頭疼的風險寫完了這篇文章,記得多點贊多分享!

 

《Android應用安全防護和逆向分析》

點擊立即購買:京東  天貓  

更多內容:點擊這裏

關注微信公衆號,最新技術乾貨實時推送

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章