1. 程式人生 > >深入理解Android之Xposed詳解

深入理解Android之Xposed詳解

一、背景

Xposed,大名鼎鼎得Xposed,是Android平臺上最負盛名的一個框架。在這個框架下,我們可以載入很多外掛App,這些外掛App可以直接或間接操縱系統層面的東西,比如操縱一些本來只對系統廠商才open的功能(實際上是因為Android系統很多API是不公開的,而第三方APP又沒有許可權)。有了Xposed後,理論上我們的外掛APP可以hook到系統任意一個Java程序(zygote,systemserver,systemui好不啦!)。

功能太強大,自然也有缺點。Xposed不僅僅是一個外掛載入功能,而是它從根上Hook了Android Java虛擬機器,所以它需要root,所以每次為它啟用新外掛APP都需要重新啟動。而如果僅是一個外掛載入模組的話,當前有很多開源的外掛載入模組,就沒這麼複雜了。

Anyway,Xposed強大,我們可以學習其中的精髓,並且可以把它的思想和技術用到自己的外掛載入模組裡。這就是我們要學習Xposed的意義。

Xposed支援32位和64位的dalvik以及ART,同時支援selinux。我仔細看了下,如果拓展把這些東西都講的話,一個是很枯燥,另外一個是背離了我們本章講解外掛的主線。所以,本章將圍繞下面幾個點開展介紹:

l  32位dalvik上非selinux模式下的xposed實現原理

64位、selinux模式其實難度不在虛擬機器上,而是在selinux上。

二、  初識Xposed

本章先來介紹下Xposed,這是一個大工程,包含多個專案,我也是花了不少時間才把它整個玩轉起來的。Xposed包含如下幾個工程:

  • XposedInstaller,這是Xposed的外掛管理和功能控制APP,也就是說Xposed整體管控功能就是由這個APP來完成的,它包括啟用Xposed外掛功能,下載和啟用指定外掛APP,還可以禁用Xposed外掛功能等。注意,這個app要正常無誤得執行必須能拿到root許可權。
  • Xposed,這個專案屬於Xposed框架,其實它就是單獨搞了一套xposed版的zygote。這個zygote會替換系統原生的zygote。所以,它需要由XposedInstaller在root之後放到/system/bin下。
  • XposedBridge。這個專案也是Xposed框架,它屬於Xposed框架的Java部分,編譯出來是一個XposedBridge.jar包。
  • XposedTools。Xposed和XposedBridge編譯依賴於Android原始碼,而且還有一些定製化的東西。所以XposedTools就是用來幫助我們編譯XposedXposedBridge的。

提示,提示,提示:

我把所有相關程式碼都下載並放到下面的地址了:

1  xposed所有程式碼庫的內容

2  XposedDemo:Xposed外掛APP Demo,非常簡單

3  XposedDemoTarget:XposedDemo將hook上的目標App

2.1  編譯Xposed

下面介紹下如何編譯Xposed,這裡以Android 4.4.4為例。做Android開發最好配一個Nexus手機或Pad。我用得是Nexus 7 2013Wi-Fi版。Anyway,Xposed的編譯和具體機器無關,不過下面的前提條件需要滿足:

  • 準備Android 4.4.4原始碼。我分享了很多AOSP原始碼,注意:我提供的原始碼沒有.repo和.git,雖然省了很多空間,不過在編譯Xposed的時候,還是需要有.repo。
  • 下載我提供的Xposed原始碼和測試Demo。

好,馬上開始我們的步驟:

2.1.1  下載支援庫

根據XposedTools的說明,我們先要修改下AOSP原始碼裡的.repo,具體步驟如下:

  • 進入AOSP/.repo目錄。
  • 在.repo目錄下新建local_manifests目錄。
  • 把XposedTools/local_manifests/下的目標檔案拷貝過去。local_manifests/目錄下是各種API版本(即SDK=19,21之類)對應的xml檔案。由於本例對應的SDK版本是19,所以需要把該目錄下xposed_sdk19.xml檔案拷貝到.repo/local_manifests/目錄下。

這個檔案是什麼內容呢?來看圖1:

這個檔案內容是啥意思呢?

  • <remote>:新添遠端倉庫地址為github。
  • 第一個<project>:為frameworks/base/cmds/下新增加xposed工程。當然,這個工程我們剛才下載過了。你可以直接copy到指定目錄下。
  • <remove-project>:移除AOSP/build目錄。xposed有自己的處理。
  • 第二個<project>:下載xposed自己的build。path="build",表示下載路徑就是AOSP/build。

配置好後,請在AOSP目錄下執行repo sync。這樣它會根據manifests更新AOSP原始碼。當然,也可以只是下載frameworks/base/cmds/xposed工程和更新build工程。

注意,repo sync是一個重型操作,會導致所有工程都進行一次同步。我建議的方法是直接下載和更新對應的工程

比如,下載xposed工程,用repo sync frameworks/base/cmds/xposed即可。對於build目錄,先把原來的build挪到其他地方。然後repo sync build即可。

好了,到此,所有原始碼都已經ready了。

2.1.2  修改build.conf

下面我們進入XposedTools目錄,然後修改其中的build.conf檔案。該檔案用於指示AOSP原始碼等引數。如圖2所示:

XposdTools提供了一個build.conf.sample模板,圖2中的build.conf檔案是在這個模板基礎上修改而來。紅框中是我修改的結果。其他選項沒有變化。

2.1.3  執行build.pl

到XposedTools目錄下,執行:./build.pl -t arm:19命令,這表明我要編譯arm平臺上SDK=19版本的xposed框架。注意,./build.pl --help會打印出使用方法。build.pl是一個perl指令碼。圖3是編譯過程截圖:

圖3中,你可以發現build.pl跑到AOSP原始碼目錄下,執行了:

  • . build/envsetup.sh:初始化AOSP編譯環境
  • lunch aosp_flo-userdebug:選擇交叉編譯平臺。注意,這一塊我是修改了XposedTools/xposed.pom,使它單獨為我的nexus 7編譯,而不是針對ARM平臺做generic的編譯。
  • make -j4 xposed libxposed_dalvik:編譯xposed和libxposed_dalvik這兩個目標檔案。

在使用build.pl時,它還依賴一些Perl的類庫,請童鞋們按照下面步驟下載這些依賴庫:

sudo apt-get install libconfig-inifiles-perl

sudo apt-get install libio-all-perl

sudo apt-get install libfile-readbackwards-perl

sudo apt-get install libfile-tail-perl

sudo apt-get install libtie-ixhash-perl

build.pl執行過程中,如果報還有其他依賴庫未找到,請通過下面命令

apt-cache search perl XXX  來查詢需要apt-get install哪個目標庫。XXX是build.pl執行過程中報錯時提供的庫資訊

2.1.4  編譯結果

編譯完成後,將產生一個zip包到AOSP/out/sdk19/arm下。AOSP/out是我在build.conf中指定的目錄。如圖4所示:

編譯結果是一個xposed-v65-arm-custom-build-xyz-20151030.zip包,這個包可以通過recovery刷到手機上。包的內容就是files資料夾下的內容,包含:

  • system/bin/app_process_xposed:xposed版zygote。
  • system/bin/libxposed_dalvik.so:xposed框架的native層。
  • system/xposed.prop:xposed框架資訊,包含版本號等。內容如右邊圖所示。

三、  XposedInstaller

XposedInstaller是Xposed的App,用於管理Xposed框架和外掛App。本節我們主要討論它是如何安裝Xposed框架和外掛App的。

XposedInstaller啟動介面如圖5所示:

圖5中可知XposedInstaller提供好幾項子頁面。第一個“框架”用來安裝或解除安裝Xposed框架的。我們來看它。

3.1  安裝Xposed框架

如圖6所示:

注意,圖6右上角的“程式自帶”兩個版本號,分別是app_process版本號為58,XposedBridge.jar版本號是54.

而“啟用”這兩項為空,因為我們的系統還沒有安裝Xposed框架。

“程式自帶”是什麼意思?原來,XposedInstaller在自己的assets目錄下攜帶了所需要的xposed框架程式和模組,如圖7所示:

從圖7可知,XposdInstaller自帶了xposed版zyote(比如app_process_xposed_sdk16),為了更好得支援不同版本的Android,它還區分了SDK版本。另外,XposedInstaller也支援刷機包把xposed框架模組刷入系統,比如Xposed-Installer-Recovery.zip,裡邊包含的主要內容就是一個指令碼,在recovery模式下執行,其內部也是把assets裡的檔案拷貝到/system相關目錄中。這一塊我們後續看程式碼就知道怎麼玩兒了。

安裝Xposed框架的主要功能由InstallerFragment.java提供,我們看看相關程式碼。

3.1.1  onCreateView

onCreateView函式是Fragment裡初始化UI的核心回撥,其程式碼如圖8所示:

圖8程式碼中最後的refreshVersions用於獲取版本號,也就是圖6中右上角“程式自帶”要顯示的資訊,它包括兩個東西:

  • xposed版app_process的版本,包括程式自帶(也就是XposedInstaller放在assets目錄下的)和系統安裝的。當然,只有該系統安裝過Xposed版app_process才有必要檢查它的版本。圖6中由於沒有裝過,所以“啟用”那一欄顯示為“-”。
  • XposedBridge.jar:也包括程式自帶和系統已安裝兩個jar包的版本。

檢查版本主要是為了相容性考慮。程式碼中的refreshVersions用於獲取他們的版本號,程式碼如圖9所示:

圖9中:

  • getInstalledAppProcessVersion:讀取/system/bin/app_process的版本號。
  • getLatestAppProcessVersion:讀取自帶app_process_sdkXX的版本號。
  • getJarInstalledVersion,讀取/data/data/de.robv.android.xposed.installer/bin/ XposedBridge.jar版本號。對,你沒看錯,XposedBridge.jar是放在XposedInstaller自己的目錄下的。
  • getJarLatestVersion:讀取自帶XposedBridge.jar的版本號。

想知道怎麼獲取版本系想你嗎?來看圖10:


 

  •  對於app_process,直接讀取檔案內容,然後找到紅框中的那一行,後面的58就是版本號。
  •  對於XposedBrdige.jar,開啟這個jar包,讀取assets/VERSION的內容,裡邊就是儲存了一個版本號資訊。

太簡單了,不惜得說。

注意XposedBridge.jar包安裝版的位置,它在/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar裡

3.1.2  安裝xposed框架

InstallerFragment的install函式用於安裝xposed框架相關的模組。這個函式一點也不復雜,不過還是給大家看看。如圖11所示:

install函式沒什麼難度,但是我們要總結下xposed框架安裝到底做了什麼手腳:

  • 將xposed版的app_process拷貝到/system/bin/,系統原來的app_process改名為app_process.orig
  • 將XposedBridge.jar放到/data/data/de.robv.android.xposed.installer
  • 刪除/data/data/de.robv.android.xposed.installer/conf/disabled檔案。如果有disabled檔案則表明整個Xposed功能被禁止使用。

嗯嗯,也沒什麼太多可說的。

3.1.3  安裝xposed外掛

xposed外掛,在xposed世界裡我們說它是外掛,但是放到Android世界裡它就是一種特殊的APP。這種型別的APP由xposed框架識別並載入,然後hook到其他的App程序。

當然,這裡既然提到程序,那麼這些APP就必須要被先啟動起來才是。

XposedInstaller的外掛管控由ModulesFragment介面來處理。本節主要想介紹下XposedInstaller是怎麼對待Xposed外掛APP的。

來看程式碼,如圖12所示:

我們重點來看看Xposed外掛APP是怎麼被load的,程式碼其實在ModuleUtil的reloadInstalledModules函式中。程式碼很簡單如圖13所示:

圖13左下角已經告訴各位Xposed外掛APP該是什麼樣的了,右下角是XposedDemo示例。

在AndroidManifest.xml裡定義這些東西只是告訴Xposed自己是一個外掛APP。但是作為一個掛鉤外掛,Xposed還需要知道這個APP裡哪個類是用來掛鉤的。這句話的意思是:

1  這個APP是一個外掛APP。該APP包含很多功能。

2  這個APP包含的眾多功能中,有一個功能是給目標程序掛鉤。掛鉤操作是Xposed框架來做的,所以它需要知道該APP中哪個類是繼承了Xposed鉤子介面。這樣,Xposed框架找到外掛APP之後會觸發這個app的鉤子介面進行掛鉤。

要做到第二步就得在assets/下放一個名叫xposed_init檔案,裡邊指明外掛APP的掛鉤類名。XposedDemo的

assets/xposed_init的內容就是:com.xposed.demo.MyXposedModule。當然,這個外掛類必須實現Xposed的IXposedHookLoadPackage介面類。這個我們以後再討論。

四、  Xposed框架介紹

Xposed框架分為xposed版app_process和XposedBridge.jar兩部分。app_process就是zygote,我們先看看xposed版的zygote幹了些什麼。

4.1  Xposed版zygote

注意,本章只分析32位,dalvik版的xposed app_process,其入口main函式位於app_main.cpp裡。

圖14所示的程式碼展示了Xposed版zygote與眾不同之處。

圖14中,左上角的框是app_main的main函式,裡邊有兩處不同之處:

  • AppRuntime:此處的AppRuntime類是xposed版的AppRuntime。它的與眾不同之處從左下圖的onVmCreated開始。當檢查到isXposedLoaded為真是時,將呼叫xposed::onVmCreated函式。
  • xposed::onVmCreated函式位於右上框,它先判斷當前虛擬機器是ART還是dalvik,然後載入xposed一個特殊so,此處是/system/bin/libxposed_dalvik.so。注意,這裡的xposed框架和XposedInstaller略有區別。因為XposedInstaller比較舊了(也就支援sdk 16的樣子),還沒有涉及到ART和dalvik的區別。在onVmCreate中,它將呼叫libxposed_dalvik.so中的xposedInitLib函式,然後再呼叫so中設定的onVmCreated函式。這個onVmCreated函式由xposedInitLib設定。
  • 左上框中還有一個xposed:initialized函式。這個函式初始化了XposedShared型別的全域性變數xposed,同時還把一個重要的jar包加到了CLASSPATH裡。來看程式碼。如圖15所示:

圖9展示了initialize函式的內容,主要是最後一個把/system/bin/XposedBridge.jar(這個jar包的位置和我們在XposedInstaller那裡看到得不同,原因前面解釋過了)加到CLASSPATH比較重要。

注意,我們這裡雖然對initialize介紹的內容很少,但實際上這個函式要真正看明白還是很需要技術實力的:

1  logcat:start:裡邊fork了logcat程序用來儲存xposed自己的log

2  service:startAll:為了完美支援selinux,這裡的處理更是很有技巧。selinux是一個完整的知識體系,想徹底掌握它的童鞋請參考我的三部曲文章《深入理解SELinux SEAndroid》 

1&2其實很真實得反映出xposed的作者在Android、Linux上水平很高,經驗很豐富。

最後,如果xposed框架啟用成功,那麼zygote的入口類將由以前的com.android.internal.os.ZygoteInit變成de.robv.android.xposed.XposedBridge

下面我們按照執行流程,把相關函式分析一遍:

  • AppRuntime的onVmCreated,它最終會導致libxposed_dalvik.so中的onVmCreated被呼叫。我們直接分析最後這個onVmCreated
  • 然後AppRuntime裡會呼叫de.robv.android.xposed.XposedBridge.main函式。

4.2  onVmCreated

圖16為程式碼:

onVmCreated比較簡單了:

  • 針對android/content/res/XResource&XTypedArray進行了一些處理,這是為將來資源替換時候做準備。以後我們碰到再說。
  • XposedBridge註冊JNI函式

xposed官方說MIUI大量用了xposed的東西,並且不共享,以後碰到MIUI的問題他們不再支援。Don't know what to say....

4.3  XposdBridge.main函式

main函式程式碼如圖17所示:

main函式裡有三個重要函式:

  • initNative:好好學習下
  • initForZygote:好好學習下
  • loadModules:載入App外掛

4.3.1  initNative

initNative很重要,來看程式碼,如圖18所示:

圖18中,我們重點看一下callback_XposedBridge_initNativeregister_natives_XResources這兩個函式。這兩個函式比較簡單,我們統一放到圖19中:

不多說了,沒什麼難度。

4.3.2  initForZygote

從這個函式開始,xposed就開始給系統一些關鍵函式掛鉤子了。我們看看它怎麼玩兒的。程式碼如圖20所示:

圖20中我在eclipse裡用了程式碼縮略顯示的方法,可知一共有五個框,分別hook了一些關鍵內容。我們從上到下一次分析。先來分析下Xposed框架提供的掛鉤函式findAndHookMethod

(1)  findAndHookMethod介紹

findAndHookMethod用來對指定類的指定函式進行掛鉤。這個函式很重要,開發外掛APP時用得最多。來看它的程式碼,如圖21所示:

findAndHookMethod程式碼Java層面的邏輯還是比較好理解的:

  • 首先是通過class相關的函式找到指定的函式物件。這裡的查詢需要指定函式所在的類,函式名,函式引數資訊等,屬於精確(exact)查詢。注意,findAndHookMethod函式的最後一個引數必須是XC_MethodHook型別,即鉤子物件的資料型別。
  • 然後就是XposedBridge.hookMethod

來看hookMethod,如圖22所示:

hookMethod可知,一個目標函式可以掛多個鉤子,這些鉤子由一個集合來儲存。然後我們將轉到JNI層去看看hookMethodNative幹了什麼事情。這才是hook的核心。程式碼如圖23所示:

hookMethodNative完成了真正的掛鉤處理,其思想很簡單:

  • 先找到目標函式在native層對應的Method物件。
  • 修改這個Method為native方法,並設定nativeFunchookedMethodCallback函式。
  • 然後還要儲存原函式的一些資訊到insns域。

我們在《深入理解Android之dalvik》一文中介紹過,JVM呼叫java函式時候,發現這個函式為