Xposed 實現原理分析

2021-02-11 l0neman未央
Xposed 實現原理分析

前言

Xposed 使用方法

Xposed 原理概述

Android zygote 進程

基於 Dalvik 的方法 Hook

基於 ART 的方法 Hook

Xposed 工作流程

Xposed 項目結構

Xposed

XposedBridge

XposedInstaller

android_art

XposedTools

Xposed 源碼分析

Xposed 安裝

Xposed 啟動

Xposed 方法 Hook

總結

參考

前言

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 虛擬機運行原理的了解。

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 的回調方法。

下面正式開始分析。

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 相關內容。

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.rc

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
writepid /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 實現。

基於 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 操作。

基於 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 進行分析。

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 的整體工作流程,下面開始著手進行源碼分析。

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 這個項目。

Xposed 源碼分析

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

Xposed 安裝下載

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

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

// StatusInstallerFragment.java

private 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() {
@Override
public 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.java

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

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

直接刷入

// FlashDirectly.java

public 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.cpp

int 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) {
// 初始化 xposed
isXposedLoaded = 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.cpp

bool 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 類型,無法加載 Xposed
if (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_SELINUX
xposed->isSELinuxEnabled = is_selinux_enabled() == 1;
xposed->isSELinuxEnforcing = xposed->isSELinuxEnabled && security_getenforce() == 1;
#else
xposed->isSELinuxEnabled = false;
xposed->isSELinuxEnforcing = false;
#endif // XPOSED_WITH_SELINUX

if (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_SELINUX
if (xposed->isSELinuxEnabled) {
xposed::service::membased::restrictMemoryInheritance();
}
#endif // XPOSED_WITH_SELINUX

if (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.cpp

bool 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.cpp

static void runtimeStart(AppRuntime& runtime, const char *classname, const Vector<String8>& options, bool zygote)
{
#if PLATFORM_SDK_VERSION >= 23
runtime.start(classname, options, zygote);
#else
// try newer variant (5.1.1_r19 and later) first
void (*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 variant
void (*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 happen
LOG_ALWAYS_FATAL("app_process: could not locate AndroidRuntime::start() method.");
#endif
}

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

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

// AndroidRuntime.cpp

void 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/XposedBridge
char* 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.java

protected static void main(String[] args) {
// Initialize the Xposed framework and modules
try {
// 判斷 native 加載成功
if (!hadInitErrors()) {
// Xposed 相關初始化
initXResources();
// SELinux 相關支持
SELinuxHelper.initOnce();
SELinuxHelper.initForProcess(null);

// runtime 表示 ART 還是 Dalivk
runtime = 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() {
@Override
protected 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 well
lpparam.classLoader = XposedBridge.BOOTCLASSLOADER;
lpparam.appInfo = null;
lpparam.isFirstApplication = true;
XC_LoadPackage.callAll(lpparam);
}
});
}
// ...

hookAllConstructors(LoadedApk.class, new XC_MethodHook() {
@Override
protected 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.java

public 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.java

private 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.java

public 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.java

public 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.java

public 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.java

public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
// 只允許 Method 和 Constructor 類型,Constructor 類型為了支持 findAndHookConstructor
if (!(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 {
// 處理 Constructor
slot = 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 Unhook

public void unhook() {
XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
}

// XposedBridge.java

public 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.cpp

void 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.cpp

void onVmCreated(JNIEnv* env) {
const char* xposedLibPath = NULL;
// 首先確認 Xposed 庫的路徑是 ART 還是 Dalvik
if (!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_SELINUX
xposed->zygoteservice_accessFile = &service::membased::accessFile;
xposed->zygoteservice_statFile = &service::membased::statFile;
xposed->zygoteservice_readFile = &service::membased::readFile;
#endif // XPOSED_WITH_SELINUX

if (xposedInitLib(xposed)) {
// 調用綁定的 onVmCreated 回調函數
xposed->onVmCreated(env);
}
}

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

// xposed.cpp

static 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.cpp

bool xposedInitLib(XposedShared* shared) {
xposed = shared;
xposed->onVmCreated = &onVmCreatedCommon;
return true;
}

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

// libxposed_common.cpp

void onVmCreatedCommon(JNIEnv* env) {
if (!initXposedBridge(env) || !initZygoteService(env)) {
return;
}

if (!onVmCreated(env)) {
return;
}

xposedLoadedSuccessfully = true;
return;
}

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

// libxposed_common.cpp

bool 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 方法的 jmethodID
methodXposedBridgeHandleHookedMethod = 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.cpp

int 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.cpp

void 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.cpp

void 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.h

INLINE 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.cpp

void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
jobject, jint, jobject javaAdditionalInfo) {
ScopedObjectAccess soa(env);
if (javaReflectedMethod == nullptr) {
#if PLATFORM_SDK_VERSION >= 23
ThrowIllegalArgumentException("method must not be null");
#else
ThrowIllegalArgumentException(nullptr, "method must not be null");
#endif
return;
}

// 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.cc

void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
if (UNLIKELY(IsXposedHookedMethod())) {
// 已被 Hook
return;
} 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_method
backup_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 函數就會返回 true
SetAccessFlags((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_handler
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_R0
// 傳遞相關參數
mov r2, r9 @ pass Thread::Current
mov 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_FRAME
cbnz r2, 1f @ success if no exception is pending
vmov 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.cc

extern "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.cc

JValue 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_info
const 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;
// 方法的目標作用對象 this
invocation_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.java

private 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);
else
param.setThrowable(lastThrowable);
}
} while (--afterIdx >= 0);

// 如果有異常,則拋出異常,否則返回處理後的結果
if (param.hasThrowable())
throw param.getThrowable();
else
return param.getResult();
}

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

// libxposed_art.cpp

jobject 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);
#else
return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs, true);
#endif
}

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

調用過程

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

// jni_internal.cc

static 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.cc

JValue 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);
// 調用 InvokeWithArgArray
InvokeWithArgArray(soa, method, &arg_array, &result, shorty);
if (is_string_init) {
UpdateReference(soa.Self(), obj, result.GetL());
}
return result;
}

繼續看 InvokeWithArgArray:

// reflection.cc

static 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.cc

void 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.cc

extern "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.cc

template <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.S

ENTRY art_quick_invoke_stub_internal
SPILL_ALL_CALLEE_SAVE_GPRS @ spill regs (9)
mov r11, sp @ save the stack pointer
.cfi_def_cfa_register r11

mov r9, r3 @ move managed thread pointer into r9

add r4, r2, #4 @ create space for method pointer in frame
sub r4, sp, r4 @ reserve & align *stack* to 16 bytes: native calling
and r4, #0xFFFFFFF0 @ convention only aligns to 8B, so we have to ensure ART
mov sp, r4 @ 16B alignment ourselves.

mov r4, r0 @ save method*
add r0, sp, #4 @ pass stack pointer + method ptr as dest for memcpy
bl memcpy @ memcpy (dest, src, bytes)
mov ip, #0 @ set ip to 0
str ip, [sp] @ store null for method* at bottom of frame

ldr ip, [r11, #48] @ load fp register argument array pointer
vldm ip, {s0-s15} @ copy s0 - s15

ldr ip, [r11, #44] @ load core register argument array pointer
mov r0, r4 @ restore method*
add ip, ip, #4 @ skip r0
ldm ip, {r1-r3} @ copy r1 - r3

#ifdef ARM_R4_SUSPEND_FLAG
mov r4, #SUSPEND_CHECK_INTERVAL @ reset r4 to suspend check interval
#endif

ldr ip, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] @ get pointer to the code
blx ip @ call the method

mov sp, r11 @ restore the stack pointer
.cfi_def_cfa_register sp

ldr r4, [sp, #40] @ load result_is_float
ldr r9, [sp, #36] @ load the result pointer
cmp r4, #0
ite eq
strdeq r0, [r9] @ store r0/r1 into result pointer
vstrne d0, [r9] @ store s0-s1/d0 into result pointer

pop {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.h

static 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.h

void 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

相關焦點

  • 為所欲為的 Xposed 是怎麼實現的?
    作者:l0neman本文轉載自公眾號l0neman。
  • HOOK工具原理系列之Xposed
    #initXposedBridgelibxposed_art.cpp#onVmCreatedde.robv.android.xposed.XposedBridge#main例子Hook原理分析XposedBridge#findAndHookMethodXposedBridge#hookMethodlibxposed.cpp#hookMethodNativeEnableXposedHookartQuickProxyInvokeHandlerInvokeXposedHandleHookedMethodInvokeXposedHandleHookedMethodXposed.java
  • 免安裝xposed 也能用xposed框架及插件
    免root免刷機,甚至不用安裝xposed模塊,直接在分身大師內部運行xposed插件!2. 首批支持的功能有:自動搶紅包,本地修改餘額,偽裝地理位置,偽裝設備信息等;筆者親測了幾個功能,本地修改餘額和自動搶紅包都實現了,免去了root的煩惱,確實方便太多。
  • Xposed高級用法 實現Tinker熱修復
    給大家簡單介紹一下,如何用Xposed實現對某個類的替換覆蓋。(之前的這篇帖子,也不錯。Xposed實現Native層Hook:https://bbs.pediy.com/thread-251171.htm)當我們在 findClas的時候,其實我們最終都會去dexElements數組裡面,去尋找,我們需要的類。
  • 免root,免刷機,甚至免安裝 xposed 框架,也能用 xposed 框架及插件...
    下面這篇文章將分為功能介紹和技術原理兩部分,由於技術原理比較深奧,對技術感興趣的同學可以直接跳到文章後半部分觀看。歡迎互相討論!  首先說功能部分。該應用是<分身大師X版>,是基於分身大師提出的升級版本。
  • xposed 入門之修改手機 IMEI
    2019年1月加入去哪兒網,現負責app的分析和設備指紋反爬事項,對app脫殼,java/nativec層加解密算法分析還原有豐富經驗。
  • Android Hook神器:XPosed入門與登陸劫持演示
    地址為: http://repo.xposed.info/module/de.robv.android.xposed.installer。 安裝好後進入XposedInstaller應用程式,會出現需要激活框架的界面,如下圖所示。這裡我們點擊「安裝/更新」就能完成框架的激活了。
  • Xposed精品連載 | 一篇文章搞定各系統Xposed安裝
    大體上可以分為兩個部分a) Java層的支持 XposedBridge.jarb) C層的支持 主要就是XposedBridge.jar中相應的native函數的實現一般情況下,一個Hook框架,要完成它的Hook功能。需要把代碼注入到對應的app進程中。而Xposed使用的是Zygote注入。綜上所述,Xposed框架在安裝過程中,需要替換一些系統重要的文件。
  • 介紹下那個可以讓你為所欲為的神器 Xposed
    比如朋友圈微信步數的修改,其實就是 Hook 了數據發送的方法,實現步數的修改。比如處理安卓的 SSL Pining,用 Hook 技術也可以修改 SSL 證書校驗結果,實現校驗的繞過。對於 App 爬蟲來說,我們也可以 Hook 一些關鍵的方法拿到方法執行前後的結果,從而實現數據的截獲。那這些技術怎麼來實現呢?這裡就不得不提一個框架,叫做 Xposed。
  • 「最美應用」Virtual Xposed:無需 ROOT 就能讓你用上 Xposed 框架
    Xposed 框架是 Android 系統上的一款神器,它對於任何一個喜歡鼓搗手機的抖 M 來說都是不可或缺的,通過在這個框架上安裝特定的某些模塊,普通用戶都可以很自由很 easy 的 DIY 自己的手機系統,實現許多看起來很複雜很高級的功能。
  • 想讓Android手機開掛,安裝Xposed框架就行了!
    其通過替換Android系統的關鍵文件,可以攔截幾乎所有Java函數的調用,並允許通過模塊擴展方式來實現各種功能,模塊中的自定義代碼可以更改調用函數時的行為,常被用來修改Android系統和應用程式的功能。用戶可以在一些應用商店或其自帶的下載庫來下載安裝各種模塊,相比於重新安裝系統來獲得新功能,Xposed提供了一種更便捷的方式[1][2]。
  • xposed模塊整理
    xposed模塊整理前言:相信高階一點的手機玩家經常聽說這個名字,沒錯,這是一個非常強大的工具,是讓你在不修改官方應用的情況下,利用各種模塊來達到定製軟體的目的
  • KVO實現原理分析
    在開發過程中,很多時候會用到KVO鍵值觀察,它能夠很輕鬆地去監聽某對象屬性的變化,監聽一些帶狀態的控制項的狀態變化,字符串的改變等等,今天就來探討一下KVO的使用及實現原理。舉例:很多情況下我們會藉助KVO,實現對某一對象屬性的觀察。我們不禁感嘆,只有這簡簡單單的幾行代碼,就能對被觀察對象的屬性變化獲得實時通知,這麼強大的功能,底層的實現原理是什麼呢?
  • Chat | 詞法分析與語法分析的原理及部分實現
    自頂向下的代表方法是 LL(1) 分析;自底向上的代表方法是算符優先分析、LR(0) 分析、SLR(1) 分析。對編譯原理有興趣,想要了解的;想要編寫自製編譯器的。了解基本數據結構,例如:棧(因為在語法分析的過程中,使用了分析)。對文法有基本的了解與認識。
  • 利用Xposed對ollvm後的so中flag爆破
    解題思路:首先分析ollvm控制流混淆的so找到關鍵函數,然後分析Android8.1源碼加載so的函數並編寫Xposed插件hook之,最後基於sandhook框架編寫爆破so。 考察知識點:ollvm控制流混淆代碼的分析方法,以及利用Xposed對so中函數的主動調用。1.分析ollvm控制流混淆的so找到關鍵函數首先利用Jadx軟體打開apk,核心代碼如下,將用戶輸入字符串作為參數調用jni函數jnitest進行判斷。
  • 802.1Qbv實現原理分析
    了解Qbv的工作原理對TSN設計和應用十分關鍵。本文基於論文[1]中給出的Qbv實現示例,對Qbv的實現原理進行分析。一、802.1Qbv工作原理示例IEEE802.1Qbv協議定義的時間敏感整形機制(TAS)為時間敏感流量的確定性傳輸提供保證。在圖1所示單跳網絡中包含三臺終端和一臺TSN交換機。
  • 方差分析(ANOVA)原理及其實現
    方差分析的基本原理    在一次實驗中,可以得到一系列不同的觀測值。造成觀測值不同的原因可能是由於處理因素不同引起的,即處理效應;也可能是由於實驗過程中偶然性因素的幹擾和測量誤差所致,即誤差效應。反應測量數據變異性的指標有多個,在方差分析中選用方差來度量資料的變異程度。要正確認識觀測值的便宜是由於處理效應還是誤差效應引起的,我們可以分別計算出處理效應的方差以及誤差小於的方差,在一定顯著水平下進行比較,如果二者相差不大,說明實驗處理對觀測值的影響不大;如果差異較大則說明實驗處理對於觀測的影響較大。    方差分析建立在三個基本假定的基礎上:一.
  • 免ROOT、免刷機使用Xposed模塊,玩機必備!
    可以設置在每次應用構建完成後向通知欄發送信息,也可以設置全模塊禁用以及智能脫殼、反加固功能等(智能脫殼、反加固和免修改構建應用需要下載額外的原生庫文件),此外可以在設置當中打開調試模式、 禁用安全模式,方便開發者分析模塊的工作情況。重新構建後的應用默認會加載本機安裝的所有模塊,集成模塊以後應用不再依賴本機安裝的模塊軟體,可以將安裝包分享他人。
  • Elasticsearch實現原理分析
    本文是分析Elasticsearch系列文章中的一篇,是一個譯文。共有三個部分,每部分講解部分Elasticsearch的實現原理。
  • 方向特效實現原理剖析及動手實現
    偶然機會發現了一個方向特效效果,感覺很不錯,然後就開始想著自己去實現一下,馬不停蹄我就開始分析其原理,並一步步實現效果。如果您對此感興趣,可以關注並私信我獲取源碼。最終效果圖我製作的原理圖三個錨點位置(藍、紅、黃)div動效層的初始位置(初始旋轉角度、初始位置)div動效層旋轉方向和頂角錨點設置