日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Xposed是如何为所欲为的?

發布時間:2024/4/15 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Xposed是如何为所欲为的? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文通過分析 Xposed 的實現原理,對于 Java 方法的 Hook 原理有了一些了解,同時回顧了 Android zygote 進程相關的內容,對于 ART 虛擬機執行方法的過程也有了一個大概的認識。

Pangu-Immortal (Pangu-Immortal) · GitHub


1、前言

Xposed 是 Android 平臺上著名的 Java 層 Hook 框架,通過在 Android 設備上安裝 Xposed 框架,編寫 Xposed 模塊,可實現對任意 Android 應用的 Java 方法的 Hook,以及應用資源的替換。

(Hook 是一種函數鉤子技術,能夠對函數進行接管,從而修改函數的返回值,改變函數的原始意圖)

本文將基于 Xposed 最新的開源代碼對 Xposed 的實現原理進行分析。

Xposed 有兩種實現版本,一個是基于 Dalvik 虛擬機的實現,它是針對早期的 Android 4.4 之前的 Android 設備設計的;另一個是基于 ART 虛擬機的實現,自 Android 5.0 系統開始,Android 系統正式采用了 ART 虛擬機模式運行,Dalvik 就成了歷史,目前市面上幾乎所有的手機都是以 ART 模式運行的,下面將主要對于 ART 上的 Xposed 實現進行詳細分析,對于 Dalvik 上的 Xposed 的實現,進行必要性的分析。

通過了解 Xposed 的實現原理可以學到在 Android 平臺上對于 Java 層代碼的一種 Hook 機制的實現,同時復習 Android 系統的啟動原理以及增加對于 Android ART 虛擬機運行原理的了解。

2、Xposed 使用方法

在對 Xposed 進行分析之前,先回顧一下 Xposed 基本 API 的使用。

Xposed 的核心用法就是對一個 Java 方法進行 Hook,它的典型調用如下:

XposedHelpers.findAndHookMethod(Application.class,?"onCreate",?Context.class,new?XC_MethodHook()?{@Override?protected?void?beforeHookedMethod(MethodHookParam?param)?throws?Throwable?{Application?app?=?(Application)?param.thisObject;Context?context?=?(Context)?param.args[0];Log.d(TAG,?"Application#onCreate(Context);?this:?"?+?app?+?"?arg:?"?+?context);param.setResult(null);}@Override?protected?void?afterHookedMethod(MethodHookParam?param)?throws?Throwable?{super.afterHookedMethod(param);}});

以上代碼的意思是對 Application 這個類的 onCreate 方法進行 Hook,并使用 XC_MethodHook 對象提供一個 Hook 處理方法來接管原來方法的邏輯,當應用的 Application 類型的 onCreate 方法被調用時,beforeHookedMethod 將在被調用之前執行,同時 onCreate 的參數將會傳遞給 beforeHookedMethod 方法進行處理,上面的處理只是將參數打印了出來(一個 Context)。

同時還可以拿到被調用的 this 目標對象,也就是 Application 的對象,還可以使用 setResult 方法更改原始方法的返回值,不過這里的 Application#onCreate 方法是 void 返回類型的,setResult 不起作用,如果是其他類型,那么原方法的返回值將被更改。

這樣就達到了修改一個 Java 方法的目的,即改變了原始方法的邏輯和意圖。

public?class?App?extends?Application?{@Override?void?onCreate(Context?context)?{//?...}?? }

可以看到,如果要使用 Xposed 對一個 Java 方法進行 Hook,需要提供要 Hook 方法的名字、參數列表類型和方法所在類,以及處理 Hook 的回調方法。

下面正式開始分析。

3、Xposed 原理概述

首先概述 Xposed 原理,之后再對具體細節進行分析。

Xposed 是一個 Hook 框架,它提供了對任意 Android 應用的 Java 方法進行 Hook 的一種方法,通常它的使用方法如下:

  • 首先按照 Xposed 官網提供的開發規范編寫一個 Xposed 模塊,它是一個普通的 Android 應用,包含一塊開發者自己定義的代碼,這塊代碼有能力通過 Xposed 框架提供的 Hook API 對任意應用的 Java 方法進行 Hook。

  • 在要啟用 Xposed 的 Android 設備上安裝 Xposed 框架和這個 Xposed 模塊,然后在 Xposed 框架應用中啟用這個 Xposed 模塊,重新啟動設備后,Xposed 模塊將被激活,當任意的應用運行起來后,Xposed 模塊的 Hook 代碼將會在這個應用進程中被加載,然后執行,從而對這個應用的 Java 方法進行指定 Hook 操作。

  • 那么根據以上使用方法實現一個 Xposed 框架需要分成如下幾個部分:

  • 提供用于 Hook 操作的 API,為了讓開發者進行模塊開發。它通常是一個 jar 包;

  • 提供一個具有界面的管理器應用,用于安裝和管理 Xposed 本身和 Xposed 模塊;

  • 提供將代碼加載到每一個應用進程中的能力,目的是支持 Xposed 模塊的代碼在進程中使用 Xposed API 進行 Hook 操作;

  • 提供 Hook 任意 Java 方法的能力,為 Xposed 模塊的調用提供支持,當 Xposed 模塊在應用進程中執行時可對方法進行 Hook。

  • 前兩點對于我們開發者來說都很熟悉,沒有什么難點,后面兩點才是實現 Xposed 的核心。

    首先是 Xposed 怎樣實現的將代碼加載到每一個應用進程中(Xposed 是基于 Root 權限實現的,所以有修改 Android 系統的能力)?

    Xposed 是通過修改系統 zygote 進程的實現將代碼注入應用進程中的。

    為了知道 Xposed 是如何修改 Zygote 進程的,下面首先介紹 Android 系統 Zygote 相關內容。

    4、Android zygote 進程

    zygote 進程是 Android 系統中第一個擁有 Java 運行環境的進程,它是由用戶空間 1 號進程 init 進程通過解析 init.rc 文件創建出來的,從 init 進程 fork 而來。

    zygote 進程是一個孵化器。Android 系統中所有運行在 Java 虛擬機中的系統服務以及應用均由 zygote 進程孵化而來。

    zygote 通過克隆(fork)的方式創建子進程,fork 出來的子進程將繼承父進程的所有資源,基于這個特性,zygote 進程在啟動過程將創建 Java ART 虛擬機,預加載一個 Java 進程需要的所有系統資源,之后子進程被創建后,就可以直接使用這些資源運行了。

    自 Android 5.0 系統開始,zygote 不再是一個進程,而是兩個進程,一個是 32 位 zygote,負責孵化 32 位進程(為了兼容使用了 armeabi 和 armeabi-v7a 等 32 位架構的本地動態庫的應用),另一個是 64 位 zygote 進程,負責孵化 64 位應用進程(可加載 arm64-v8a 等 64 位架構本地庫)。

    init 進程是 Android 系統中的 pid 為 1 的進程,是用戶空間的第一個進程,它會在 Android 系統啟動時被內核創建出來,之后會對 init.rc 文件進行解析,init.rc 文件是一個按照特定規則編寫的腳本文件,init 進程通過解析它的規則來創建對應的服務進程。下面看一下 zygote 相關的 rc 文件的內容。

    注:自 Android 5.0 開始,32 位 zygote 啟動內容在 init.zygote32.rc 文件中,64 位 zygote 啟動內容在 init.zygote64.rc 中。

    注:自 Android 9.0 開始,兩個 zygote 啟動配置放在一個文件中 init.zygote64_32.rc。

    這里看一下 Android 8.1 系統的 32 位 zygote 的 rc 文件內容:

    #?init.zygote32.rcservice?zygote?/system/bin/app_process?-Xzygote?/system/bin?--zygote?--start-system-serverclass?mainsocket?zygote?stream?660?root?systemonrestart?write?/sys/android_power/request_state?wakeonrestart?write?/sys/power/state?ononrestart?restart?mediaonrestart?restart?netdwritepid?/dev/cpuset/foreground/tasks

    上面的含義是,創建一個名為 zygote 的服務進程,它的可執行文件在?/system/bin/app_process 中,后面的?-Xzygote、/system.bin 等是可執行文件的 main 函數將要接收的參數。

    具體的 init 進程和 zygote 進程的啟動細節,可以參考之前的文章:

    Android init 進程啟動分析

    Android zygote 進程啟動分析

    那么現在回到 Xposed,Xposed 對 zygote 進程的實現源碼進行修改后,重新編譯出 app_process 可執行文件,替換了系統的 app_process 文件(包括 64 位 zygote),并在其中加載了 XposedBridge.jar 這個 Dex 代碼包,它包含 Xposed 的 Java 層實現代碼和提供給 Xposed 模塊的 API 代碼,那么當 init 進程啟動 zygote 服務進程時,將執行修改過的 app_process 文件,此時 zygote 進程就具有了 Xposed 的代碼,Xposed 可以進行加載 Xposed 模塊代碼等任意操作了。

    所有 Android 應用都是運行在 Java 虛擬機上的,所有的 Android 應用都是 zygote 的子進程,那么當 Android 應用進程啟動后,將具備 zygote 進程加載的所有資源,從而將 Xposed 代碼繼承到了 Android 應用進程中,實現了將 Xposed 代碼加載到每一個進程中的目的。

    接下來是如何實現對應用中 Java 方法的 Hook。Hook 的基本原理如下,將 Java 方法的原始邏輯,轉接到一個中間處理方法上,這個處理方法會對原始 Java 方法的參數進行轉發,轉發到一個用于處理 Hook 的方法上(即 XC_MethodHook 的實現),等處理 Hook 的方法執行自定義邏輯后(自定義邏輯可選擇調用原始邏輯先獲取原始返回值,再處理),再返回新的返回值。

    下面分別是 Xposed 在 Dalvik 虛擬機和 ART 虛擬機下的 Hook 實現。

    5、基于 Dalvik 的方法 Hook

    基于 Dalvik 的 Hook 方案是通過將被 Hook 方法修改為一個 JNI 方法,然后綁定一個 Xposed 自定義處理方法邏輯的函數上來實現的。

    當需要 Hook 一個指定方法時,需要提供要 Hook 方法的名字、參數列表類型和方法所在類型,還要提供一個用于處理 Hook 的回調,回調方法用于修改原始方法的邏輯,它可以接收 Hook 方法的參數,然后返回一個新的返回值。

    首先 Xposed 會取得這個方法的反射表示對象(例如通過 Class.getDeclaredMethod),它是一個 java.lang.reflect.Method 對象,然后取得這個對象的一個私有成員變量 slot 的值,將它和處理 Hook 的回調傳遞給 Xposed 的 Native 層代碼,這個 slot 變量實際上是一個 Java 方法在虛擬機中的索引。

    使用這個索引可以從 Dalvik 中用于表示 Java 類的 ClassObject 映射類型的 directMethod 和 virtualMethods 數組中取出一個 Method 對象,它在虛擬機中表示一個 Java 方法,Xposed 的 Native 層代碼接收到 Xposed Java 層傳遞過來的 slot 變量后,取出虛擬機中的 Method 對象。

    然后將這個 Method 對象的類型設置為 JNI 方法,即前面帶有 native 修飾符的方法,然后將它的 nativeFunc 賦值給一個處理 Hook 邏輯的函數上,這個函數中將對這個 Method 的參數進行處理,傳遞給一開始提供的 Java 層中用于處理 Hook 的回調方法,讓它來決定方法的新邏輯,從而返回新的返回值。此時便完成了 Hook。

    那么調用一個被 Hook 的方法的過程是:

    當一個 Android 應用內的代碼調用一個被 Hook 的方法時,Dalvik 將會進行代碼的解釋執行,Java 方法進入 Dalvik 虛擬機中會被轉化為一個 Method 對象,然后虛擬機判斷這個方法如果是一個 JNI 方法,就會直接調用它綁定的的 nativeFunc 函數,那么就走到了 Xposed 處理 Hook 的函數中,這個函數將這個被 Hook 方法的參數進行轉發,讓 Xposed 模塊提供的處理 Hook 的回調方法來接管原來的邏輯,獲得新的返回值返回給被 Hook 方法,即可完成整個 Hook 操作。

    6、基于 ART 的方法 Hook

    基于 ART 的 Hook 方案相比 Dalvik 要復雜一些,需要重新修改編譯 ART 虛擬機的源碼,重新編譯出 ART 虛擬機的可執行文件 libart.so,替換 Android 系統中的 ART 虛擬機實現。

    它的核心原理就是直接修改一個方法對應的匯編代碼的地址,讓方法直接跳轉到指定地址執行,然后就可以執行自定義的邏輯進行 Hook 處理了。

    ART 虛擬機為了提高執行效率,采用了 AOT(Ahead Of Time,預編譯)?模式運行,在應用運行之前先將整個 APK 包含的 Java 編譯為二進制代碼,然后應用運行時將執行每個方法對應的機器代碼,比采用 JIT(Just In Time Compiler,即時編譯)?的 Dalvik 虛擬機每次在運行時才編譯代碼執行的效率更高。

    前面的過程和 Dalvik 一樣,都需要在 Hook 一個指定方法時,提供要 Hook 方法的名字、參數列表類型和方法所在類型,和一個用于處理 Hook 的回調,這個回調用于修改原始方法的邏輯。

    接下來 Xposed 取得這個方法的反射表示對象,它是一個 java.lang.reflect.Method 對象,然后和用于處理 Hook 的回調一起傳遞給 Xposed 的 Native 層代碼,Native 層代碼使用 ArtMethod 的一個靜態轉換方法,將 Java 層的反射對象 Method 轉換為一個 ART 中用于表示一個 Java 方法的 ArtMethod 對象,獲取這個表示被 Hook 的 Java 方法的 ArtMethod 對象后,會創建它的副本對象用于備份,備份目的是可以在可是的時候再調用原始方法,然后給這個 ArtMethod 對象重新設置匯編代碼的地址,這個地址指向一段匯編代碼,這個匯編代碼是一段蹦床代碼(Trampoline),會跳入原本用于處理 Java 動態代理的方法的函數,Xposed 對其進行了修改,在其中加入了處理 Hook 的邏輯,也就是轉發被 Hook 方法的參數給處理 Hook 的回調方法,讓 Hook 回調方法處理被 Hook 方法的邏輯,從而完成 Hook。

    至此就完成了 ART 中的 Hook 處理。

    那么調用一個被 Hook 的方法的過程是:

    當一個 Android 應用內代碼調用一個被 Hook 的方法時,ART 將會對方法代碼進行執行,首先這個 Java 方法在 ART 虛擬機中將使用一個 ArtMethod 對象表示,然后進入 ART 的 Java 方法執行函數中,會跳入一段蹦床代碼中進行執行,這段蹦床代碼又會跳入這個 ArtMethod 對象設置的匯編代碼地址處,從而執行到 Xposed 用于處理 Hook 的代碼中,之后完成 Hook 邏輯。

    上面使用書面語言分別概述了基于 Dalvik 和 ART 的方法 Hook 的實現,目的是對整個 Xposed 實現對方法的 Hook 原理進行概括,建立一個初步的印象。真正的細節還是在源代碼中,為了分析最終源代碼,下面進一步對 Xposed 進行分析。

    到這基本就可以大概了解吹逼了,前方高能,建議收藏。

    7、Xposed 工作流程

    為了進一步分析 Xposed 的實現原理,先對 Xposed 的整體工作流程進行了解。

    要使 Xposed 在 Android 設備上工作,首先需要安裝 Xposed 框架。

    首先獲取 XposedInstaller 應用(去官方下載,或者通過 clone XposedInstaller 項目后自行編譯),安裝到已經 root 的設備上,然后打開 XposedInstaller。

    XposedInstaller 主頁會有“INSTALL/UPDATE” 的按鈕,點擊將會出現 Install 和 Install via recovery 兩個選擇,一個是直接進行安裝;另一個是通過 recovery 進行刷入安裝。不管選擇哪個,都會首先從服務器下載相同的 xposed 補丁包。

    XposedInstaller 會根據系統版本和 CPU 支持架構下載對應的系統補丁包。

    在 ARM64 架構 CPU 的 Android 8.1 系統上,補丁包內容如下:

    xposed-v90-sdk27-arm64-beta3.zip+-META-INF│?+-?CERT.RSA│?+-?CERT.SF│?+-?MANIFEST.MF│?????+-?com/google/android│?????????+-?flash-script.sh│?????????+-?update-binary│?????????+-?updater-script│+-?system+-?xposed.prop+-?bin|???+-?app_process32_xposed|???+-?app_process64_xposed|???+-?dex2oat|???+-?dexdiag|???+-?dexlist|???+-?dexoptanalyzer|???+-?oatdump|???+-?patchoat|???+-?profman|+-?framework|???+-?XposedBridge.jar|+-?lib|???+-?libart-compiler.so|???+-?libart-dexlayout.so|???+-?libart.so|???+-?libopenjdkjvm.so|???+-?libsigchain.so|???+-?libxposed_art.so|+-?lib64+-?libart-compiler.so+-?libart-disassembler.so+-?libart.so+-?libopenjdkjvm.so+-?libsigchain.so+-?libxposed_art.so

    壓縮包名為 xposed-v90-sdk27-arm64-beta3.zip,文件名包含系統版本、CPU 架構和 Xposed 版本信息。

    META-INF 目錄存放文件簽名信息,和 Xposed 刷機腳本 flash-script.sh 文件,update-binary 為刷入文件時執行的文件,它的源代碼在 Android 源代碼 bootable/recovery/updater/?目錄中。

    system 目錄為 Xposed 所需的文件,刷入時將會復制到系統 system 目錄下,同名文件將進行覆蓋,其中 xposed.prop 為 Xposed 的屬性文件,里面會存放 Xposed 版本相關信息,如下:

    version=90-beta3 arch=arm64 minsdk=27 maxsdk=27 requires:fbe_aware=1

    bin 目錄存放系統可執行文件;framwrok 目錄存放 Xposed 的 Java 層 Dex 代碼包,用于在 Zygote 進程中進行加載;lib、lib64 是 32 位和 64 位系統庫,包括 ART 虛擬機庫 libart.so 和依賴的庫,還有 Xposed Native 層代碼的實現 libxposed_art.so。

    回到 XposedInstaller 中,如果選擇了 Install,那么首先將壓縮包中的 system 目錄下的的可執行文件以及依賴庫、配置文件等復制入系統 system 中覆蓋相應系統文件,然后請求重啟 Android 系統,重啟后開機過程中,系統將會執行 app_process 可執行文件,從而啟動 Xposed 修改過的 zygote 進程,其中會把 XposedBridge.jar 代碼包加載起來,加載后其中的 Java 代碼會加載已經安裝的 Xposed 模塊,當手機中的應用進程啟動后,Xposed 模塊代碼將會被包含在應用進程中,開始工作;

    如果是 Install via recovery,將創建文件?/cache/recovery/command 并寫入指定刷機包路徑的刷機命令,然后重啟手機進入 recovery 模式,recovery 模式會自動執行 command 文件中的命令將 Xposed 文件刷入,然后正常重啟至系統,啟動過程和上面一致。

    了解了 Xposed 的整體工作流程,下面開始著手進行源碼分析。

    8、Xposed 項目結構

    首先了解 Xposed 開源項目的結構,Xposed 包含如下幾個開源項目:

    Xposed

    https://github.com/rovo89/Xposed

    Xposed Native 層代碼的實現,主要修改了系統 app_process 的實現(即 zygote 服務進程的實現),為將 Hook 代碼注入每個應用進程提供了入口。

    XposedBridge

    https://github.com/rovo89/XposedBridge

    Xposed Java 層的代碼,它將單獨作為一個 jar 包的形式通過 zygote 的分裂(fork)注入到每一個應用進程中,內部會 Xposed 模塊,并為 Xposed 模塊中的 Hook 操作提供 API 支持。

    XposedInstaller

    https://github.com/rovo89/XposedInstaller

    統一管理 Xposed 框架的 Android 應用,也是一個 Xposed 框架安裝器,用于安裝更新 Xposed 框架核心以及作為統一管理 Xposed 模塊安裝的模塊管理器。

    android_art

    https://github.com/rovo89/android_art

    Xposed 修改后的 Android ART 虛擬機的實現,將編譯出 libart.so 和其依賴庫,替換系統的 ART 虛擬機實現。包含方法 Hook 的核心實現。

    這個倉庫最新分支是基于 Android Nougat MR2 源碼修改的 ART 代碼,目前 Xposed 最新版本支持到了 Android 8.1 系統,說明作者沒有開源出最新代碼,不過都是基于 ART 實現的 Hook,核心 Hook 實現是一致的,不影響分析。

    XposedTools

    https://github.com/rovo89/XposedTools

    用于編譯 Xposed 框架的腳本工具。

    目前只分析 Xposed 的實現,不需要對 Xposed 進行定制,所以先不關注 XposedTools 這個項目。

    9、Xposed 源碼分析

    可以對上面的項目進行 clone,然后用 Android Studio 和 VS Code 打開源代碼,方便閱讀。下面進入源碼中分析具體實現。

    Xposed 安裝下載

    首先從 Xposed 的安裝開始分析,這部分代碼的實現在 XposedInstaller 中。

    在一臺 Root 過的設備上安裝 XposedInstaller 后打開,點擊主頁的“INSTALL/UPDATE”,會彈出一個對話框,選擇“Install”或“Install via recovery”安裝 Xposed 框架,此時會首先進行框架核心文件的下載,進入 StatusInstallerFragment#download 方法中:

    //?StatusInstallerFragment.javaprivate?void?download(Context?context,?String?title,?FrameworkZips.Type?type,?final?RunnableWithParam<File>?callback)?{OnlineFrameworkZip?zip?=?FrameworkZips.getOnline(title,?type);new?DownloadsUtil.Builder(context).setTitle(zip.title)//?設置下載?url.setUrl(zip.url).setDestinationFromUrl(DownloadsUtil.DOWNLOAD_FRAMEWORK).setCallback(new?DownloadFinishedCallback()?{@Overridepublic?void?onDownloadFinished(Context?context,?DownloadInfo?info)?{//?下載完成,觸發回調LOCAL_ZIP_LOADER.triggerReload(true);callback.run(new?File(info.localFilename));}}).setMimeType(DownloadsUtil.MIME_TYPES.ZIP).setDialog(true).download(); }

    其中 zip.url 為 Xposed 框架壓縮包的下載地址,我們重點關注安裝,所以這里簡要描述 zip 對象,zip 是 OnlineFrameworkZip 類的對象,表示一個 Xposed 框架包,它包含 title、type 和 url 三個成員,type 有兩種,Installer 和 Uninstaller,即安裝包和卸載包,都是包含刷機腳本的 Xposed 補丁包(就是上面工作流程中的壓縮包),title 有三種,Xposed 測試版、Xposed 正式版、和 Uninstaller,用于界面顯示。

    上面的 zip.url 在 Android 8.1 的 Pixel 手機上運行出來是 http://dl-xda.xposed.info/framework/sdk27/arm64/xposed-v90-sdk27-arm64-beta3.zip,這個 url 是根據設備支持的 CPU 架構、系統版本和 Xposed 當前最新版本組合出來的,組合規則由一個 framework.json 提供,它的本地路徑是?/data/data/de.robv.android.xposed.installer/cache/framework.json,是從 http://dl-xda.xposed.info/framework.json 解析后得到的,內容如下:

    {"zips":?[{"title":?"Version?90-beta$(version)","url":?"http://dl-xda.xposed.info/framework/sdk$(sdk)/$(arch)/xposed-v90-sdk$(sdk)-$(arch)-beta$(version).zip","versions":?[{?"version":?"3",?"current":?true?},{?"version":?"2"?},{?"version":?"1"?}],"archs":?["arm",?"arm64",?"x86"],"sdks"?:?[26,?27]},{"title":?"Version?$(version)","url":?"http://dl-xda.xposed.info/framework/sdk$(sdk)/$(arch)/xposed-v$(version)-sdk$(sdk)-$(arch).zip","versions":?[{?"version":?"89",?"current":?true?},{?"version":?"88.2"?},{?"version":?"88.1"?},{?"version":?"88"?},...],"archs":?["arm",?"arm64",?"x86"],"sdks"?:?[21,?22,?23,?24,?25],"exclude":?[{"versions":?["88.1"],"sdks":?[21,?22,?23]},{"versions":?["78",?"79",?"80",?"81",?"82",?"83",?"84",?"85",?"86",?"87"],"sdks":?[24,?25]},...]},{"title":?"Uninstaller?($(version))","url":?"http://dl-xda.xposed.info/framework/uninstaller/xposed-uninstaller-$(version)-$(arch).zip","type":?"uninstaller","versions":?[{?"version":?"20180117",?"current":?true?},{?"version":?"20180108"?},...],"archs":?["arm",?"arm64",?"x86"],"sdks"?:?[21,?22,?23,?24,?25,?26,?27]}] }

    能看到,其中包含了 Xposed 測試版、Xposed 正式版、和 Xposed 的 Uninstaller 三種 title 的下載信息,每個下載信息中的 url 為下載地址的模板,versions 為可用的版本,根據系統信息和 Xposed 版本對 url 模板進行填充后組成下載地址。

    回到上面的下載,下載成功后,將進入回調根據用戶選擇的安裝類型進行安裝,看一下回調的實現:

    //?StatusInstallerFragment.javaif?(action?==?ACTION_FLASH)?{runAfterDownload?=?new?RunnableWithParam<File>()?{@Overridepublic?void?run(File?file)?{//?直接刷入flash(context,?new?FlashDirectly(file,?type,?title,?false));}}; }?else?if?(action?==?ACTION_FLASH_RECOVERY)?{runAfterDownload?=?new?RunnableWithParam<File>()?{@Overridepublic?void?run(File?file)?{//?依賴?recovery?模式進行刷入flash(context,?new?FlashRecoveryAuto(file,?type,?title));}}; }?else?if?(action?==?ACTION_SAVE)?{runAfterDownload?=?new?RunnableWithParam<File>()?{@Overridepublic?void?run(File?file)?{//?僅保存saveTo(context,?file);}}; }

    上面兩個分支分別對應 Install 和 Install via recovery 兩種安裝方式的實現,flash 方法將會啟動一個新的負責展示安裝執行的界面,然后執行傳入的 Flashable 對象的 flash 方法,執行成功后展示一個對話框,詢問用戶是否重啟,重啟后將激活 Xposed。

    分別看一下兩種 Flashable 的實現。

    直接刷入

    //?FlashDirectly.javapublic?void?flash(Context?context,?FlashCallback?callback)?{ZipCheckResult?zipCheck?=?openAndCheckZip(callback);if?(zipCheck?==?null)?{return;}//?獲取壓縮包文件ZipFile?zip?=?zipCheck.getZip();if?(!zipCheck.isFlashableInApp())?{triggerError(callback,?FlashCallback.ERROR_NOT_FLASHABLE_IN_APP);closeSilently(zip);return;}//?釋放?update-binary?文件至?cache?目錄中ZipEntry?entry?=?zip.getEntry("META-INF/com/google/android/update-binary");File?updateBinaryFile?=?new?File(XposedApp.getInstance().getCacheDir(),?"update-binary");try?{AssetUtil.writeStreamToFile(zip.getInputStream(entry),?updateBinaryFile,?0700);}?catch?(IOException?e)?{Log.e(XposedApp.TAG,?"Could?not?extract?update-binary",?e);triggerError(callback,?FlashCallback.ERROR_INVALID_ZIP);return;}?finally?{closeSilently(zip);}//?使用?Root?身份執行刷入命令RootUtil?rootUtil?=?new?RootUtil();if?(!rootUtil.startShell(callback))?{return;}callback.onStarted();rootUtil.execute("export?NO_UIPRINT=1",?callback);if?(mSystemless)?{rootUtil.execute("export?SYSTEMLESS=1",?callback);}//?執行?update-binary?文件int?result?=?rootUtil.execute(getShellPath(updateBinaryFile)?+?"?2?1?"?+?getShellPath(mZipPath),?callback);if?(result?!=?FlashCallback.OK)?{triggerError(callback,?result);return;}callback.onDone(); }

    直接刷入會直接使用 Root 身份執行 update-binary 可執行文件,其中會調用 flash-script.sh 文件,它將壓縮包中的目錄復制到對應的系統目錄中,同名文件進行覆蓋,在覆蓋前會對原始系統文件進行備份,例如 libart.so.orig.gz,為了在卸載時恢復。

    刷入后正常重啟系統,系統在啟動時將會加載自定義的 app_process 可執行文件,啟動了帶有 Xposed 框架代碼的定制版 zygote 服務進程,為 Xposed 提供支持。

    使用 recovery 刷入

    //?FlashRecoveryAuto.java@Override public?void?flash(Context?context,?FlashCallback?callback)?{ZipCheckResult?zipCheck?=?openAndCheckZip(callback);if?(zipCheck?==?null)?{return;}?else?{closeSilently(zipCheck.getZip());}final?String?zipName?=?mZipPath.getName();String?cmd;//?執行刷入命令RootUtil?rootUtil?=?new?RootUtil();if?(!rootUtil.startShell(callback))?{return;}callback.onStarted();//?確認?/cache/recovery/?目錄存在if?(rootUtil.execute("ls?/cache/recovery",?null)?!=?0)?{callback.onLine(context.getString(R.string.file_creating_directory,?"/cache/recovery"));if?(rootUtil.executeWithBusybox("mkdir?/cache/recovery",?callback)?!=?0)?{callback.onError(FlashCallback.ERROR_GENERIC,context.getString(R.string.file_create_directory_failed,?"/cache/recovery"));return;}}//?復制?zip?到?/cache/recovery/?目錄callback.onLine(context.getString(R.string.file_copying,?zipName));cmd?=?"cp?-a?"?+?RootUtil.getShellPath(mZipPath)?+?"?/cache/recovery/"?+?zipName;if?(rootUtil.executeWithBusybox(cmd,?callback)?!=?0)?{callback.onError(FlashCallback.ERROR_GENERIC,context.getString(R.string.file_copy_failed,?zipName,?"/cache/recovery"));return;}//?將刷機命令寫入?/cache/recovery/command?文件中callback.onLine(context.getString(R.string.file_writing_recovery_command));cmd?=?"echo?--update_package=/cache/recovery/"?+?zipName?+?"?>?/cache/recovery/command";if?(rootUtil.execute(cmd,?callback)?!=?0)?{callback.onError(FlashCallback.ERROR_GENERIC,context.getString(R.string.file_writing_recovery_command_failed));return;}callback.onLine(context.getString(R.string.auto_flash_note,?zipName));callback.onDone(); }

    通過 recovery 模式進行刷入就是首先復制壓縮包到?/cache/recovery/?中,然后向?/cache/recovery/command 文件中寫入一條刷入壓縮包的命令,然后詢問用戶是否重啟至 recovery 模式,當系統處于 recovery 模式后將會自動檢測 command 文件是否存在,如果存在將執行其中的指令,然后執行刷機包提供的腳本,過程和上面直接刷入一致,首先執行 update-binary 可執行文件,然后其中會調用 flash-script.sh 文件,將刷機包中的文件進行復制。此時,系統退出 reocvery 正常重啟后將會加載成功 Xposed。

    這里就分析完了安裝,主要是通過刷入文件將系統關鍵組件替換為 Xposed 修改過的實現。

    下面開始分析 Xposed 的啟動,當系統啟動后,init 進程將會通過解析 init.rc 文件后執行 app_process 創建 zygote 進程,此時就進入了 Xposed 重新編譯修改過的 app_process 文件中。

    Xposed 啟動

    這部分的實現代碼在項目 Xposed 中,是使用 C++ 代碼編寫的,如果這些代碼出現崩潰,則會卡在開機界面,即 boot loop 情況。

    Xposed 的 app_process 分為 Dalvik 和 ART 兩種實現,這里只關注 ART 的實現,在 app_main2.cpp 中。

    Native 層

    入口為 main 函數:

    //?app_main2.cppint?main(int?argc,?char*?const?argv[]) {if?(prctl(PR_SET_NO_NEW_PRIVS,?1,?0,?0,?0)?<?0)?{if?(errno?!=?EINVAL)?{LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS?failed:?%s",?strerror(errno));return?12;}}//?1.?處理?xposed?測試選項if?(xposed::handleOptions(argc,?argv))?{return?0;}AppRuntime?runtime(argv[0],?computeArgBlockSize(argc,?argv));argc--;argv++;int?i;for?(i?=?0;?i?<?argc;?i++)?{if?(argv[i][0]?!=?'-')?{break;}if?(argv[i][1]?==?'-'?&&?argv[i][2]?==?0)?{++i;?//?Skip?--.break;}runtime.addOption(strdup(argv[i]));}bool?zygote?=?false;bool?startSystemServer?=?false;bool?application?=?false;String8?niceName;String8?className;++i;while?(i?<?argc)?{const?char*?arg?=?argv[i++];if?(strcmp(arg,?"--zygote")?==?0)?{zygote?=?true;niceName?=?ZYGOTE_NICE_NAME;}?else?if?(strcmp(arg,?"--start-system-server")?==?0)?{startSystemServer?=?true;}?else?if?(strcmp(arg,?"--application")?==?0)?{application?=?true;}?else?if?(strncmp(arg,?"--nice-name=",?12)?==?0)?{niceName.setTo(arg?+?12);}?else?if?(strncmp(arg,?"--",?2)?!=?0)?{className.setTo(arg);break;}?else?{--i;break;}}Vector<String8>?args;if?(!className.isEmpty())?{args.add(application???String8("application")?:?String8("tool"));runtime.setClassNameAndArgs(className,?argc?-?i,?argv?+?i);}?else?{maybeCreateDalvikCache();if?(startSystemServer)?{args.add(String8("start-system-server"));}char?prop[PROP_VALUE_MAX];if?(property_get(ABI_LIST_PROPERTY,?prop,?NULL)?==?0)?{LOG_ALWAYS_FATAL("app_process:?Unable?to?determine?ABI?list?from?property?%s.",ABI_LIST_PROPERTY);return?11;}String8?abiFlag("--abi-list=");abiFlag.append(prop);args.add(abiFlag);for?(;?i?<?argc;?++i)?{args.add(String8(argv[i]));}}if?(!niceName.isEmpty())?{runtime.setArgv0(niceName.string());set_process_name(niceName.string());}//?2.if?(zygote)?{//?初始化?xposedisXposedLoaded?=?xposed::initialize(true,?startSystemServer,?NULL,?argc,?argv);//?#define?XPOSED_CLASS_DOTS_ZYGOTE?"de.robv.android.xposed.XposedBridge"//?初始化成功則會?XposedBridge?流程,否則進入系統的?ZygoteInit?中runtimeStart(runtime,?isXposedLoaded???XPOSED_CLASS_DOTS_ZYGOTE?:?"com.android.internal.os.ZygoteInit",?args,?zygote);}?else?if?(className)?{//?非?zygote?進程流程,用于支持使用命令行啟動自定義的類,這里先不關心這個流程isXposedLoaded?=?xposed::initialize(false,?false,?className,?argc,?argv);runtimeStart(runtime,?isXposedLoaded???XPOSED_CLASS_DOTS_TOOLS?:?"com.android.internal.os.RuntimeInit",?args,?zygote);}?else?{fprintf(stderr,?"Error:?no?class?name?or?--zygote?supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process:?no?class?name?or?--zygote?supplied.");return?10;} }

    通過對比原版的 ART 代碼,發現 main 函數中只有 1 和 2 兩處代碼進行了修改,當 init 進程解析 init.rc 文件時,會啟動 zygote 進程,此時就進入了 app_process 的 main 函數中,并將 init.rc 中附帶的選項使用 argv 參數傳遞進來。

    先看第一處 handleOptions:

    //?xposed.cppbool?handleOptions(int?argc,?char*?const?argv[])?{parseXposedProp();if?(argc?==?2?&&?strcmp(argv[1],?"--xposedversion")?==?0)?{printf("Xposed?version:?%s\n",?xposedVersion);return?true;}if?(argc?==?2?&&?strcmp(argv[1],?"--xposedtestsafemode")?==?0)?{printf("Testing?Xposed?safemode?trigger\n");if?(detectSafemodeTrigger(shouldSkipSafemodeDelay()))?{printf("Safemode?triggered\n");}?else?{printf("Safemode?not?triggered\n");}return?true;}argBlockStart?=?argv[0];uintptr_t?start?=?reinterpret_cast<uintptr_t>(argv[0]);uintptr_t?end?=?reinterpret_cast<uintptr_t>(argv[argc?-?1]);end?+=?strlen(argv[argc?-?1])?+?1;argBlockLength?=?end?-?start;return?false; }

    處理了?--xposedversion 和?--xposedtestsafemode 兩個參數,不過查看 init.rc 文件中啟動 zygote 的選項中并沒有這兩項:

    service?zygote?/system/bin/app_process?-Xzygote?/system/bin?--zygote?--start-system-server ...

    所以應該是用于 Xposed 的測試代碼,那么這里就不再關心。

    繼續下面第 2 部分,在 zygote 流程中,首先會初始化 Xposed,如果初始化成功就會傳入 XposedBridge 的完整類名,用于進入 Java 層的 XposedBridge 入口。

    首先看 xposed::initialize 函數:

    bool?initialize(bool?zygote,?bool?startSystemServer,?const?char*?className,?int?argc,?char*?const?argv[])?{ #if?!defined(XPOSED_ENABLE_FOR_TOOLS)if?(!zygote)return?false; #endif//?判斷系統是否處于?minmal?framework?模式,此時?/data?是?tmpfs?類型,無法加載?Xposedif?(isMinimalFramework())?{ALOGI("Not?loading?Xposed?for?minimal?framework?(encrypted?device)");return?false;}//?保存相關參數//?xposed?是一個用于共享信息的對象,XposedShared*?xposed?=?new?XposedShared;xposed->zygote?=?zygote;xposed->startSystemServer?=?startSystemServer;xposed->startClassName?=?className;xposed->xposedVersionInt?=?xposedVersionInt;#if?XPOSED_WITH_SELINUXxposed->isSELinuxEnabled???=?is_selinux_enabled()?==?1;xposed->isSELinuxEnforcing?=?xposed->isSELinuxEnabled?&&?security_getenforce()?==?1; #elsexposed->isSELinuxEnabled???=?false;xposed->isSELinuxEnforcing?=?false; #endif??//?XPOSED_WITH_SELINUXif?(startSystemServer)?{xposed::logcat::printStartupMarker();}?else?if?(zygote)?{//?給另一個架構的優先執行的?zygote?進程一些時間啟動,從而避免同時打印日志,造成難以閱讀sleep(10);}//?打印?Xposed?版本和?Device、ROM?等信息,開機時可以在?logcat?中看到printRomInfo();if?(startSystemServer)?{//?確保?XposedInstaller?uid?和?gid?存在,即表示安裝了?XposedInstaller//?啟動?XposedService?服務if?(!determineXposedInstallerUidGid()?||?!xposed::service::startAll())?{return?false;}xposed::logcat::start(); #if?XPOSED_WITH_SELINUX}?else?if?(xposed->isSELinuxEnabled)?{if?(!xposed::service::startMembased())?{return?false;} #endif??//?XPOSED_WITH_SELINUX}#if?XPOSED_WITH_SELINUXif?(xposed->isSELinuxEnabled)?{xposed::service::membased::restrictMemoryInheritance();} #endif??//?XPOSED_WITH_SELINUXif?(zygote?&&?!isSafemodeDisabled()?&&?detectSafemodeTrigger(shouldSkipSafemodeDelay()))disableXposed();if?(isDisabled()?||?(!zygote?&&?shouldIgnoreCommand(argc,?argv)))return?false;//?將?XposedBridge.jar?加入系統?CLASSPATH?變量,使其代碼中的類可被加載return?addJarToClasspath(); }

    以上代碼主要是保存了 app_process 的啟動選項,設置一些 xposed 支持,最后使用?addJarToClasspath?將 XposedBridge.jar 加入系統路徑。

    //?xposed.cppbool?addJarToClasspath()?{ALOGI("-----------------");//?#define?XPOSED_JAR?"/system/framework/XposedBridge.jar"if?(access(XPOSED_JAR,?R_OK)?==?0)?{if?(!addPathToEnv("CLASSPATH",?XPOSED_JAR))return?false;ALOGI("Added?Xposed?(%s)?to?CLASSPATH",?XPOSED_JAR);return?true;}?else?{ALOGE("ERROR:?Could?not?access?Xposed?jar?'%s'",?XPOSED_JAR);return?false;} }

    這里就初始化完成了,如果中間有一步執行失敗,返回 false,那么 Xposed 就不能正常工作了,會通過傳遞 ZygoteInit 完整類名,進入系統正常的 zygote 流程。

    現在回到 main 函數中,下面進入 runtimeStart:

    //?app_main2.cppstatic?void?runtimeStart(AppRuntime&?runtime,?const?char?*classname,?const?Vector<String8>&?options,?bool?zygote) { #if?PLATFORM_SDK_VERSION?>=?23runtime.start(classname,?options,?zygote); #else//?try?newer?variant?(5.1.1_r19?and?later)?firstvoid?(*ptr1)(AppRuntime&,?const?char*,?const?Vector<String8>&,?bool);*(void?**)?(&ptr1)?=?dlsym(RTLD_DEFAULT,?"_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEEb");if?(ptr1?!=?NULL)?{ptr1(runtime,?classname,?options,?zygote);return;}//?fall?back?to?older?variantvoid?(*ptr2)(AppRuntime&,?const?char*,?const?Vector<String8>&);*(void?**)?(&ptr2)?=?dlsym(RTLD_DEFAULT,?"_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEE");if?(ptr2?!=?NULL)?{ptr2(runtime,?classname,?options);return;}//?should?not?happenLOG_ALWAYS_FATAL("app_process:?could?not?locate?AndroidRuntime::start()?method."); #endif }

    其實就是直接調用系統 AppRuntime 的 start 函數,如果是 Android 5.1.1 之前需要通過通過獲取 AppRuntime::start 函數符號句柄的方式調用,后面一長串字符串是函數被編譯后的簽名字符串。

    調用 AppRuntime::start 后,內部會創建 Java 虛擬機,然后執行傳入類的 main 函數:

    //?AndroidRuntime.cppvoid?AndroidRuntime::start(const?char*?className,?const?Vector<String8>&?options,?bool?zygote) {ALOGD(">>>>>>?START?%s?uid?%d?<<<<<<\n",className?!=?NULL???className?:?"(unknown)",?getuid());static?const?String8?startSystemServer("start-system-server");//?...JniInvocation?jni_invocation;jni_invocation.Init(NULL);JNIEnv*?env;//?創建虛擬機if?(startVm(&mJavaVM,?&env,?zygote)?!=?0)?{return;}onVmCreated(env);//?注冊系統類?JNI?方法if?(startReg(env)?<?0)?{ALOGE("Unable?to?register?all?android?natives\n");return;}//?...//?轉換為?JNI?格式類名:com/android/internal/os/XposedBridgechar*?slashClassName?=?toSlashClassName(className);jclass?startClass?=?env->FindClass(slashClassName);if?(startClass?==?NULL)?{ALOGE("JavaVM?unable?to?locate?class?'%s'\n",?slashClassName);}?else?{jmethodID?startMeth?=?env->GetStaticMethodID(startClass,?"main","([Ljava/lang/String;)V");if?(startMeth?==?NULL)?{ALOGE("JavaVM?unable?to?find?main()?in?'%s'\n",?className);}?else?{//?調用?XposedBridge.main();env->CallStaticVoidMethod(startClass,?startMeth,?strArray);}}//?... }

    Java 層

    下面就進入到了 Java 層的 XposedBridge#main 方法中:

    //?XposedBridge.javaprotected?static?void?main(String[]?args)?{//?Initialize?the?Xposed?framework?and?modulestry?{//?判斷?native?加載成功if?(!hadInitErrors())?{//?Xposed?相關初始化initXResources();//?SELinux?相關支持SELinuxHelper.initOnce();SELinuxHelper.initForProcess(null);//?runtime?表示?ART?還是?Dalivkruntime?=?getRuntime();XPOSED_BRIDGE_VERSION?=?getXposedVersion();if?(isZygote)?{//?為資源的?Hook?注冊回調XposedInit.hookResources();//?為代碼?Hook?注冊回調,將會調用每個?Xposed?模塊的入口XposedInit.initForZygote();}//?加載設備上的?Xposed?模塊XposedInit.loadModules();}?else?{Log.e(TAG,?"Not?initializing?Xposed?because?of?previous?errors");}}?catch?(Throwable?t)?{Log.e(TAG,?"Errors?during?Xposed?initialization",?t);disableHooks?=?true;}//?調用系統正常流程?Java?層if?(isZygote)?{ZygoteInit.main(args);}?else?{RuntimeInit.main(args);} }

    重點關注 XposedInit.initForZygote();?和 XposedInit.loadModules();:

    /*package*/?static?void?initForZygote()?throws?Throwable?{//?...//?system_server?初始化if?(Build.VERSION.SDK_INT?<?21)?{findAndHookMethod("com.android.server.ServerThread",?null,Build.VERSION.SDK_INT?<?19???"run"?:?"initAndLoop",?new?XC_MethodHook()?{@Overrideprotected?void?beforeHookedMethod(MethodHookParam?param)?throws?Throwable?{SELinuxHelper.initForProcess("android");loadedPackagesInProcess.add("android");XC_LoadPackage.LoadPackageParam?lpparam?=?new?XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);lpparam.packageName?=?"android";lpparam.processName?=?"android";?//?it's?actually?system_server,?but?other?functions?return?this?as?welllpparam.classLoader?=?XposedBridge.BOOTCLASSLOADER;lpparam.appInfo?=?null;lpparam.isFirstApplication?=?true;XC_LoadPackage.callAll(lpparam);}});}?//?...hookAllConstructors(LoadedApk.class,?new?XC_MethodHook()?{@Overrideprotected?void?afterHookedMethod(MethodHookParam?param)?throws?Throwable?{LoadedApk?loadedApk?=?(LoadedApk)?param.thisObject;String?packageName?=?loadedApk.getPackageName();XResources.setPackageNameForResDir(packageName,?loadedApk.getResDir());if?(packageName.equals("android")?||?!loadedPackagesInProcess.add(packageName))return;if?(!getBooleanField(loadedApk,?"mIncludeCode"))return;XC_LoadPackage.LoadPackageParam?lpparam?=?new?XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);lpparam.packageName?=?packageName;lpparam.processName?=?AndroidAppHelper.currentProcessName();lpparam.classLoader?=?loadedApk.getClassLoader();lpparam.appInfo?=?loadedApk.getApplicationInfo();lpparam.isFirstApplication?=?false;XC_LoadPackage.callAll(lpparam);}});//?... }

    上面省略了一部分代碼,上面的代碼主要是通過 Hook 系統關鍵類的流程,為 Xposed 模塊注冊加載代碼包的回調,當這些系統流程執行時,會通過 XC_LoadPackage.callAll(lpparm)?通知所有的 Xposed 模塊。

    上面創建了 XC_LoadPackage.LoadPackageParam 的對象,就是為了給 Xposed 模塊的入口進行傳遞。

    XposedBridge.sLoadedPackageCallbacks 是 Xposed 模塊回調的集合,是一個 CopyOnWriteSortedSet<XC_LoadPackage>?類型。

    XC_LoadPackage 有一個 call 方法,用于回調自己的 handleLoadPackage 方法。

    //?XC_LoadPackage.java@Override protected?void?call(Param?param)?throws?Throwable?{if?(param?instanceof?LoadPackageParam)handleLoadPackage((LoadPackageParam)?param); }

    XC_LoadPackage.callAll 將會調用每一個 XC_LoadPackage 的 call 方法,從而向 Xposed 模塊傳遞 lpparm 參數。

    //?XC_LoadPackage.javapublic?static?void?callAll(Param?param)?{if?(param.callbacks?==?null)throw?new?IllegalStateException("This?object?was?not?created?for?use?with?callAll");for?(int?i?=?0;?i?<?param.callbacks.length;?i++)?{try?{((XCallback)?param.callbacks[i]).call(param);}?catch?(Throwable?t)?{?XposedBridge.log(t);?}} }

    Xposed 模塊加載

    再來看 XposedInit.loadModules();:

    //?XposedInit.java/*package*/?static?void?loadModules()?throws?IOException?{//?從?modules.list?文件讀取?Xposde?模塊列表final?String?filename?=?BASE_DIR?+?"conf/modules.list";BaseService?service?=?SELinuxHelper.getAppDataFileService();if?(!service.checkFileExists(filename))?{Log.e(TAG,?"Cannot?load?any?modules?because?"?+?filename?+?"?was?not?found");return;}ClassLoader?topClassLoader?=?XposedBridge.BOOTCLASSLOADER;ClassLoader?parent;while?((parent?=?topClassLoader.getParent())?!=?null)?{topClassLoader?=?parent;}InputStream?stream?=?service.getFileInputStream(filename);BufferedReader?apks?=?new?BufferedReader(new?InputStreamReader(stream));String?apk;while?((apk?=?apks.readLine())?!=?null)?{//?加載每個?Xposed?模塊loadModule(apk,?topClassLoader);}apks.close(); } //?XposedInit.javaprivate?static?void?loadModule(String?apk,?ClassLoader?topClassLoader)?{Log.i(TAG,?"Loading?modules?from?"?+?apk);//?...DexFile?dexFile;try?{dexFile?=?new?DexFile(apk);}?catch?(IOException?e)?{Log.e(TAG,?"??Cannot?load?module",?e);return;}//?...ZipFile?zipFile?=?null;InputStream?is;try?{zipFile?=?new?ZipFile(apk);//?打開?Xposed?模塊?apk?文件中的?xposed_init?文件,//?它的內容是?Xposed?模塊入口類的全類名ZipEntry?zipEntry?=?zipFile.getEntry("assets/xposed_init");if?(zipEntry?==?null)?{Log.e(TAG,?"??assets/xposed_init?not?found?in?the?APK");closeSilently(zipFile);return;}is?=?zipFile.getInputStream(zipEntry);}?catch?(IOException?e)?{Log.e(TAG,?"??Cannot?read?assets/xposed_init?in?the?APK",?e);closeSilently(zipFile);return;}ClassLoader?mcl?=?new?PathClassLoader(apk,?XposedBridge.BOOTCLASSLOADER);BufferedReader?moduleClassesReader?=?new?BufferedReader(new?InputStreamReader(is));try?{String?moduleClassName;//?獲取?Xposed?模塊入口類名while?((moduleClassName?=?moduleClassesReader.readLine())?!=?null)?{moduleClassName?=?moduleClassName.trim();if?(moduleClassName.isEmpty()?||?moduleClassName.startsWith("#"))continue;try?{Log.i(TAG,?"??Loading?class?"?+?moduleClassName);//?加載入口類Class<?>?moduleClass?=?mcl.loadClass(moduleClassName);//?...final?Object?moduleInstance?=?moduleClass.newInstance();if?(XposedBridge.isZygote)?{if?(moduleInstance?instanceof?IXposedHookZygoteInit)?{IXposedHookZygoteInit.StartupParam?param?=?new?IXposedHookZygoteInit.StartupParam();param.modulePath?=?apk;param.startsSystemServer?=?startsSystemServer;((IXposedHookZygoteInit)?moduleInstance).initZygote(param);}//?根據模塊關心代碼?Hook?還是資源?Hook?分別處理if?(moduleInstance?instanceof?IXposedHookLoadPackage)//?注冊到?sLoadedPackageCallbacks?中XposedBridge.hookLoadPackage(new?IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage)?moduleInstance));if?(moduleInstance?instanceof?IXposedHookInitPackageResources)//?注冊資源的?Xposed?模塊回調XposedBridge.hookInitPackageResources(new?IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources)?moduleInstance));}//?...}?catch?(Throwable?t)?{Log.e(TAG,?"????Failed?to?load?class?"?+?moduleClassName,?t);}}}?catch?(IOException?e)?{Log.e(TAG,?"??Failed?to?load?module?from?"?+?apk,?e);}?finally?{closeSilently(is);closeSilently(zipFile);} }

    上面代碼首先從 conf/modules.list 文件加載所有 Xposed 模塊的 APK 路徑列表,然后通過讀取每一個 Xposed 模塊 APK 包中的 assets/xposed_init 文件獲得 Xposed 模塊的入口類名,最后將這個類通過 XposedBridge.hookLoadPackage 注冊到前面的 XposedBridge.sLoadedPackageCallbacks 中。

    //?XposedBridge.javapublic?static?void?hookLoadPackage(XC_LoadPackage?callback)?{synchronized?(sLoadedPackageCallbacks)?{sLoadedPackageCallbacks.add(callback);} }

    那么當前面 Hook 的系統關鍵類流程被觸發后,將會通過 sLoadedPackageCallbacks 回調每個 Xposed 模塊的入口。

    到這里 Xposed 模塊的啟動的核心邏輯就分析完了,主要是通過 Xposed 定制版的 zygote 加載 XposedBridge.jar,然后調用 XposedBridge#main 方法加載所有的 Xposed 模塊,當一個進程通過 zygote 進程 clone 出來時,就會攜帶 XposedBridge.jar 的代碼,同時在進程啟動時回調所有的 Xposed 模塊的入口,XposedBridge.jar 中還包含 Hook API,那么 Xposed 模塊就可以通過這些 API 對應用程序進行 Hook 操作了。

    接下來就是 Xposed 的方法 Hook 的實現代碼分析了。

    Xposed 方法 Hook

    從 XposedHelpers.findAndHookMethod 方法開始,看 Xposed 是如何進行 Hook 的。

    //?XposedHelpers.javapublic?static?XC_MethodHook.Unhook?findAndHookMethod(Class<?>?clazz,?String?methodName,?Object...?parameterTypesAndCallback)?{if?(parameterTypesAndCallback.length?==?0?||?!(parameterTypesAndCallback[parameterTypesAndCallback.length-1]?instanceof?XC_MethodHook))throw?new?IllegalArgumentException("no?callback?defined");XC_MethodHook?callback?=?(XC_MethodHook)?parameterTypesAndCallback[parameterTypesAndCallback.length-1];//?獲取方法的反射表示對象Method?m?=?findMethodExact(clazz,?methodName,?getParameterClasses(clazz.getClassLoader(),?parameterTypesAndCallback));//?下一步return?XposedBridge.hookMethod(m,?callback); } ?

    首先使用 findMethodExact 獲取一個 Java 方法的反射表示對象 m:

    //?XposedHelpers.javapublic?static?Method?findMethodExact(Class<?>?clazz,?String?methodName,?Class<?>...?parameterTypes)?{String?fullMethodName?=?clazz.getName()?+?'#'?+?methodName?+?getParametersString(parameterTypes)?+?"#exact";if?(methodCache.containsKey(fullMethodName))?{//?首先從緩存中取Method?method?=?methodCache.get(fullMethodName);if?(method?==?null)throw?new?NoSuchMethodError(fullMethodName);return?method;}try?{//?通過反射?API?取得?Method?對象Method?method?=?clazz.getDeclaredMethod(methodName,?parameterTypes);method.setAccessible(true);methodCache.put(fullMethodName,?method);return?method;}?catch?(NoSuchMethodException?e)?{methodCache.put(fullMethodName,?null);throw?new?NoSuchMethodError(fullMethodName);} }

    這里也很簡單,使用了緩存保存方法的反射對象,然后繼續下一步,進入 XposedBridge#hookMethod 方法。

    //?XposedBridge.javapublic?static?XC_MethodHook.Unhook?hookMethod(Member?hookMethod,?XC_MethodHook?callback)?{//?只允許?Method?和?Constructor?類型,Constructor?類型為了支持?findAndHookConstructorif?(!(hookMethod?instanceof?Method)?&&?!(hookMethod?instanceof?Constructor<?>))?{throw?new?IllegalArgumentException("Only?methods?and?constructors?can?be?hooked:?"?+?hookMethod.toString());}?else?if?(hookMethod.getDeclaringClass().isInterface())?{throw?new?IllegalArgumentException("Cannot?hook?interfaces:?"?+?hookMethod.toString());}?else?if?(Modifier.isAbstract(hookMethod.getModifiers()))?{throw?new?IllegalArgumentException("Cannot?hook?abstract?methods:?"?+?hookMethod.toString());}boolean?newMethod?=?false;CopyOnWriteSortedSet<XC_MethodHook>?callbacks;synchronized?(sHookedMethodCallbacks)?{callbacks?=?sHookedMethodCallbacks.get(hookMethod);if?(callbacks?==?null)?{//?創建?method?與?hook?回調列表關聯的映射表callbacks?=?new?CopyOnWriteSortedSet<>();sHookedMethodCallbacks.put(hookMethod,?callbacks);newMethod?=?true;}}//?添加?hook?回調到和這個?method?關聯的?hook?回調列表callbacks.add(callback);if?(dnewMethod)?{Clss<?>?declaringClass?=?hookMethod.getDeclaringClass();int?slot;Class<?>[]?parameterTypes;Class<?>?returnType;if?(runtime?==?RUNTIME_ART)?{slot?=?0;parameterTypes?=?null;returnType?=?null;}?else?if?(hookMethod?instanceof?Method)?{//?slot?在?Android?5.0?以下的系統,java.reflect.Method?類中的成員,//?它是?Dralvik?虛擬機中這個?Method?在虛擬機中的地址。//?Android?5.0?開始正式使用了?ART?虛擬機,所以不存在這個成員slot?=?getIntField(hookMethod,?"slot");parameterTypes?=?((Method)?hookMethod).getParameterTypes();returnType?=?((Method)?hookMethod).getReturnType();}?else?{//?處理?Constructorslot?=?getIntField(hookMethod,?"slot");parameterTypes?=?((Constructor<?>)?hookMethod).getParameterTypes();returnType?=?null;}//?打包?Hook?回調相關信息,準備進入?Native?層AdditionalHookInfo?additionalInfo?=?new?AdditionalHookInfo(callbacks,?parameterTypes,?returnType);//?進入?Native?層代碼,傳入?method、class、slot、hook?回調等信息hookMethodNative(hookMethod,?declaringClass,?slot,?additionalInfo);}return?callback.new?Unhook(hookMethod); }

    上面主要是添加了 XC_MethodHook 類型的 Hook 回調,然后將相關信息全部傳入了 Xposed native 層代碼中。

    最后返回一個 Unhook 對象,是為了取消 Hook,它的 unhook 方法如下:

    //?XC_MethodHook.java?-?class?Unhookpublic?void?unhook()?{XposedBridge.unhookMethod(hookMethod,?XC_MethodHook.this); } //?XposedBridge.javapublic?static?void?unhookMethod(Member?hookMethod,?XC_MethodHook?callback)?{CopyOnWriteSortedSet<XC_MethodHook>?callbacks;synchronized?(sHookedMethodCallbacks)?{callbacks?=?sHookedMethodCallbacks.get(hookMethod);if?(callbacks?==?null)return;}callbacks.remove(callback); }

    就是直接移除與這個 Java 方法相關的 Hook 處理回調。

    下面查看 hookMethodNative 函數的實現,發現它是一個 JNI 方法:

    private?native?synchronized?static?void?hookMethodNative(Member?method,?Class<?>?declaringClass,?int?slot,?Object?additionalInfo);

    它的實現在 libxposed_art.so 中,源代碼在 Xposed 項目中。

    首先需要解決一個問題,這個動態庫是什么時候加載的,它的 JNI 方法和 Java 層是什么時候關聯的?

    它是在 Java 虛擬機中創建時加載的,同時關聯的 JNI 方法。

    在 app_main2.cpp 中,Xposed 除了改寫 app_process 的 main 函數,還改寫了?AppRuntime::onVmCreated 函數:

    //?app_main2.cpp namespace?android?{ class?AppRuntime?:?public?AndroidRuntime { public://?...virtual?void?onVmCreated(JNIEnv*?env){if?(isXposedLoaded)xposed::onVmCreated(env);if?(mClassName.isEmpty())?{return;}char*?slashClassName?=?toSlashClassName(mClassName.string());mClass?=?env->FindClass(slashClassName);if?(mClass?==?NULL)?{ALOGE("ERROR:?could?not?find?class?'%s'\n",?mClassName.string());env->ExceptionDescribe();}free(slashClassName);mClass?=?reinterpret_cast<jclass>(env->NewGlobalRef(mClass));}//?... }; }

    回顧前面的內容,這個函數將在 Java 虛擬機創建后被回調:

    //?AndroidRuntime.cppvoid?AndroidRuntime::start(const?char*?className,?const?Vector<String8>&?options,?bool?zygote) {//?...if?(startVm(&mJavaVM,?&env,?zygote)?!=?0)?{return;}//?回調?Java?虛擬機創建onVmCreated(env);if?(startReg(env)?<?0)?{ALOGE("Unable?to?register?all?android?natives\n");return;}//?...?? }

    進入 xposed::onVmCreated 函數:

    //?xposed.cppvoid?onVmCreated(JNIEnv*?env)?{const?char*?xposedLibPath?=?NULL;//?首先確認?Xposed?庫的路徑是?ART?還是?Dalvikif?(!determineRuntime(&xposedLibPath))?{ALOGE("Could?not?determine?runtime,?not?loading?Xposed");return;}//?打開?Xposed?動態庫void*?xposedLibHandle?=?dlopen(xposedLibPath,?RTLD_NOW);if?(!xposedLibHandle)?{ALOGE("Could?not?load?libxposed:?%s",?dlerror());return;}dlerror();//?調用初始化方法bool?(*xposedInitLib)(XposedShared*?shared)?=?NULL;*(void?**)?(&xposedInitLib)?=?dlsym(xposedLibHandle,?"xposedInitLib");if?(!xposedInitLib)??{ALOGE("Could?not?find?function?xposedInitLib");return;}#if?XPOSED_WITH_SELINUXxposed->zygoteservice_accessFile?=?&service::membased::accessFile;xposed->zygoteservice_statFile???=?&service::membased::statFile;xposed->zygoteservice_readFile???=?&service::membased::readFile; #endif??//?XPOSED_WITH_SELINUXif?(xposedInitLib(xposed))?{//?調用綁定的?onVmCreated?回調函數xposed->onVmCreated(env);} }

    首先是 determineRuntime 確認 Xposed 的庫路徑:

    //?xposed.cppstatic?bool?determineRuntime(const?char**?xposedLibPath)?{FILE?*fp?=?fopen("/proc/self/maps",?"r");if?(fp?==?NULL)?{ALOGE("Could?not?open?/proc/self/maps:?%s",?strerror(errno));return?false;}bool?success?=?false;char?line[256];while?(fgets(line,?sizeof(line),?fp)?!=?NULL)?{char*?libname?=?strrchr(line,?'/');if?(!libname)continue;libname++;if?(strcmp("libdvm.so\n",?libname)?==?0)?{ALOGI("Detected?Dalvik?runtime");//?#define?XPOSED_LIB_DALVIK?POSED_LIB_DIR?"libxposed_dalvik.so"*xposedLibPath?=?XPOSED_LIB_DALVIK;success?=?true;break;}?else?if?(strcmp("libart.so\n",?libname)?==?0)?{ALOGI("Detected?ART?runtime");//?#define?XPOSED_LIB_ART?XPOSED_LIB_DIR?"libxposed_art.so"*xposedLibPath?=?XPOSED_LIB_ART;success?=?true;break;}}fclose(fp);return?success; }

    根據系統中是否存在 libdvm.so 或 libart.so,確認加載支持 ART 還是 Dalvik 版本的 Xposed 庫,在 ART 上加載 libxposed_art.so。

    然后使用 dlopen 加載鏈接了這個動態庫,那么它的符號就可以被正常訪問了。

    后面又調用了 xposedInitLib 函數:

    //?libxposed_art.cppbool?xposedInitLib(XposedShared*?shared)?{xposed?=?shared;xposed->onVmCreated?=?&onVmCreatedCommon;return?true; }

    指定了一個 xposed->onVmCreated 為 onVmCreatedCommon,看一下它的實現。

    //?libxposed_common.cppvoid?onVmCreatedCommon(JNIEnv*?env)?{if?(!initXposedBridge(env)?||?!initZygoteService(env))?{return;}if?(!onVmCreated(env))?{return;}xposedLoadedSuccessfully?=?true;return; }

    這里主要關注 initXposedBridge,它會進行 JNI 方法的注冊。

    //?libxposed_common.cppbool?initXposedBridge(JNIEnv*?env)?{//?#define?CLASS_XPOSED_BRIDGE??"de/robv/android/xposed/XposedBridge"classXposedBridge?=?env->FindClass(CLASS_XPOSED_BRIDGE);if?(classXposedBridge?==?NULL)?{ALOGE("Error?while?loading?Xposed?class?'%s':",?CLASS_XPOSED_BRIDGE);logExceptionStackTrace();env->ExceptionClear();return?false;}classXposedBridge?=?reinterpret_cast<jclass>(env->NewGlobalRef(classXposedBridge));ALOGI("Found?Xposed?class?'%s',?now?initializing",?CLASS_XPOSED_BRIDGE);//?注冊?XposedBridge?關聯的?JNI?方法if?(register_natives_XposedBridge(env,?classXposedBridge)?!=?JNI_OK)?{ALOGE("Could?not?register?natives?for?'%s'",?CLASS_XPOSED_BRIDGE);logExceptionStackTrace();env->ExceptionClear();return?false;}//?緩存?XposedBridge?的?handleHookedMethod?方法的?jmethodIDmethodXposedBridgeHandleHookedMethod?=?env->GetStaticMethodID(classXposedBridge,?"handleHookedMethod","(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");if?(methodXposedBridgeHandleHookedMethod?==?NULL)?{ALOGE("ERROR:?could?not?find?method?%s.handleHookedMethod(Member,?int,?Object,?Object,?Object[])",?CLASS_XPOSED_BRIDGE);logExceptionStackTrace();env->ExceptionClear();return?false;}return?true; } //?libxposed_common.cppint?register_natives_XposedBridge(JNIEnv*?env,?jclass?clazz)?{const?JNINativeMethod?methods[]?=?{NATIVE_METHOD(XposedBridge,?hadInitErrors,?"()Z"),NATIVE_METHOD(XposedBridge,?getStartClassName,?"()Ljava/lang/String;"),NATIVE_METHOD(XposedBridge,?getRuntime,?"()I"),NATIVE_METHOD(XposedBridge,?startsSystemServer,?"()Z"),NATIVE_METHOD(XposedBridge,?getXposedVersion,?"()I"),NATIVE_METHOD(XposedBridge,?initXResourcesNative,?"()Z"),//?注冊?hookMethodNative?方法NATIVE_METHOD(XposedBridge,?hookMethodNative,?"(Ljava/lang/reflect/Member;Ljava/lang/Class;ILjava/lang/Object;)V"),//?...};return?env->RegisterNatives(clazz,?methods,?NELEM(methods)); }

    其中 NATIVE_METHOD 是一個宏,方便注冊 JNI 方法:

    //?libxposed_common.h#ifndef?NATIVE_METHOD #define?NATIVE_METHOD(className,?functionName,?signature)?\{?#functionName,?signature,?reinterpret_cast<void*>(className?##?_?##?functionName)?} #endif

    現在回去,對 hookMethodNative 的具體實現進行分析,從這里開始就是真正開始實現方法 Hook 了。

    由于這里是 Xposed 方法 Hook 的核心實現,所以同時分析一下基于 Dalvik 的實現。

    Dalvik Hook 實現

    首先看一下 libxposed_dalvik.so 中的實現,驗證一下本文開頭基于 Dalvik 的方法 Hook 的描述。

    //?libxposed_dalvik.cppvoid?XposedBridge_hookMethodNative(JNIEnv*?env,?jclass?clazz,?jobject?reflectedMethodIndirect,jobject?declaredClassIndirect,?jint?slot,?jobject?additionalInfoIndirect)?{if?(declaredClassIndirect?==?NULL?||?reflectedMethodIndirect?==?NULL)?{dvmThrowIllegalArgumentException("method?and?declaredClass?must?not?be?null");return;}//?獲取?Dalvik?中表示?Java?類的?ClassObject?對象ClassObject*?declaredClass?=?(ClassObject*)?dvmDecodeIndirectRef(dvmThreadSelf(),?declaredClassIndirect);//?利用?slot?變量從?ClassObject?中找到?Dalvik?中表示?Java?方法的?Method?對象Method*?method?=?dvmSlotToMethod(declaredClass,?slot);if?(method?==?NULL)?{dvmThrowNoSuchMethodError("Could?not?get?internal?representation?for?method");return;}//?inline?bool?isMethodHooked(const?Method*?method)?{//???return?(method->nativeFunc?==?&hookedMethodCallback);//?}if?(isMethodHooked(method))?{//?此方法已經被?Hook,直接返回return;}//?保存原始方法的信息XposedHookInfo*?hookInfo?=?(XposedHookInfo*)?calloc(1,?sizeof(XposedHookInfo));memcpy(hookInfo,?method,?sizeof(hookInfo->originalMethodStruct));hookInfo->reflectedMethod?=?dvmDecodeIndirectRef(dvmThreadSelf(),?env->NewGlobalRef(reflectedMethodIndirect));hookInfo->additionalInfo?=?dvmDecodeIndirectRef(dvmThreadSelf(),?env->NewGlobalRef(additionalInfoIndirect));//?將此?Java?方法增加?native?描述符,即?JNI?方法SET_METHOD_FLAG(method,?ACC_NATIVE);//?設置?native?函數的處理函數,那么?Dalvik?解釋執行這個方法時,//?首先判斷會它是?JNI?方法,然后會跳轉至?nativeFunc?進行執行method->nativeFunc?=?&hookedMethodCallback;method->insns?=?(const?u2*)?hookInfo;method->registersSize?=?method->insSize;method->outsSize?=?0;if?(PTR_gDvmJit?!=?NULL)?{char?currentValue?=?*((char*)PTR_gDvmJit?+?MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull));if?(currentValue?==?0?||?currentValue?==?1)?{MEMBER_VAL(PTR_gDvmJit,?DvmJitGlobals,?codeCacheFull)?=?true;}?else?{ALOGE("Unexpected?current?value?for?codeCacheFull:?%d",?currentValue);}} }

    這里 Xposed 基于 Dalvik 實現的方法 Hook 處理比較簡單,就是先將這個 Java 方法修改為 native 方法,然后給它綁定一個 nativeFunc,當 Java 代碼調用這個方法時,由于它是 JNI 方法,虛擬機就會跳轉到 nativeFunc 進行執行。

    Dalvik 虛擬機執行 Java 方法的實現如下:

    //?Stack.cppvoid?dvmCallMethodV(Thread*?self,?const?Method*?method,?Object*?obj,?bool?fromJni,?JValue*?pResult,?va_list?args) {//?...if?(dvmIsNativeMethod(method))?{TRACE_METHOD_ENTER(self,?method);//?如果是?native?方法,則跳轉?nativeFunc?進行執行(*method->nativeFunc)((u4*)self->interpSave.curFrame,?pResult,?method,?self);TRACE_METHOD_EXIT(self,?method);}?else?{dvmInterpret(self,?method,?pResult);}//?... } //?Object.hINLINE?bool?dvmIsNativeMethod(const?Method*?method)?{return?(method->accessFlags?&?ACC_NATIVE)?!=?0; }

    可以看到,如果一個方法是 JNI 方法,那么 Dalvik 虛擬機就會調用它綁定的 nativeFunc 函數。

    前面設置的 hookedMethodCallback 函數將會把被調用的 Java 方法的參數進行轉發,最終會調用 Java 層 XposedBridge 的 handleHookedMethod 方法進行處理,就能夠達到 Hook 的目的了,至于 hookedMethodCallback 函數的實現,這里不再詳細分析,可以自己看一下。Java 層 handleHookedMethod 方法的實現和 ART 沒有區別,都是在 XposedBridge.jar 中,在下面 ART 部分中會進行分析。

    下面進入 libxposed_art.so 中的 hookMethodNative 函數實現:

    ART Hook 實現

    接下來關注 libxposed_art.so 中的實現。

    //?libxposed_art.cppvoid?XposedBridge_hookMethodNative(JNIEnv*?env,?jclass,?jobject?javaReflectedMethod,jobject,?jint,?jobject?javaAdditionalInfo)?{ScopedObjectAccess?soa(env);if?(javaReflectedMethod?==?nullptr)?{ #if?PLATFORM_SDK_VERSION?>=?23ThrowIllegalArgumentException("method?must?not?be?null"); #elseThrowIllegalArgumentException(nullptr,?"method?must?not?be?null"); #endifreturn;}//?ART?虛擬機中表示?Java?方法的?artMethod?對象ArtMethod*?artMethod?=?ArtMethod::FromReflectedMethod(soa,?javaReflectedMethod);//?fHook?這個方法artMethod->EnableXposedHook(soa,?javaAdditionalInfo); }

    上面使用 Java 層方法的反射表示對象 javaReflectedMethod,獲取了一個 ART 虛擬機中用來表示 Java 方法的 ArtMethod 對象,然后就直接進入 ArtMethod 的 EnableXposedHook 函數中了。

    其中 FromReflectedMethod 是 ART 虛擬機本來就有的方法;ScopedObjectAccess 是一個工具類,需要借助 env 進行操作。

    下面進入 ArtMethod 的 EnableXposedHook 函數中,從這里開始就進入 Xposed 修改過的 ART 虛擬機的項目 android_art 中了。

    //?art_method.ccvoid?ArtMethod::EnableXposedHook(ScopedObjectAccess&?soa,?jobject?additional_info)?{if?(UNLIKELY(IsXposedHookedMethod()))?{//?已被?Hookreturn;}?else?if?(UNLIKELY(IsXposedOriginalMethod()))?{//?是用于備份的?ArtMethod?對象,通常不應該走到這ThrowIllegalArgumentException(StringPrintf("Cannot?hook?the?method?backup:?%s",?PrettyMethod(this).c_str()).c_str());return;}//?獲取?ClassLinker,它是鏈接器auto*?cl?=?Runtime::Current()->GetClassLinker();//?獲取線性分配器,用于分配內存,類似于?malloc?auto*?linear_alloc?=?cl->GetAllocatorForClassLoader(GetClassLoader());//?創建一個新的?ArtMethod?對象,用于備份原始方法ArtMethod*?backup_method?=?cl->CreateRuntimeMethod(linear_alloc);//?復制當前?ArtMethod?至?backup_methodbackup_method->CopyFrom(this,?cl->GetImagePointerSize());//?添加?kAccXposedOriginalMethod?標記,說明是備份的方法backup_method->SetAccessFlags(backup_method->GetAccessFlags()?|?kAccXposedOriginalMethod);//?創建備份方法對應的反射對象mirror::AbstractMethod*?reflected_method;if?(IsConstructor())?{reflected_method?=?mirror::Constructor::CreateFromArtMethod(soa.Self(),?backup_method);}?else?{reflected_method?=?mirror::Method::CreateFromArtMethod(soa.Self(),?backup_method);}reflected_method->SetAccessible<false>(true);//?將備份的方法和一路從?Java?層傳過來的?additional_info(包含處理?Hook?的回調)裝到?XposedHookInfo?對象中XposedHookInfo*?hook_info?=?reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(),?sizeof(XposedHookInfo)));hook_info->reflected_method?=?soa.Vm()->AddGlobalRef(soa.Self(),?reflected_method);hook_info->additional_info?=?soa.Env()->NewGlobalRef(additional_info);hook_info->original_method?=?backup_method;ScopedThreadSuspension?sts(soa.Self(),?kSuspended);jit::ScopedJitSuspend?sjs;gc::ScopedGCCriticalSection?gcs(soa.Self(),gc::kGcCauseXposed,gc::kCollectorTypeXposed);ScopedSuspendAll?ssa(__FUNCTION__);//?清除本方法的調用者信息cl->InvalidateCallersForMethod(soa.Self(),?this);jit::Jit*?jit?=?art::Runtime::Current()->GetJit();if?(jit?!=?nullptr)?{//?將本方法的?CodeCache?中的內容移動到備份方法對象中//?CodeCache?就是從?Dex?文件中解析到的類和方法的相關信息,//?緩存起來,方便直接取用,而不是每次都解析?Dex?文件jit->GetCodeCache()->MoveObsoleteMethod(this,?backup_method);}//?將?hook_info?保存在用于原本用于存儲?JNI?方法的內存地址上?SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info),?sizeof(void*));//?設置此方法對應的匯編代碼的地址,一個?Java?方法經過編譯器編譯后會對應一段匯編代碼,//?當虛擬機執行這個?Java?方法時,如果處于?AOT?模式,就會直接跳轉到匯編代碼執行機器指令SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());//?設置?dex?中此方法的偏移為?0,表示它是?native?或?abstract?方法,沒有具體代碼SetCodeItemOffset(0);//?清除以下標志const?uint32_t?kRemoveFlags?=?kAccNative?|?kAccSynchronized?|?kAccAbstract?|?kAccDefault?|?kAccDefaultConflict;//?添加?Xposed?自定義的?kAccXposedHookedMethod?標志,用來標識它是被?Hook?的方法//?添加后,IsXposedHookedMethod?函數就會返回?trueSetAccessFlags((GetAccessFlags()?&?~kRemoveFlags)?|?kAccXposedHookedMethod);MutexLock?mu(soa.Self(),?*Locks::thread_list_lock_);Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation,?this); }

    上面代碼的主要工作是備份當前需要被 Hook 的方法,然后設置當前方法的匯編代碼地址為 GetQuickProxyInvokeHandler(),此時就完成了 Hook 目的。

    當這個 Java 方法被調用時,會跳轉到上面設置的匯編代碼地址處,Xposed 將會對這個 Java 方法的參數進行轉發等處理,修改方法返回值,實現最終 Hook。

    不過沒有看到最終的處理,并不知道是怎么做的,下面繼續分析。

    首先看 GetQuickProxyInvokeHandler()?的返回值:

    extern?"C"?void?art_quick_proxy_invoke_handler(); static?inline?const?void*?GetQuickProxyInvokeHandler()?{return?reinterpret_cast<const?void*>(art_quick_proxy_invoke_handler); }

    它是一個 art_quick_proxy_invoke_handler 函數的地址,這個函數是在其他地方實現的(有 extern 聲明),經過了解,發現它是由匯編代碼實現的,有 arm、arm64、mips、mips64、x86、x86_64 這幾個指令集的實現,這里看一下 arm 上的實現:

    //?quick_entrypoints_arm.S.extern?artQuickProxyInvokeHandler ENTRY?art_quick_proxy_invoke_handlerSETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_R0//?傳遞相關參數mov?????r2,?r9?????????????????@?pass?Thread::Currentmov?????r3,?sp?????????????????@?pass?SP//?跳轉至?artQuickProxyInvokeHandler?函數blx?????artQuickProxyInvokeHandler??@?(Method*?proxy?method,?receiver,?Thread*,?SP)ldr?????r2,?[r9,?#THREAD_EXCEPTION_OFFSET]??@?load?Thread::Current()->exception_add?????sp,?#(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE?-?FRAME_SIZE_REFS_ONLY_CALLEE_SAVE).cfi_adjust_cfa_offset?-(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE?-?FRAME_SIZE_REFS_ONLY_CALLEE_SAVE)RESTORE_REFS_ONLY_CALLEE_SAVE_FRAMEcbnz????r2,?1f?????????????????@?success?if?no?exception?is?pendingvmov????d0,?r0,?r1?????????????@?store?into?fpr,?for?when?it's?a?fpr?return...bx??????lr?????????????????????@?return?on?success 1:DELIVER_PENDING_EXCEPTION END?art_quick_proxy_invoke_handler

    art_quick_proxy_invoke_handler 跳轉至了 artQuickProxyInvokeHandler 函數中,那么繼續跟進。

    //?qucik_trampoline_entrypoints.ccextern?"C"?uint64_t?artQuickProxyInvokeHandler(ArtMethod*?proxy_method,?mirror::Object*?receiver,?Thread*?self,?ArtMethod**?sp)SHARED_REQUIRES(Locks::mutator_lock_)?{//?bool?IsXposedHookedMethod()?{//???return?(GetAccessFlags()?&?kAccXposedHookedMethod)?!=?0;//?}const?bool?is_xposed?=?proxy_method->IsXposedHookedMethod();if?(!is_xposed)?{DCHECK(proxy_method->IsRealProxyMethod())?<<?PrettyMethod(proxy_method);DCHECK(receiver->GetClass()->IsProxyClass())?<<?PrettyMethod(proxy_method);}const?char*?old_cause?=?self->StartAssertNoThreadSuspension("Adding?to?IRT?proxy?object?arguments");DCHECK_EQ((*sp),?proxy_method)?<<?PrettyMethod(proxy_method);self->VerifyStack();JNIEnvExt*?env?=?self->GetJniEnv();ScopedObjectAccessUnchecked?soa(env);ScopedJniEnvLocalRefState?env_state(env);const?bool?is_static?=?proxy_method->IsStatic();jobject?rcvr_jobj?=?is_static???nullptr?:?soa.AddLocalReference<jobject>(receiver);ArtMethod*?non_proxy_method?=?proxy_method->GetInterfaceMethodIfProxy(sizeof(void*));CHECK(is_xposed?||?!non_proxy_method->IsStatic())?<<?PrettyMethod(proxy_method)?<<?"?"<<?PrettyMethod(non_proxy_method);std::vector<jvalue>?args;uint32_t?shorty_len?=?0;const?char*?shorty?=?non_proxy_method->GetShorty(&shorty_len);BuildQuickArgumentVisitor?local_ref_visitor(sp,?is_static,?shorty,?shorty_len,?&soa,?&args);local_ref_visitor.VisitArguments();if?(!is_static)?{DCHECK_GT(args.size(),?0U)?<<?PrettyMethod(proxy_method);args.erase(args.begin());}if?(is_xposed)?{jmethodID?proxy_methodid?=?soa.EncodeMethod(proxy_method);self->EndAssertNoThreadSuspension(old_cause);//?處理?Hook?方法JValue?result?=?InvokeXposedHandleHookedMethod(soa,?shorty,?rcvr_jobj,?proxy_methodid,?args);local_ref_visitor.FixupReferences();//?返回?Java?方法的返回值return?result.GetJ();}//?... }

    可以大概看出來 artQuickProxyInvokeHandler 函數是用于處理動態代理方法的,不過 Xposed 對這個方法進行了修改,使其能夠處理被 Hook 的方法,重點關注下面判斷語句中的代碼,如果是被 Xposed Hook 的方法,那么進入 InvokeXposedHandleHookedMethod 進行處理:

    //?entrypoint_utils.ccJValue?InvokeXposedHandleHookedMethod(ScopedObjectAccessAlreadyRunnable&?soa,?const?char*?shorty,jobject?rcvr_jobj,?jmethodID?method,std::vector<jvalue>&?args)?{soa.Self()->AssertThreadSuspensionIsAllowable();jobjectArray?args_jobj?=?nullptr;const?JValue?zero;int32_t?target_sdk_version?=?Runtime::Current()->GetTargetSdkVersion();//?...//?取出?hook_infoconst?XposedHookInfo*?hook_info?=?soa.DecodeMethod(method)->GetXposedHookInfo();//?調用?Java?層的?XposedBridge.handleHookedMethod?方法jvalue?invocation_args[5];invocation_args[0].l?=?hook_info->reflected_method;invocation_args[1].i?=?1;invocation_args[2].l?=?hook_info->additional_info;//?方法的目標作用對象?thisinvocation_args[3].l?=?rcvr_jobj;//?參數保存傳給方法的參數invocation_args[4].l?=?args_jobj;jobject?result?=soa.Env()->CallStaticObjectMethodA(ArtMethod::xposed_callback_class,ArtMethod::xposed_callback_method,invocation_args);if?(UNLIKELY(soa.Self()->IsExceptionPending()))?{return?zero;}?else?{if?(shorty[0]?==?'V'?||?(shorty[0]?==?'L'?&&?result?==?nullptr))?{return?zero;}size_t?pointer_size?=?Runtime::Current()->GetClassLinker()->GetImagePointerSize();mirror::Class*?result_type?=?soa.DecodeMethod(method)->GetReturnType(true?/*?resolve?*/,?pointer_size);mirror::Object*?result_ref?=?soa.Decode<mirror::Object*>(result);JValue?result_unboxed;if?(!UnboxPrimitiveForResult(result_ref,?result_type,?&result_unboxed))?{DCHECK(soa.Self()->IsExceptionPending());return?zero;}return?result_unboxed;} }

    這里就調用到了 Java 層 XposedBridge 的 handleHookedMethod 方法中。

    //?XposedBridge.javaprivate?static?Object?handleHookedMethod(Member?method,?int?originalMethodId,?Object?additionalInfoObj,Object?thisObject,?Object[]?args)?throws?Throwable?{//?取出?Hook?處理回調等信息AdditionalHookInfo?additionalInfo?=?(AdditionalHookInfo)?additionalInfoObj;if?(disableHooks)?{try?{//?如果關閉?Hook,那么調用原始方法return?invokeOriginalMethodNative(method,?originalMethodId,?additionalInfo.parameterTypes,additionalInfo.returnType,?thisObject,?args);}?catch?(InvocationTargetException?e)?{throw?e.getCause();}}Object[]?callbacksSnapshot?=?additionalInfo.callbacks.getSnapshot();final?int?callbacksLength?=?callbacksSnapshot.length;if?(callbacksLength?==?0)?{try?{//?沒有處理?Hook?的回調,則調用原始方法return?invokeOriginalMethodNative(method,?originalMethodId,?additionalInfo.parameterTypes,additionalInfo.returnType,?thisObject,?args);}?catch?(InvocationTargetException?e)?{throw?e.getCause();}}MethodHookParam?param?=?new?MethodHookParam();param.method?=?method;param.thisObject?=?thisObject;param.args?=?args;int?beforeIdx?=?0;do?{try?{//?回調?beforeHookedMethod?方法,表示在?Hook?之前((XC_MethodHook)?callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);}?catch?(Throwable?t)?{XposedBridge.log(t);param.setResult(null);param.returnEarly?=?false;continue;}if?(param.returnEarly)?{beforeIdx++;break;}}?while?(++beforeIdx?<?callbacksLength);//?Hook?回調沒有處理,則調用原始方法if?(!param.returnEarly)?{try?{param.setResult(invokeOriginalMethodNative(method,?originalMethodId,additionalInfo.parameterTypes,?additionalInfo.returnType,?param.thisObject,?param.args));}?catch?(InvocationTargetException?e)?{param.setThrowable(e.getCause());}}int?afterIdx?=?beforeIdx?-?1;do?{Object?lastResult?=??param.getResult();Throwable?lastThrowable?=?param.getThrowable();try?{//?調用?afterHookedMethod?方法,表示?Hook?之后((XC_MethodHook)?callbacksSnapshot[afterIdx]).afterHookedMethod(param);}?catch?(Throwable?t)?{XposedBridge.log(t);if?(lastThrowable?==?null)param.setResult(lastResult);elseparam.setThrowable(lastThrowable);}}?while?(--afterIdx?>=?0);//?如果有異常,則拋出異常,否則返回處理后的結果if?(param.hasThrowable())throw?param.getThrowable();elsereturn?param.getResult(); }

    這里就能清晰的看到 Hook 最終處理了,至此就完成了 Hook。其中 invokeOriginalMethodNative 的實現如下:

    //?libxposed_art.cppjobject?XposedBridge_invokeOriginalMethodNative(JNIEnv*?env,?jclass,?jobject?javaMethod,jint?isResolved,?jobjectArray,?jclass,?jobject?javaReceiver,?jobjectArray?javaArgs)?{ScopedFastNativeObjectAccess?soa(env);if?(UNLIKELY(!isResolved))?{//?從備份的方法中取得原始方法ArtMethod*?artMethod?=?ArtMethod::FromReflectedMethod(soa,?javaMethod);if?(LIKELY(artMethod->IsXposedHookedMethod()))?{javaMethod?=?artMethod->GetXposedHookInfo()->reflected_method;}} #if?PLATFORM_SDK_VERSION?>=?23//?調用虛擬機的執行方法調用原始方法邏輯return?InvokeMethod(soa,?javaMethod,?javaReceiver,?javaArgs); #elsereturn?InvokeMethod(soa,?javaMethod,?javaReceiver,?javaArgs,?true); #endif }

    還有最后一個問題,就是一個被 Hook 的方法的調用過程,上面只分析了處理過程,而沒有正向的調用,下面開始分析。

    調用過程

    分析一個 Java 方法的調用,可以從 AndroidRuntime.start 中開始,Java 虛擬機執行的第一個類是 ZygoteInit 從此就進入了 Java 層,它使用的是 JNIEnv 提供的 CallStaticVoidMethod 方法,看一下它的實現。

    //?jni_internal.ccstatic?void?CallStaticVoidMethod(JNIEnv*?env,?jclass,?jmethodID?mid,?...)?{va_list?ap;va_start(ap,?mid);CHECK_NON_NULL_ARGUMENT_RETURN_VOID(mid);ScopedObjectAccess?soa(env);InvokeWithVarArgs(soa,?nullptr,?mid,?ap);va_end(ap); }

    調用了 InvokeWithVarArgs 函數:

    //?reflection.ccJValue?InvokeWithVarArgs(const?ScopedObjectAccessAlreadyRunnable&?soa,?jobject?obj,?jmethodID?mid,va_list?args)SHARED_REQUIRES(Locks::mutator_lock_)?{//?...ArtMethod*?method?=?soa.DecodeMethod(mid);bool?is_string_init?=?method->GetDeclaringClass()->IsStringClass()?&&?method->IsConstructor();if?(is_string_init)?{method?=?soa.DecodeMethod(WellKnownClasses::StringInitToStringFactoryMethodID(mid));}mirror::Object*?receiver?=?method->IsStatic()???nullptr?:?soa.Decode<mirror::Object*>(obj);uint32_t?shorty_len?=?0;const?char*?shorty?=?method->GetInterfaceMethodIfProxy(sizeof(void*))->GetShorty(&shorty_len);JValue?result;ArgArray?arg_array(shorty,?shorty_len);arg_array.BuildArgArrayFromVarArgs(soa,?receiver,?args);//?調用?InvokeWithArgArrayInvokeWithArgArray(soa,?method,?&arg_array,?&result,?shorty);if?(is_string_init)?{UpdateReference(soa.Self(),?obj,?result.GetL());}return?result; }

    繼續看 InvokeWithArgArray:

    //?reflection.ccstatic?void?InvokeWithArgArray(const?ScopedObjectAccessAlreadyRunnable&?soa,ArtMethod*?method,?ArgArray*?arg_array,?JValue*?result,const?char*?shorty)SHARED_REQUIRES(Locks::mutator_lock_)?{uint32_t*?args?=?arg_array->GetArray();if?(UNLIKELY(soa.Env()->check_jni))?{CheckMethodArguments(soa.Vm(),?method->GetInterfaceMethodIfProxy(sizeof(void*)),?args);}method->Invoke(soa.Self(),?args,?arg_array->GetNumBytes(),?result,?shorty); }

    最終是調用到了 ArtMethod 的 Invoke 函數:

    //?reflection.ccvoid?ArtMethod::Invoke(Thread*?self,?uint32_t*?args,?uint32_t?args_size,?JValue*?result,const?char*?shorty)?{//?...ManagedStack?fragment;self->PushManagedStackFragment(&fragment);Runtime*?runtime?=?Runtime::Current();if?(UNLIKELY(!runtime->IsStarted()?||?Dbg::IsForcedInterpreterNeededForCalling(self,?this)))?{//?...}?else?{DCHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(),?sizeof(void*));constexpr?bool?kLogInvocationStartAndReturn?=?false;bool?have_quick_code?=?GetEntryPointFromQuickCompiledCode()?!=?nullptr;if?(LIKELY(have_quick_code))?{//?...if?(!IsStatic())?{(*art_quick_invoke_stub)(this,?args,?args_size,?self,?result,?shorty);}?else?{(*art_quick_invoke_static_stub)(this,?args,?args_size,?self,?result,?shorty);}//?...}?else?{//?...}}self->PopManagedStackFragment(fragment); }

    根據 Java 方法類型是非靜態還是靜態,跳入 art_quick_invoke_stub 或 art_quick_invoke_static_stub,看一下 art_quick_invoke_stub:

    //?reflection.ccextern?"C"?void?art_quick_invoke_stub(ArtMethod*?method,?uint32_t*?args,?uint32_t?args_size,Thread*?self,?JValue*?result,?const?char*?shorty)?{quick_invoke_reg_setup<false>(method,?args,?args_size,?self,?result,?shorty); } //?reflection.cctemplate?<bool?kIsStatic> static?void?quick_invoke_reg_setup(ArtMethod*?method,?uint32_t*?args,?uint32_t?args_size,Thread*?self,?JValue*?result,?const?char*?shorty)?{uint32_t?core_reg_args[4];uint32_t?fp_reg_args[16];uint32_t?gpr_index?=?1;uint32_t?fpr_index?=?0;uint32_t?fpr_double_index?=?0;uint32_t?arg_index?=?0;const?uint32_t?result_in_float?=?kArm32QuickCodeUseSoftFloat???0?:(shorty[0]?==?'F'?||?shorty[0]?==?'D')???1?:?0;if?(!kIsStatic)?{core_reg_args[gpr_index++]?=?args[arg_index++];}for?(uint32_t?shorty_index?=?1;?shorty[shorty_index]?!=?'\0';?++shorty_index,?++arg_index)?{char?arg_type?=?shorty[shorty_index];if?(kArm32QuickCodeUseSoftFloat)?{arg_type?=?(arg_type?==?'D')???'J'?:?arg_type;arg_type?=?(arg_type?==?'F')???'I'?:?arg_type;}switch?(arg_type)?{case?'D':?{fpr_double_index?=?std::max(fpr_double_index,?RoundUp(fpr_index,?2));if?(fpr_double_index?<?arraysize(fp_reg_args))?{fp_reg_args[fpr_double_index++]?=?args[arg_index];fp_reg_args[fpr_double_index++]?=?args[arg_index?+?1];}++arg_index;break;}case?'F':if?(fpr_index?%?2?==?0)?{fpr_index?=?std::max(fpr_double_index,?fpr_index);}if?(fpr_index?<?arraysize(fp_reg_args))?{fp_reg_args[fpr_index++]?=?args[arg_index];}break;//?...}}//?進入下一步art_quick_invoke_stub_internal(method,?args,?args_size,?self,?result,?result_in_float,core_reg_args,?fp_reg_args); }

    最后是調用了 art_quick_invoke_stub_internal,它是匯編代碼實現的:

    //?quick_entrypoints_arm.SENTRY?art_quick_invoke_stub_internalSPILL_ALL_CALLEE_SAVE_GPRS?????????????@?spill?regs?(9)mov????r11,?sp?????????????????????????@?save?the?stack?pointer.cfi_def_cfa_register?r11mov????r9,?r3??????????????????????????@?move?managed?thread?pointer?into?r9add????r4,?r2,?#4??????????????????????@?create?space?for?method?pointer?in?framesub????r4,?sp,?r4??????????????????????@?reserve?&?align?*stack*?to?16?bytes:?native?callingand????r4,?#0xFFFFFFF0?????????????????@?convention?only?aligns?to?8B,?so?we?have?to?ensure?ARTmov????sp,?r4??????????????????????????@?16B?alignment?ourselves.mov????r4,?r0??????????????????????????@?save?method*add????r0,?sp,?#4??????????????????????@?pass?stack?pointer?+?method?ptr?as?dest?for?memcpybl?????memcpy??????????????????????????@?memcpy?(dest,?src,?bytes)mov????ip,?#0??????????????????????????@?set?ip?to?0str????ip,?[sp]????????????????????????@?store?null?for?method*?at?bottom?of?frameldr????ip,?[r11,?#48]??????????????????@?load?fp?register?argument?array?pointervldm???ip,?{s0-s15}????????????????????@?copy?s0?-?s15ldr????ip,?[r11,?#44]??????????????????@?load?core?register?argument?array?pointermov????r0,?r4??????????????????????????@?restore?method*add????ip,?ip,?#4??????????????????????@?skip?r0ldm????ip,?{r1-r3}?????????????????????@?copy?r1?-?r3#ifdef?ARM_R4_SUSPEND_FLAGmov????r4,?#SUSPEND_CHECK_INTERVAL?????@?reset?r4?to?suspend?check?interval #endifldr????ip,?[r0,?#ART_METHOD_QUICK_CODE_OFFSET_32]??@?get?pointer?to?the?codeblx????ip??????????????????????????????@?call?the?methodmov????sp,?r11?????????????????????????@?restore?the?stack?pointer.cfi_def_cfa_register?spldr????r4,?[sp,?#40]???????????????????@?load?result_is_floatldr????r9,?[sp,?#36]???????????????????@?load?the?result?pointercmp????r4,?#0ite????eqstrdeq?r0,?[r9]????????????????????????@?store?r0/r1?into?result?pointervstrne?d0,?[r9]????????????????????????@?store?s0-s1/d0?into?result?pointerpop????{r4,?r5,?r6,?r7,?r8,?r9,?r10,?r11,?pc}???????????????@?restore?spill?regs END?art_quick_invoke_stub_internal

    其中中間部分一行代碼使用 ldr 指令設置 ip 寄存器的位置來指示指令地址,使用到了 ART_METHOD_QUICK_CODE_OFFSET_32 這個宏,它是 32,表示 EntryPointFromQuickCompiledCodeOffset 這個函數返回的成員的偏移,也就是 entry_point_from_quick_compiled_code_。

    //?asm_support.h#define?ART_METHOD_QUICK_CODE_OFFSET_32?32 ADD_TEST_EQ(ART_METHOD_QUICK_CODE_OFFSET_32,art::ArtMethod::EntryPointFromQuickCompiledCodeOffset(4).Int32Value()) //?art_method.hstatic?MemberOffset?EntryPointFromQuickCompiledCodeOffset(size_t?pointer_size)?{return?MemberOffset(PtrSizedFieldsOffset(pointer_size)?+?OFFSETOF_MEMBER(PtrSizedFields,?entry_point_from_quick_compiled_code_)?/?sizeof(void*)?*?pointer_size); }

    回到前面 Hook 時,使用了 SetEntryPointFromQuickCompiledCode,其實就是設置這個變量。

    //?art_method.hvoid?SetEntryPointFromQuickCompiledCode(const?void*?entry_point_from_quick_compiled_code)?{SetEntryPointFromQuickCompiledCodePtrSize(entry_point_from_quick_compiled_code,sizeof(void*));}ALWAYS_INLINE?void?SetEntryPointFromQuickCompiledCodePtrSize(const?void*?entry_point_from_quick_compiled_code,?size_t?pointer_size)?{DCHECK(Runtime::Current()->IsAotCompiler()?||?!IsXposedHookedMethod());SetNativePointer(EntryPointFromQuickCompiledCodeOffset(pointer_size),entry_point_from_quick_compiled_code,?pointer_size);}

    那么下一步使用了 blx 跳轉指令,代碼就會跳轉到這個地址上執行,進入 GetQuickProxyInvokeHandler 返回的地址 art_quick_proxy_invoke_handler 中, 最后執行 artQuickProxyInvokeHandler 函數,Xposed 在這個函數里面處理了 Hook,完成 Hook。

    到這里就分析完了 Xposed 的實現,其實還有很多細節沒有去分析,通過對比 ART 虛擬機的原始代碼和 Xposed 修改后的代碼,發現 Xposed 修改的地方還是很多的,大概有幾百處,雖然每個文件修改的代碼不多,但是足以說明 Xposed 作者對與于 Android 系統原理和 ART 虛擬機的了解的深入程度。

    總結

    通過分析 Xposed 的實現原理,對于 Java 方法的 Hook 原理有了一些了解,同時回顧了 Android zygote 進程相關的內容,對于 ART 虛擬機執行方法的過程也有了一個大概的認識。

    參考

    https://bbs.pediy.com/thread-257844.htm

    https://blog.csdn.net/Innost/article/details/50461783

    https://blog.csdn.net/Luoshengyang/article/details/39256813

    https://blog.csdn.net/Luoshengyang/article/details/8914953

    贈送源碼:https://github.com/Pangu-Immortal《最完整的Android逆向知識體系》

    總結

    以上是生活随笔為你收集整理的Xposed是如何为所欲为的?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。