日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Xposed是如何为所欲为的?

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

本文通過分析 Xposed 的實(shí)現(xiàn)原理,對于 Java 方法的 Hook 原理有了一些了解,同時(shí)回顧了 Android zygote 進(jìn)程相關(guān)的內(nèi)容,對于 ART 虛擬機(jī)執(zhí)行方法的過程也有了一個(gè)大概的認(rèn)識(shí)。

Pangu-Immortal (Pangu-Immortal) · GitHub


1、前言

Xposed 是 Android 平臺(tái)上著名的 Java 層 Hook 框架,通過在 Android 設(shè)備上安裝 Xposed 框架,編寫 Xposed 模塊,可實(shí)現(xiàn)對任意 Android 應(yīng)用的 Java 方法的 Hook,以及應(yīng)用資源的替換。

(Hook 是一種函數(shù)鉤子技術(shù),能夠?qū)瘮?shù)進(jìn)行接管,從而修改函數(shù)的返回值,改變函數(shù)的原始意圖)

本文將基于 Xposed 最新的開源代碼對 Xposed 的實(shí)現(xiàn)原理進(jìn)行分析。

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

通過了解 Xposed 的實(shí)現(xiàn)原理可以學(xué)到在 Android 平臺(tái)上對于 Java 層代碼的一種 Hook 機(jī)制的實(shí)現(xiàn),同時(shí)復(fù)習(xí) Android 系統(tǒng)的啟動(dòng)原理以及增加對于 Android ART 虛擬機(jī)運(yùn)行原理的了解。

2、Xposed 使用方法

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

Xposed 的核心用法就是對一個(gè) Java 方法進(jìn)行 Hook,它的典型調(diào)用如下:

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 這個(gè)類的 onCreate 方法進(jìn)行 Hook,并使用 XC_MethodHook 對象提供一個(gè) Hook 處理方法來接管原來方法的邏輯,當(dāng)應(yīng)用的 Application 類型的 onCreate 方法被調(diào)用時(shí),beforeHookedMethod 將在被調(diào)用之前執(zhí)行,同時(shí) onCreate 的參數(shù)將會(huì)傳遞給 beforeHookedMethod 方法進(jìn)行處理,上面的處理只是將參數(shù)打印了出來(一個(gè) Context)。

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

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

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

可以看到,如果要使用 Xposed 對一個(gè) Java 方法進(jìn)行 Hook,需要提供要 Hook 方法的名字、參數(shù)列表類型和方法所在類,以及處理 Hook 的回調(diào)方法。

下面正式開始分析。

3、Xposed 原理概述

首先概述 Xposed 原理,之后再對具體細(xì)節(jié)進(jìn)行分析。

Xposed 是一個(gè) Hook 框架,它提供了對任意 Android 應(yīng)用的 Java 方法進(jìn)行 Hook 的一種方法,通常它的使用方法如下:

  • 首先按照 Xposed 官網(wǎng)提供的開發(fā)規(guī)范編寫一個(gè) Xposed 模塊,它是一個(gè)普通的 Android 應(yīng)用,包含一塊開發(fā)者自己定義的代碼,這塊代碼有能力通過 Xposed 框架提供的 Hook API 對任意應(yīng)用的 Java 方法進(jìn)行 Hook。

  • 在要啟用 Xposed 的 Android 設(shè)備上安裝 Xposed 框架和這個(gè) Xposed 模塊,然后在 Xposed 框架應(yīng)用中啟用這個(gè) Xposed 模塊,重新啟動(dòng)設(shè)備后,Xposed 模塊將被激活,當(dāng)任意的應(yīng)用運(yùn)行起來后,Xposed 模塊的 Hook 代碼將會(huì)在這個(gè)應(yīng)用進(jìn)程中被加載,然后執(zhí)行,從而對這個(gè)應(yīng)用的 Java 方法進(jìn)行指定 Hook 操作。

  • 那么根據(jù)以上使用方法實(shí)現(xiàn)一個(gè) Xposed 框架需要分成如下幾個(gè)部分:

  • 提供用于 Hook 操作的 API,為了讓開發(fā)者進(jìn)行模塊開發(fā)。它通常是一個(gè) jar 包;

  • 提供一個(gè)具有界面的管理器應(yīng)用,用于安裝和管理 Xposed 本身和 Xposed 模塊;

  • 提供將代碼加載到每一個(gè)應(yīng)用進(jìn)程中的能力,目的是支持 Xposed 模塊的代碼在進(jìn)程中使用 Xposed API 進(jìn)行 Hook 操作;

  • 提供 Hook 任意 Java 方法的能力,為 Xposed 模塊的調(diào)用提供支持,當(dāng) Xposed 模塊在應(yīng)用進(jìn)程中執(zhí)行時(shí)可對方法進(jìn)行 Hook。

  • 前兩點(diǎn)對于我們開發(fā)者來說都很熟悉,沒有什么難點(diǎn),后面兩點(diǎn)才是實(shí)現(xiàn) Xposed 的核心。

    首先是 Xposed 怎樣實(shí)現(xiàn)的將代碼加載到每一個(gè)應(yīng)用進(jìn)程中(Xposed 是基于 Root 權(quán)限實(shí)現(xiàn)的,所以有修改 Android 系統(tǒng)的能力)?

    Xposed 是通過修改系統(tǒng) zygote 進(jìn)程的實(shí)現(xiàn)將代碼注入應(yīng)用進(jìn)程中的。

    為了知道 Xposed 是如何修改 Zygote 進(jìn)程的,下面首先介紹 Android 系統(tǒng) Zygote 相關(guān)內(nèi)容。

    4、Android zygote 進(jìn)程

    zygote 進(jìn)程是 Android 系統(tǒng)中第一個(gè)擁有 Java 運(yùn)行環(huán)境的進(jìn)程,它是由用戶空間 1 號(hào)進(jìn)程 init 進(jìn)程通過解析 init.rc 文件創(chuàng)建出來的,從 init 進(jìn)程 fork 而來。

    zygote 進(jìn)程是一個(gè)孵化器。Android 系統(tǒng)中所有運(yùn)行在 Java 虛擬機(jī)中的系統(tǒng)服務(wù)以及應(yīng)用均由 zygote 進(jìn)程孵化而來。

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

    自 Android 5.0 系統(tǒng)開始,zygote 不再是一個(gè)進(jìn)程,而是兩個(gè)進(jìn)程,一個(gè)是 32 位 zygote,負(fù)責(zé)孵化 32 位進(jìn)程(為了兼容使用了 armeabi 和 armeabi-v7a 等 32 位架構(gòu)的本地動(dòng)態(tài)庫的應(yīng)用),另一個(gè)是 64 位 zygote 進(jìn)程,負(fù)責(zé)孵化 64 位應(yīng)用進(jìn)程(可加載 arm64-v8a 等 64 位架構(gòu)本地庫)。

    init 進(jìn)程是 Android 系統(tǒng)中的 pid 為 1 的進(jìn)程,是用戶空間的第一個(gè)進(jìn)程,它會(huì)在 Android 系統(tǒng)啟動(dòng)時(shí)被內(nèi)核創(chuàng)建出來,之后會(huì)對 init.rc 文件進(jìn)行解析,init.rc 文件是一個(gè)按照特定規(guī)則編寫的腳本文件,init 進(jìn)程通過解析它的規(guī)則來創(chuàng)建對應(yīng)的服務(wù)進(jìn)程。下面看一下 zygote 相關(guān)的 rc 文件的內(nèi)容。

    注:自 Android 5.0 開始,32 位 zygote 啟動(dòng)內(nèi)容在 init.zygote32.rc 文件中,64 位 zygote 啟動(dòng)內(nèi)容在 init.zygote64.rc 中。

    注:自 Android 9.0 開始,兩個(gè) zygote 啟動(dòng)配置放在一個(gè)文件中 init.zygote64_32.rc。

    這里看一下 Android 8.1 系統(tǒng)的 32 位 zygote 的 rc 文件內(nèi)容:

    #?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

    上面的含義是,創(chuàng)建一個(gè)名為 zygote 的服務(wù)進(jìn)程,它的可執(zhí)行文件在?/system/bin/app_process 中,后面的?-Xzygote、/system.bin 等是可執(zhí)行文件的 main 函數(shù)將要接收的參數(shù)。

    具體的 init 進(jìn)程和 zygote 進(jìn)程的啟動(dòng)細(xì)節(jié),可以參考之前的文章:

    Android init 進(jìn)程啟動(dòng)分析

    Android zygote 進(jìn)程啟動(dòng)分析

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

    所有 Android 應(yīng)用都是運(yùn)行在 Java 虛擬機(jī)上的,所有的 Android 應(yīng)用都是 zygote 的子進(jìn)程,那么當(dāng) Android 應(yīng)用進(jìn)程啟動(dòng)后,將具備 zygote 進(jìn)程加載的所有資源,從而將 Xposed 代碼繼承到了 Android 應(yīng)用進(jìn)程中,實(shí)現(xiàn)了將 Xposed 代碼加載到每一個(gè)進(jìn)程中的目的。

    接下來是如何實(shí)現(xiàn)對應(yīng)用中 Java 方法的 Hook。Hook 的基本原理如下,將 Java 方法的原始邏輯,轉(zhuǎn)接到一個(gè)中間處理方法上,這個(gè)處理方法會(huì)對原始 Java 方法的參數(shù)進(jìn)行轉(zhuǎn)發(fā),轉(zhuǎn)發(fā)到一個(gè)用于處理 Hook 的方法上(即 XC_MethodHook 的實(shí)現(xiàn)),等處理 Hook 的方法執(zhí)行自定義邏輯后(自定義邏輯可選擇調(diào)用原始邏輯先獲取原始返回值,再處理),再返回新的返回值。

    下面分別是 Xposed 在 Dalvik 虛擬機(jī)和 ART 虛擬機(jī)下的 Hook 實(shí)現(xiàn)。

    5、基于 Dalvik 的方法 Hook

    基于 Dalvik 的 Hook 方案是通過將被 Hook 方法修改為一個(gè) JNI 方法,然后綁定一個(gè) Xposed 自定義處理方法邏輯的函數(shù)上來實(shí)現(xiàn)的。

    當(dāng)需要 Hook 一個(gè)指定方法時(shí),需要提供要 Hook 方法的名字、參數(shù)列表類型和方法所在類型,還要提供一個(gè)用于處理 Hook 的回調(diào),回調(diào)方法用于修改原始方法的邏輯,它可以接收 Hook 方法的參數(shù),然后返回一個(gè)新的返回值。

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

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

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

    那么調(diào)用一個(gè)被 Hook 的方法的過程是:

    當(dāng)一個(gè) Android 應(yīng)用內(nèi)的代碼調(diào)用一個(gè)被 Hook 的方法時(shí),Dalvik 將會(huì)進(jìn)行代碼的解釋執(zhí)行,Java 方法進(jìn)入 Dalvik 虛擬機(jī)中會(huì)被轉(zhuǎn)化為一個(gè) Method 對象,然后虛擬機(jī)判斷這個(gè)方法如果是一個(gè) JNI 方法,就會(huì)直接調(diào)用它綁定的的 nativeFunc 函數(shù),那么就走到了 Xposed 處理 Hook 的函數(shù)中,這個(gè)函數(shù)將這個(gè)被 Hook 方法的參數(shù)進(jìn)行轉(zhuǎn)發(fā),讓 Xposed 模塊提供的處理 Hook 的回調(diào)方法來接管原來的邏輯,獲得新的返回值返回給被 Hook 方法,即可完成整個(gè) Hook 操作。

    6、基于 ART 的方法 Hook

    基于 ART 的 Hook 方案相比 Dalvik 要復(fù)雜一些,需要重新修改編譯 ART 虛擬機(jī)的源碼,重新編譯出 ART 虛擬機(jī)的可執(zhí)行文件 libart.so,替換 Android 系統(tǒng)中的 ART 虛擬機(jī)實(shí)現(xiàn)。

    它的核心原理就是直接修改一個(gè)方法對應(yīng)的匯編代碼的地址,讓方法直接跳轉(zhuǎn)到指定地址執(zhí)行,然后就可以執(zhí)行自定義的邏輯進(jìn)行 Hook 處理了。

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

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

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

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

    那么調(diào)用一個(gè)被 Hook 的方法的過程是:

    當(dāng)一個(gè) Android 應(yīng)用內(nèi)代碼調(diào)用一個(gè)被 Hook 的方法時(shí),ART 將會(huì)對方法代碼進(jìn)行執(zhí)行,首先這個(gè) Java 方法在 ART 虛擬機(jī)中將使用一個(gè) ArtMethod 對象表示,然后進(jìn)入 ART 的 Java 方法執(zhí)行函數(shù)中,會(huì)跳入一段蹦床代碼中進(jìn)行執(zhí)行,這段蹦床代碼又會(huì)跳入這個(gè) ArtMethod 對象設(shè)置的匯編代碼地址處,從而執(zhí)行到 Xposed 用于處理 Hook 的代碼中,之后完成 Hook 邏輯。

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

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

    7、Xposed 工作流程

    為了進(jìn)一步分析 Xposed 的實(shí)現(xiàn)原理,先對 Xposed 的整體工作流程進(jìn)行了解。

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

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

    XposedInstaller 主頁會(huì)有“INSTALL/UPDATE” 的按鈕,點(diǎn)擊將會(huì)出現(xiàn) Install 和 Install via recovery 兩個(gè)選擇,一個(gè)是直接進(jìn)行安裝;另一個(gè)是通過 recovery 進(jìn)行刷入安裝。不管選擇哪個(gè),都會(huì)首先從服務(wù)器下載相同的 xposed 補(bǔ)丁包。

    XposedInstaller 會(huì)根據(jù)系統(tǒng)版本和 CPU 支持架構(gòu)下載對應(yīng)的系統(tǒng)補(bǔ)丁包。

    在 ARM64 架構(gòu) CPU 的 Android 8.1 系統(tǒng)上,補(bǔ)丁包內(nèi)容如下:

    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,文件名包含系統(tǒng)版本、CPU 架構(gòu)和 Xposed 版本信息。

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

    system 目錄為 Xposed 所需的文件,刷入時(shí)將會(huì)復(fù)制到系統(tǒng) system 目錄下,同名文件將進(jìn)行覆蓋,其中 xposed.prop 為 Xposed 的屬性文件,里面會(huì)存放 Xposed 版本相關(guān)信息,如下:

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

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

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

    如果是 Install via recovery,將創(chuàng)建文件?/cache/recovery/command 并寫入指定刷機(jī)包路徑的刷機(jī)命令,然后重啟手機(jī)進(jìn)入 recovery 模式,recovery 模式會(huì)自動(dòng)執(zhí)行 command 文件中的命令將 Xposed 文件刷入,然后正常重啟至系統(tǒng),啟動(dòng)過程和上面一致。

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

    8、Xposed 項(xiàng)目結(jié)構(gòu)

    首先了解 Xposed 開源項(xiàng)目的結(jié)構(gòu),Xposed 包含如下幾個(gè)開源項(xiàng)目:

    Xposed

    https://github.com/rovo89/Xposed

    Xposed Native 層代碼的實(shí)現(xiàn),主要修改了系統(tǒng) app_process 的實(shí)現(xiàn)(即 zygote 服務(wù)進(jìn)程的實(shí)現(xiàn)),為將 Hook 代碼注入每個(gè)應(yīng)用進(jìn)程提供了入口。

    XposedBridge

    https://github.com/rovo89/XposedBridge

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

    XposedInstaller

    https://github.com/rovo89/XposedInstaller

    統(tǒng)一管理 Xposed 框架的 Android 應(yīng)用,也是一個(gè) Xposed 框架安裝器,用于安裝更新 Xposed 框架核心以及作為統(tǒng)一管理 Xposed 模塊安裝的模塊管理器。

    android_art

    https://github.com/rovo89/android_art

    Xposed 修改后的 Android ART 虛擬機(jī)的實(shí)現(xiàn),將編譯出 libart.so 和其依賴庫,替換系統(tǒng)的 ART 虛擬機(jī)實(shí)現(xiàn)。包含方法 Hook 的核心實(shí)現(xiàn)。

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

    XposedTools

    https://github.com/rovo89/XposedTools

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

    目前只分析 Xposed 的實(shí)現(xiàn),不需要對 Xposed 進(jìn)行定制,所以先不關(guān)注 XposedTools 這個(gè)項(xiàng)目。

    9、Xposed 源碼分析

    可以對上面的項(xiàng)目進(jìn)行 clone,然后用 Android Studio 和 VS Code 打開源代碼,方便閱讀。下面進(jìn)入源碼中分析具體實(shí)現(xiàn)。

    Xposed 安裝下載

    首先從 Xposed 的安裝開始分析,這部分代碼的實(shí)現(xiàn)在 XposedInstaller 中。

    在一臺(tái) Root 過的設(shè)備上安裝 XposedInstaller 后打開,點(diǎn)擊主頁的“INSTALL/UPDATE”,會(huì)彈出一個(gè)對話框,選擇“Install”或“Install via recovery”安裝 Xposed 框架,此時(shí)會(huì)首先進(jìn)行框架核心文件的下載,進(jìn)入 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)//?設(shè)置下載?url.setUrl(zip.url).setDestinationFromUrl(DownloadsUtil.DOWNLOAD_FRAMEWORK).setCallback(new?DownloadFinishedCallback()?{@Overridepublic?void?onDownloadFinished(Context?context,?DownloadInfo?info)?{//?下載完成,觸發(fā)回調(diào)LOCAL_ZIP_LOADER.triggerReload(true);callback.run(new?File(info.localFilename));}}).setMimeType(DownloadsUtil.MIME_TYPES.ZIP).setDialog(true).download(); }

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

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

    {"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 的下載信息,每個(gè)下載信息中的 url 為下載地址的模板,versions 為可用的版本,根據(jù)系統(tǒng)信息和 Xposed 版本對 url 模板進(jìn)行填充后組成下載地址。

    回到上面的下載,下載成功后,將進(jìn)入回調(diào)根據(jù)用戶選擇的安裝類型進(jìn)行安裝,看一下回調(diào)的實(shí)現(xiàn):

    //?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?模式進(jìn)行刷入flash(context,?new?FlashRecoveryAuto(file,?type,?title));}}; }?else?if?(action?==?ACTION_SAVE)?{runAfterDownload?=?new?RunnableWithParam<File>()?{@Overridepublic?void?run(File?file)?{//?僅保存saveTo(context,?file);}}; }

    上面兩個(gè)分支分別對應(yīng) Install 和 Install via recovery 兩種安裝方式的實(shí)現(xiàn),flash 方法將會(huì)啟動(dòng)一個(gè)新的負(fù)責(zé)展示安裝執(zhí)行的界面,然后執(zhí)行傳入的 Flashable 對象的 flash 方法,執(zhí)行成功后展示一個(gè)對話框,詢問用戶是否重啟,重啟后將激活 Xposed。

    分別看一下兩種 Flashable 的實(shí)現(xiàn)。

    直接刷入

    //?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?身份執(zhí)行刷入命令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);}//?執(zhí)行?update-binary?文件int?result?=?rootUtil.execute(getShellPath(updateBinaryFile)?+?"?2?1?"?+?getShellPath(mZipPath),?callback);if?(result?!=?FlashCallback.OK)?{triggerError(callback,?result);return;}callback.onDone(); }

    直接刷入會(huì)直接使用 Root 身份執(zhí)行 update-binary 可執(zhí)行文件,其中會(huì)調(diào)用 flash-script.sh 文件,它將壓縮包中的目錄復(fù)制到對應(yīng)的系統(tǒng)目錄中,同名文件進(jìn)行覆蓋,在覆蓋前會(huì)對原始系統(tǒng)文件進(jìn)行備份,例如 libart.so.orig.gz,為了在卸載時(shí)恢復(fù)。

    刷入后正常重啟系統(tǒng),系統(tǒng)在啟動(dòng)時(shí)將會(huì)加載自定義的 app_process 可執(zhí)行文件,啟動(dòng)了帶有 Xposed 框架代碼的定制版 zygote 服務(wù)進(jìn)程,為 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;//?執(zhí)行刷入命令RootUtil?rootUtil?=?new?RootUtil();if?(!rootUtil.startShell(callback))?{return;}callback.onStarted();//?確認(rèn)?/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;}}//?復(fù)制?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;}//?將刷機(jī)命令寫入?/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 模式進(jìn)行刷入就是首先復(fù)制壓縮包到?/cache/recovery/?中,然后向?/cache/recovery/command 文件中寫入一條刷入壓縮包的命令,然后詢問用戶是否重啟至 recovery 模式,當(dāng)系統(tǒng)處于 recovery 模式后將會(huì)自動(dòng)檢測 command 文件是否存在,如果存在將執(zhí)行其中的指令,然后執(zhí)行刷機(jī)包提供的腳本,過程和上面直接刷入一致,首先執(zhí)行 update-binary 可執(zhí)行文件,然后其中會(huì)調(diào)用 flash-script.sh 文件,將刷機(jī)包中的文件進(jìn)行復(fù)制。此時(shí),系統(tǒng)退出 reocvery 正常重啟后將會(huì)加載成功 Xposed。

    這里就分析完了安裝,主要是通過刷入文件將系統(tǒng)關(guān)鍵組件替換為 Xposed 修改過的實(shí)現(xiàn)。

    下面開始分析 Xposed 的啟動(dòng),當(dāng)系統(tǒng)啟動(dòng)后,init 進(jìn)程將會(huì)通過解析 init.rc 文件后執(zhí)行 app_process 創(chuàng)建 zygote 進(jìn)程,此時(shí)就進(jìn)入了 Xposed 重新編譯修改過的 app_process 文件中。

    Xposed 啟動(dòng)

    這部分的實(shí)現(xiàn)代碼在項(xiàng)目 Xposed 中,是使用 C++ 代碼編寫的,如果這些代碼出現(xiàn)崩潰,則會(huì)卡在開機(jī)界面,即 boot loop 情況。

    Xposed 的 app_process 分為 Dalvik 和 ART 兩種實(shí)現(xiàn),這里只關(guān)注 ART 的實(shí)現(xiàn),在 app_main2.cpp 中。

    Native 層

    入口為 main 函數(shù):

    //?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?測試選項(xiàng)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"//?初始化成功則會(huì)?XposedBridge?流程,否則進(jìn)入系統(tǒng)的?ZygoteInit?中runtimeStart(runtime,?isXposedLoaded???XPOSED_CLASS_DOTS_ZYGOTE?:?"com.android.internal.os.ZygoteInit",?args,?zygote);}?else?if?(className)?{//?非?zygote?進(jìn)程流程,用于支持使用命令行啟動(dòng)自定義的類,這里先不關(guān)心這個(gè)流程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 代碼,發(fā)現(xiàn) main 函數(shù)中只有 1 和 2 兩處代碼進(jìn)行了修改,當(dāng) init 進(jìn)程解析 init.rc 文件時(shí),會(huì)啟動(dòng) zygote 進(jìn)程,此時(shí)就進(jìn)入了 app_process 的 main 函數(shù)中,并將 init.rc 中附帶的選項(xiàng)使用 argv 參數(shù)傳遞進(jìn)來。

    先看第一處 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 兩個(gè)參數(shù),不過查看 init.rc 文件中啟動(dòng) zygote 的選項(xiàng)中并沒有這兩項(xiàng):

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

    所以應(yīng)該是用于 Xposed 的測試代碼,那么這里就不再關(guān)心。

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

    首先看 xposed::initialize 函數(shù):

    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//?判斷系統(tǒng)是否處于?minmal?framework?模式,此時(shí)?/data?是?tmpfs?類型,無法加載?Xposedif?(isMinimalFramework())?{ALOGI("Not?loading?Xposed?for?minimal?framework?(encrypted?device)");return?false;}//?保存相關(guān)參數(shù)//?xposed?是一個(gè)用于共享信息的對象,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)?{//?給另一個(gè)架構(gòu)的優(yōu)先執(zhí)行的?zygote?進(jìn)程一些時(shí)間啟動(dòng),從而避免同時(shí)打印日志,造成難以閱讀sleep(10);}//?打印?Xposed?版本和?Device、ROM?等信息,開機(jī)時(shí)可以在?logcat?中看到printRomInfo();if?(startSystemServer)?{//?確保?XposedInstaller?uid?和?gid?存在,即表示安裝了?XposedInstaller//?啟動(dòng)?XposedService?服務(wù)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?加入系統(tǒng)?CLASSPATH?變量,使其代碼中的類可被加載return?addJarToClasspath(); }

    以上代碼主要是保存了 app_process 的啟動(dòng)選項(xiàng),設(shè)置一些 xposed 支持,最后使用?addJarToClasspath?將 XposedBridge.jar 加入系統(tǒng)路徑。

    //?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;} }

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

    現(xiàn)在回到 main 函數(shù)中,下面進(jìn)入 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 }

    其實(shí)就是直接調(diào)用系統(tǒng) AppRuntime 的 start 函數(shù),如果是 Android 5.1.1 之前需要通過通過獲取 AppRuntime::start 函數(shù)符號(hào)句柄的方式調(diào)用,后面一長串字符串是函數(shù)被編譯后的簽名字符串。

    調(diào)用 AppRuntime::start 后,內(nèi)部會(huì)創(chuàng)建 Java 虛擬機(jī),然后執(zhí)行傳入類的 main 函數(shù):

    //?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;//?創(chuàng)建虛擬機(jī)if?(startVm(&mJavaVM,?&env,?zygote)?!=?0)?{return;}onVmCreated(env);//?注冊系統(tǒng)類?JNI?方法if?(startReg(env)?<?0)?{ALOGE("Unable?to?register?all?android?natives\n");return;}//?...//?轉(zhuǎn)換為?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?{//?調(diào)用?XposedBridge.main();env->CallStaticVoidMethod(startClass,?startMeth,?strArray);}}//?... }

    Java 層

    下面就進(jìn)入到了 Java 層的 XposedBridge#main 方法中:

    //?XposedBridge.javaprotected?static?void?main(String[]?args)?{//?Initialize?the?Xposed?framework?and?modulestry?{//?判斷?native?加載成功if?(!hadInitErrors())?{//?Xposed?相關(guān)初始化initXResources();//?SELinux?相關(guān)支持SELinuxHelper.initOnce();SELinuxHelper.initForProcess(null);//?runtime?表示?ART?還是?Dalivkruntime?=?getRuntime();XPOSED_BRIDGE_VERSION?=?getXposedVersion();if?(isZygote)?{//?為資源的?Hook?注冊回調(diào)XposedInit.hookResources();//?為代碼?Hook?注冊回調(diào),將會(huì)調(diào)用每個(gè)?Xposed?模塊的入口XposedInit.initForZygote();}//?加載設(shè)備上的?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;}//?調(diào)用系統(tǒng)正常流程?Java?層if?(isZygote)?{ZygoteInit.main(args);}?else?{RuntimeInit.main(args);} }

    重點(diǎn)關(guān)注 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 系統(tǒng)關(guān)鍵類的流程,為 Xposed 模塊注冊加載代碼包的回調(diào),當(dāng)這些系統(tǒng)流程執(zhí)行時(shí),會(huì)通過 XC_LoadPackage.callAll(lpparm)?通知所有的 Xposed 模塊。

    上面創(chuàng)建了 XC_LoadPackage.LoadPackageParam 的對象,就是為了給 Xposed 模塊的入口進(jìn)行傳遞。

    XposedBridge.sLoadedPackageCallbacks 是 Xposed 模塊回調(diào)的集合,是一個(gè) CopyOnWriteSortedSet<XC_LoadPackage>?類型。

    XC_LoadPackage 有一個(gè) call 方法,用于回調(diào)自己的 handleLoadPackage 方法。

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

    XC_LoadPackage.callAll 將會(huì)調(diào)用每一個(gè) XC_LoadPackage 的 call 方法,從而向 Xposed 模塊傳遞 lpparm 參數(shù)。

    //?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)?{//?加載每個(gè)?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?文件,//?它的內(nèi)容是?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);}//?根據(jù)模塊關(guān)心代碼?Hook?還是資源?Hook?分別處理if?(moduleInstance?instanceof?IXposedHookLoadPackage)//?注冊到?sLoadedPackageCallbacks?中XposedBridge.hookLoadPackage(new?IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage)?moduleInstance));if?(moduleInstance?instanceof?IXposedHookInitPackageResources)//?注冊資源的?Xposed?模塊回調(diào)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 路徑列表,然后通過讀取每一個(gè) Xposed 模塊 APK 包中的 assets/xposed_init 文件獲得 Xposed 模塊的入口類名,最后將這個(gè)類通過 XposedBridge.hookLoadPackage 注冊到前面的 XposedBridge.sLoadedPackageCallbacks 中。

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

    那么當(dāng)前面 Hook 的系統(tǒng)關(guān)鍵類流程被觸發(fā)后,將會(huì)通過 sLoadedPackageCallbacks 回調(diào)每個(gè) Xposed 模塊的入口。

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

    接下來就是 Xposed 的方法 Hook 的實(shí)現(xiàn)代碼分析了。

    Xposed 方法 Hook

    從 XposedHelpers.findAndHookMethod 方法開始,看 Xposed 是如何進(jìn)行 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 獲取一個(gè) 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);} }

    這里也很簡單,使用了緩存保存方法的反射對象,然后繼續(xù)下一步,進(jìn)入 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)?{//?創(chuàng)建?method?與?hook?回調(diào)列表關(guān)聯(lián)的映射表callbacks?=?new?CopyOnWriteSortedSet<>();sHookedMethodCallbacks.put(hookMethod,?callbacks);newMethod?=?true;}}//?添加?hook?回調(diào)到和這個(gè)?method?關(guān)聯(lián)的?hook?回調(diào)列表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?以下的系統(tǒng),java.reflect.Method?類中的成員,//?它是?Dralvik?虛擬機(jī)中這個(gè)?Method?在虛擬機(jī)中的地址。//?Android?5.0?開始正式使用了?ART?虛擬機(jī),所以不存在這個(gè)成員slot?=?getIntField(hookMethod,?"slot");parameterTypes?=?((Method)?hookMethod).getParameterTypes();returnType?=?((Method)?hookMethod).getReturnType();}?else?{//?處理?Constructorslot?=?getIntField(hookMethod,?"slot");parameterTypes?=?((Constructor<?>)?hookMethod).getParameterTypes();returnType?=?null;}//?打包?Hook?回調(diào)相關(guān)信息,準(zhǔn)備進(jìn)入?Native?層AdditionalHookInfo?additionalInfo?=?new?AdditionalHookInfo(callbacks,?parameterTypes,?returnType);//?進(jìn)入?Native?層代碼,傳入?method、class、slot、hook?回調(diào)等信息hookMethodNative(hookMethod,?declaringClass,?slot,?additionalInfo);}return?callback.new?Unhook(hookMethod); }

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

    最后返回一個(gè) 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); }

    就是直接移除與這個(gè) Java 方法相關(guān)的 Hook 處理回調(diào)。

    下面查看 hookMethodNative 函數(shù)的實(shí)現(xiàn),發(fā)現(xiàn)它是一個(gè) JNI 方法:

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

    它的實(shí)現(xiàn)在 libxposed_art.so 中,源代碼在 Xposed 項(xiàng)目中。

    首先需要解決一個(gè)問題,這個(gè)動(dòng)態(tài)庫是什么時(shí)候加載的,它的 JNI 方法和 Java 層是什么時(shí)候關(guān)聯(lián)的?

    它是在 Java 虛擬機(jī)中創(chuàng)建時(shí)加載的,同時(shí)關(guān)聯(lián)的 JNI 方法。

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

    //?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));}//?... }; }

    回顧前面的內(nèi)容,這個(gè)函數(shù)將在 Java 虛擬機(jī)創(chuàng)建后被回調(diào):

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

    進(jìn)入 xposed::onVmCreated 函數(shù):

    //?xposed.cppvoid?onVmCreated(JNIEnv*?env)?{const?char*?xposedLibPath?=?NULL;//?首先確認(rèn)?Xposed?庫的路徑是?ART?還是?Dalvikif?(!determineRuntime(&xposedLibPath))?{ALOGE("Could?not?determine?runtime,?not?loading?Xposed");return;}//?打開?Xposed?動(dòng)態(tài)庫void*?xposedLibHandle?=?dlopen(xposedLibPath,?RTLD_NOW);if?(!xposedLibHandle)?{ALOGE("Could?not?load?libxposed:?%s",?dlerror());return;}dlerror();//?調(diào)用初始化方法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))?{//?調(diào)用綁定的?onVmCreated?回調(diào)函數(shù)xposed->onVmCreated(env);} }

    首先是 determineRuntime 確認(rèn) 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; }

    根據(jù)系統(tǒng)中是否存在 libdvm.so 或 libart.so,確認(rèn)加載支持 ART 還是 Dalvik 版本的 Xposed 庫,在 ART 上加載 libxposed_art.so。

    然后使用 dlopen 加載鏈接了這個(gè)動(dòng)態(tài)庫,那么它的符號(hào)就可以被正常訪問了。

    后面又調(diào)用了 xposedInitLib 函數(shù):

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

    指定了一個(gè) xposed->onVmCreated 為 onVmCreatedCommon,看一下它的實(shí)現(xiàn)。

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

    這里主要關(guān)注 initXposedBridge,它會(huì)進(jìn)行 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?關(guān)聯(lián)的?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 是一個(gè)宏,方便注冊 JNI 方法:

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

    現(xiàn)在回去,對 hookMethodNative 的具體實(shí)現(xiàn)進(jìn)行分析,從這里開始就是真正開始實(shí)現(xiàn)方法 Hook 了。

    由于這里是 Xposed 方法 Hook 的核心實(shí)現(xiàn),所以同時(shí)分析一下基于 Dalvik 的實(shí)現(xiàn)。

    Dalvik Hook 實(shí)現(xiàn)

    首先看一下 libxposed_dalvik.so 中的實(shí)現(xiàn),驗(yàn)證一下本文開頭基于 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))?{//?此方法已經(jīng)被?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);//?設(shè)置?native?函數(shù)的處理函數(shù),那么?Dalvik?解釋執(zhí)行這個(gè)方法時(shí),//?首先判斷會(huì)它是?JNI?方法,然后會(huì)跳轉(zhuǎn)至?nativeFunc?進(jìn)行執(zhí)行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 實(shí)現(xiàn)的方法 Hook 處理比較簡單,就是先將這個(gè) Java 方法修改為 native 方法,然后給它綁定一個(gè) nativeFunc,當(dāng) Java 代碼調(diào)用這個(gè)方法時(shí),由于它是 JNI 方法,虛擬機(jī)就會(huì)跳轉(zhuǎn)到 nativeFunc 進(jìn)行執(zhí)行。

    Dalvik 虛擬機(jī)執(zhí)行 Java 方法的實(shí)現(xiàn)如下:

    //?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?方法,則跳轉(zhuǎn)?nativeFunc?進(jìn)行執(zhí)行(*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; }

    可以看到,如果一個(gè)方法是 JNI 方法,那么 Dalvik 虛擬機(jī)就會(huì)調(diào)用它綁定的 nativeFunc 函數(shù)。

    前面設(shè)置的 hookedMethodCallback 函數(shù)將會(huì)把被調(diào)用的 Java 方法的參數(shù)進(jìn)行轉(zhuǎn)發(fā),最終會(huì)調(diào)用 Java 層 XposedBridge 的 handleHookedMethod 方法進(jìn)行處理,就能夠達(dá)到 Hook 的目的了,至于 hookedMethodCallback 函數(shù)的實(shí)現(xiàn),這里不再詳細(xì)分析,可以自己看一下。Java 層 handleHookedMethod 方法的實(shí)現(xiàn)和 ART 沒有區(qū)別,都是在 XposedBridge.jar 中,在下面 ART 部分中會(huì)進(jìn)行分析。

    下面進(jìn)入 libxposed_art.so 中的 hookMethodNative 函數(shù)實(shí)現(xiàn):

    ART Hook 實(shí)現(xiàn)

    接下來關(guān)注 libxposed_art.so 中的實(shí)現(xiàn)。

    //?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?虛擬機(jī)中表示?Java?方法的?artMethod?對象ArtMethod*?artMethod?=?ArtMethod::FromReflectedMethod(soa,?javaReflectedMethod);//?fHook?這個(gè)方法artMethod->EnableXposedHook(soa,?javaAdditionalInfo); }

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

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

    下面進(jìn)入 ArtMethod 的 EnableXposedHook 函數(shù)中,從這里開始就進(jìn)入 Xposed 修改過的 ART 虛擬機(jī)的項(xiàng)目 android_art 中了。

    //?art_method.ccvoid?ArtMethod::EnableXposedHook(ScopedObjectAccess&?soa,?jobject?additional_info)?{if?(UNLIKELY(IsXposedHookedMethod()))?{//?已被?Hookreturn;}?else?if?(UNLIKELY(IsXposedOriginalMethod()))?{//?是用于備份的?ArtMethod?對象,通常不應(yīng)該走到這ThrowIllegalArgumentException(StringPrintf("Cannot?hook?the?method?backup:?%s",?PrettyMethod(this).c_str()).c_str());return;}//?獲取?ClassLinker,它是鏈接器auto*?cl?=?Runtime::Current()->GetClassLinker();//?獲取線性分配器,用于分配內(nèi)存,類似于?malloc?auto*?linear_alloc?=?cl->GetAllocatorForClassLoader(GetClassLoader());//?創(chuàng)建一個(gè)新的?ArtMethod?對象,用于備份原始方法ArtMethod*?backup_method?=?cl->CreateRuntimeMethod(linear_alloc);//?復(fù)制當(dāng)前?ArtMethod?至?backup_methodbackup_method->CopyFrom(this,?cl->GetImagePointerSize());//?添加?kAccXposedOriginalMethod?標(biāo)記,說明是備份的方法backup_method->SetAccessFlags(backup_method->GetAccessFlags()?|?kAccXposedOriginalMethod);//?創(chuàng)建備份方法對應(yīng)的反射對象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?的回調(diào))裝到?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__);//?清除本方法的調(diào)用者信息cl->InvalidateCallersForMethod(soa.Self(),?this);jit::Jit*?jit?=?art::Runtime::Current()->GetJit();if?(jit?!=?nullptr)?{//?將本方法的?CodeCache?中的內(nèi)容移動(dòng)到備份方法對象中//?CodeCache?就是從?Dex?文件中解析到的類和方法的相關(guān)信息,//?緩存起來,方便直接取用,而不是每次都解析?Dex?文件jit->GetCodeCache()->MoveObsoleteMethod(this,?backup_method);}//?將?hook_info?保存在用于原本用于存儲(chǔ)?JNI?方法的內(nèi)存地址上?SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info),?sizeof(void*));//?設(shè)置此方法對應(yīng)的匯編代碼的地址,一個(gè)?Java?方法經(jīng)過編譯器編譯后會(huì)對應(yīng)一段匯編代碼,//?當(dāng)虛擬機(jī)執(zhí)行這個(gè)?Java?方法時(shí),如果處于?AOT?模式,就會(huì)直接跳轉(zhuǎn)到匯編代碼執(zhí)行機(jī)器指令SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());//?設(shè)置?dex?中此方法的偏移為?0,表示它是?native?或?abstract?方法,沒有具體代碼SetCodeItemOffset(0);//?清除以下標(biāo)志const?uint32_t?kRemoveFlags?=?kAccNative?|?kAccSynchronized?|?kAccAbstract?|?kAccDefault?|?kAccDefaultConflict;//?添加?Xposed?自定義的?kAccXposedHookedMethod?標(biāo)志,用來標(biāo)識(shí)它是被?Hook?的方法//?添加后,IsXposedHookedMethod?函數(shù)就會(huì)返回?trueSetAccessFlags((GetAccessFlags()?&?~kRemoveFlags)?|?kAccXposedHookedMethod);MutexLock?mu(soa.Self(),?*Locks::thread_list_lock_);Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation,?this); }

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

    當(dāng)這個(gè) Java 方法被調(diào)用時(shí),會(huì)跳轉(zhuǎn)到上面設(shè)置的匯編代碼地址處,Xposed 將會(huì)對這個(gè) Java 方法的參數(shù)進(jìn)行轉(zhuǎn)發(fā)等處理,修改方法返回值,實(shí)現(xiàn)最終 Hook。

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

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

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

    它是一個(gè) art_quick_proxy_invoke_handler 函數(shù)的地址,這個(gè)函數(shù)是在其他地方實(shí)現(xiàn)的(有 extern 聲明),經(jīng)過了解,發(fā)現(xiàn)它是由匯編代碼實(shí)現(xiàn)的,有 arm、arm64、mips、mips64、x86、x86_64 這幾個(gè)指令集的實(shí)現(xiàn),這里看一下 arm 上的實(shí)現(xiàn):

    //?quick_entrypoints_arm.S.extern?artQuickProxyInvokeHandler ENTRY?art_quick_proxy_invoke_handlerSETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_R0//?傳遞相關(guān)參數(shù)mov?????r2,?r9?????????????????@?pass?Thread::Currentmov?????r3,?sp?????????????????@?pass?SP//?跳轉(zhuǎn)至?artQuickProxyInvokeHandler?函數(shù)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 跳轉(zhuǎn)至了 artQuickProxyInvokeHandler 函數(shù)中,那么繼續(xù)跟進(jìn)。

    //?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 函數(shù)是用于處理動(dòng)態(tài)代理方法的,不過 Xposed 對這個(gè)方法進(jìn)行了修改,使其能夠處理被 Hook 的方法,重點(diǎn)關(guān)注下面判斷語句中的代碼,如果是被 Xposed Hook 的方法,那么進(jìn)入 InvokeXposedHandleHookedMethod 進(jìn)行處理:

    //?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();//?調(diào)用?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;//?方法的目標(biāo)作用對象?thisinvocation_args[3].l?=?rcvr_jobj;//?參數(shù)保存?zhèn)鹘o方法的參數(shù)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;} }

    這里就調(diào)用到了 Java 層 XposedBridge 的 handleHookedMethod 方法中。

    //?XposedBridge.javaprivate?static?Object?handleHookedMethod(Member?method,?int?originalMethodId,?Object?additionalInfoObj,Object?thisObject,?Object[]?args)?throws?Throwable?{//?取出?Hook?處理回調(diào)等信息AdditionalHookInfo?additionalInfo?=?(AdditionalHookInfo)?additionalInfoObj;if?(disableHooks)?{try?{//?如果關(guān)閉?Hook,那么調(diào)用原始方法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?的回調(diào),則調(diào)用原始方法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?{//?回調(diào)?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?回調(diào)沒有處理,則調(diào)用原始方法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?{//?調(diào)用?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);//?如果有異常,則拋出異常,否則返回處理后的結(jié)果if?(param.hasThrowable())throw?param.getThrowable();elsereturn?param.getResult(); }

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

    //?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//?調(diào)用虛擬機(jī)的執(zhí)行方法調(diào)用原始方法邏輯return?InvokeMethod(soa,?javaMethod,?javaReceiver,?javaArgs); #elsereturn?InvokeMethod(soa,?javaMethod,?javaReceiver,?javaArgs,?true); #endif }

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

    調(diào)用過程

    分析一個(gè) Java 方法的調(diào)用,可以從 AndroidRuntime.start 中開始,Java 虛擬機(jī)執(zhí)行的第一個(gè)類是 ZygoteInit 從此就進(jìn)入了 Java 層,它使用的是 JNIEnv 提供的 CallStaticVoidMethod 方法,看一下它的實(shí)現(xiàn)。

    //?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); }

    調(diào)用了 InvokeWithVarArgs 函數(shù):

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

    繼續(xù)看 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); }

    最終是調(diào)用到了 ArtMethod 的 Invoke 函數(shù):

    //?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); }

    根據(jù) Java 方法類型是非靜態(tài)還是靜態(tài),跳入 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;//?...}}//?進(jìn)入下一步art_quick_invoke_stub_internal(method,?args,?args_size,?self,?result,?result_in_float,core_reg_args,?fp_reg_args); }

    最后是調(diào)用了 art_quick_invoke_stub_internal,它是匯編代碼實(shí)現(xiàn)的:

    //?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 指令設(shè)置 ip 寄存器的位置來指示指令地址,使用到了 ART_METHOD_QUICK_CODE_OFFSET_32 這個(gè)宏,它是 32,表示 EntryPointFromQuickCompiledCodeOffset 這個(gè)函數(shù)返回的成員的偏移,也就是 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 時(shí),使用了 SetEntryPointFromQuickCompiledCode,其實(shí)就是設(shè)置這個(gè)變量。

    //?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 跳轉(zhuǎn)指令,代碼就會(huì)跳轉(zhuǎn)到這個(gè)地址上執(zhí)行,進(jìn)入 GetQuickProxyInvokeHandler 返回的地址 art_quick_proxy_invoke_handler 中, 最后執(zhí)行 artQuickProxyInvokeHandler 函數(shù),Xposed 在這個(gè)函數(shù)里面處理了 Hook,完成 Hook。

    到這里就分析完了 Xposed 的實(shí)現(xiàn),其實(shí)還有很多細(xì)節(jié)沒有去分析,通過對比 ART 虛擬機(jī)的原始代碼和 Xposed 修改后的代碼,發(fā)現(xiàn) Xposed 修改的地方還是很多的,大概有幾百處,雖然每個(gè)文件修改的代碼不多,但是足以說明 Xposed 作者對與于 Android 系統(tǒng)原理和 ART 虛擬機(jī)的了解的深入程度。

    總結(jié)

    通過分析 Xposed 的實(shí)現(xiàn)原理,對于 Java 方法的 Hook 原理有了一些了解,同時(shí)回顧了 Android zygote 進(jìn)程相關(guān)的內(nèi)容,對于 ART 虛擬機(jī)執(zhí)行方法的過程也有了一個(gè)大概的認(rèn)識(shí)。

    參考

    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

    贈(zèng)送源碼:https://github.com/Pangu-Immortal《最完整的Android逆向知識(shí)體系》

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    国产一区二区不卡视频 | 91视频91色| 亚洲国产成人在线播放 | 国产大尺度视频 | 国产成人av综合色 | 激情丁香5月 | 色综合天天天天做夜夜夜夜做 | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 波多野结衣电影一区二区 | 日韩中文字幕在线观看 | 麻花豆传媒mv在线观看网站 | 91亚洲精品久久久 | 亚洲传媒在线 | 麻花豆传媒一二三产区 | 色五月色开心色婷婷色丁香 | 国产精品欧美久久久久天天影视 | 又黄又爽免费视频 | 中文字幕在线看片 | 午夜精品一区二区三区免费 | 欧美一区二区三区在线观看 | 人人草在线观看 | 精品久久久久久综合日本 | 亚洲国产大片 | 国产精彩视频一区 | 免费看国产一级片 | 五月亚洲综合 | 国产群p视频 | 中文字幕中文字幕在线中文字幕三区 | 日韩中文在线字幕 | 欧美专区日韩专区 | 久久论理| 草久久精品 | 免费午夜视频在线观看 | 亚洲欧美视频网站 | 国产成人久久av免费高清密臂 | 亚洲另类视频在线观看 | 999久久国精品免费观看网站 | 精品1区二区 | 欧美日韩三级在线观看 | 久草在线官网 | 午夜国产在线观看 | 99在线视频精品 | 国产日韩av在线 | 色综合亚洲精品激情狠狠 | 精品视频久久 | 91av久久| 超碰午夜 | 国产在线播放观看 | 一级理论片在线观看 | 国内丰满少妇猛烈精品播 | 玖玖爱国产在线 | 久草电影在线 | 欧美成人一区二区 | 亚洲天天摸日日摸天天欢 | 日韩电影在线观看一区 | 91在线播放综合 | 亚洲1区在线| 午夜精品久久久久久99热明星 | 欧美韩国日本在线 | 99视频在线观看一区三区 | 久久99影院 | 国产丝袜美腿在线 | 激情五月婷婷丁香 | 狠狠色丁香婷综合久久 | 4hu视频| 福利视频网址 | 国产成人专区 | 99在线免费观看视频 | 观看免费av | 超碰在线亚洲 | 欧美精品一区二区在线观看 | 久久成年人| 久热只有精品 | 日本一区二区三区免费观看 | 91大神精品视频 | 99综合电影在线视频 | 91精选在线 | 久久精品久久精品久久39 | 麻豆国产网站 | 最新色站| 国产精品久久久久免费 | 97网站| 中文字幕乱码电影 | 成人黄色毛片视频 | 色综合狠狠干 | 92中文资源在线 | 欧美老女人xx | 美女久久网站 | 欧美精品成人在线 | 91福利专区| 午夜三级影院 | 一本一本久久a久久精品牛牛影视 | 久操免费视频 | 二区视频在线 | 欧美xxxxx在线视频 | 午夜黄网 | 在线а√天堂中文官网 | 日日干美女 | 亚洲高清在线 | 日韩黄色一级电影 | 日韩一区在线播放 | 国产69精品久久99的直播节目 | 区一区二在线 | 最新av在线网址 | 麻豆久久一区二区 | 久久免费视频在线观看30 | 亚洲国产成人在线 | 97电影在线| 69视频在线| 国产黑丝一区二区 | 高清av网| 91av福利视频| 国产亚洲免费观看 | 成人av片在线观看 | 夜夜夜夜操 | 亚洲人成综合 | 黄色福利| 日韩欧美视频一区二区 | 久久黄色小说 | 91一区二区在线 | 亚洲精品乱码久久 | 夜色.com | 久久亚洲综合国产精品99麻豆的功能介绍 | 久久国产精品99久久人人澡 | 亚洲视频免费视频 | 色婷婷狠狠操 | 欧美精品一区二区在线播放 | 99九九热只有国产精品 | 亚洲专区一二三 | www免费网站在线观看 | 国产成人精品一区二区三区福利 | 国产 视频 高清 免费 | 国产区第一页 | 91成人免费看 | 天天射天天干天天爽 | 天天做天天爱夜夜爽 | 超碰在线最新地址 | 亚洲精品美女久久 | 成人一级在线观看 | 亚洲一区二区三区精品在线观看 | 日韩欧美91 | 国产亚洲精品久久久久久久久久 | 亚洲精品午夜久久久 | 婷婷丁香激情综合 | 国产 在线观看 | 99久久www| 国产在线永久 | 在线观看视频黄色 | 日韩三级精品 | 六月天色婷婷 | 久久综合久久综合久久综合 | 伊人开心激情 | 欧美日韩一区二区在线 | 一级一片免费看 | 亚洲第一伊人 | 日韩久久久久久久久久 | 亚洲一级性 | 日韩电影在线观看一区二区三区 | 808电影 | 欧美 日韩 国产 中文字幕 | 欧美成人亚洲成人 | 色综合天天综合在线视频 | 国产免费又粗又猛又爽 | 超碰97免费在线 | 国产一线二线三线性视频 | 久久综合色综合88 | 综合久久综合久久 | 中文字幕国语官网在线视频 | 麻豆av一区二区三区在线观看 | 婷婷久久亚洲 | 午夜av电影院 | 天天舔天天射天天操 | 日韩理论影院 | 91精选在线 | 亚洲高清视频在线 | 成年人在线| 国产成人精品亚洲精品 | 99久久久成人国产精品 | 久热香蕉视频 | 最近中文字幕免费视频 | 国产亚洲成av人片在线观看桃 | 国产玖玖在线 | 九九热久久免费视频 | 久久69精品久久久久久久电影好 | 国产麻豆电影在线观看 | 日韩av电影中文字幕在线观看 | 精品亚洲视频在线 | 婷婷网五月天 | 成人一级影视 | 婷婷在线五月 | 999久久a精品合区久久久 | 综合亚洲视频 | www婷婷| 色网址99 | 黄色毛片视频 | 狠狠操欧美 | 黄色一级免费网站 | 午夜精品久久 | 亚洲91视频 | 一二三精品视频 | 亚洲免费av在线 | 中文在线免费看视频 | 日韩欧美综合在线视频 | 日韩在线首页 | 一区三区视频在线观看 | 超碰97人人干 | 久草在线最新免费 | 成年人视频在线观看免费 | 91中文字幕视频 | 精品999在线| 婷婷深爱五月 | 96看片| 国产亚洲精品av | 国产精品久久久久久模特 | 黄色亚洲精品 | 国产精品v欧美精品 | 婷婷激情站 | 日韩电影精品 | 免费看黄网站在线 | 99久久精品网 | 偷拍区另类综合在线 | 五月在线视频 | 在线观看av国产 | 精品视频久久 | 草久久久久久 | 青草草在线 | 黄色a一级片 | 韩日精品中文字幕 | 国内精品久久久久久久久久 | 91色蜜桃| 欧美人人爱 | 欧美日韩色婷婷 | 91麻豆精品国产91久久久久久久久 | www.com黄色 | 欧美性色黄大片在线观看 | 午夜精品福利在线 | 日韩一区二区三区免费电影 | av一区二区三区在线 | 99久久电影| 91成人免费 | 国产日产精品一区二区三区四区的观看方式 | 欧美一级黄色视屏 | 国产老太婆免费交性大片 | 日韩在线视频看看 | 欧美激情操 | 国产视频综合在线 | 国产精品欧美久久 | 久艹视频在线免费观看 | 国产精品中文字幕在线观看 | 美女网站视频免费黄 | 日韩精品高清视频 | 字幕网在线观看 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 婷婷精品进入 | 一区二区精品在线视频 | 成人av一区二区在线观看 | 免费视频久久 | 欧美另类xxxx | 亚洲女欲精品久久久久久久18 | 久草线| 一级片免费观看 | 一区二区视频在线免费观看 | 亚洲精品高清视频 | 亚洲精品高清一区二区三区四区 | 精品久久久免费视频 | 在线不卡中文字幕播放 | 国产欧美三级 | 欧美精品在线免费 | av品善网 | av免费网站 | 青青色影院 | 天天摸天天干天天操天天射 | 日本aa在线 | 久久久久福利视频 | 免费观看一级特黄欧美大片 | 欧美一级片在线免费观看 | 国产视频 亚洲视频 | 91久久精品日日躁夜夜躁国产 | 欧美老少交 | 激情九九| 精品色999| 激情婷婷 | 久久婷婷一区二区三区 | 99热超碰在线 | 亚洲色图美腿丝袜 | 97超碰在线久草超碰在线观看 | 片网址 | 久久久久国产一区二区 | 日韩美精品视频 | 中文字幕一区二区三区在线视频 | 狠狠色丁香婷婷综合欧美 | 日韩羞羞 | 精品视频专区 | 夜夜爽天天爽 | 91丨九色丨国产女 | www.狠狠插.com | 五月天久久婷 | 在线观看av免费 | 精品一区免费 | 国产 日韩 欧美 自拍 | 91丨porny丨九色 | 激情婷婷色 | 日本特黄一级片 | 人人爽人人爽人人爽 | 国产91九色蝌蚪 | 精品国产大片 | 天天天天爱天天躁 | 99久久久久久国产精品 | 99精品在线免费观看 | 免费男女羞羞的视频网站中文字幕 | 成人在线免费小视频 | 丁香婷婷激情 | 天天干天天操天天 | 黄色毛片在线 | av在线观 | 麻豆国产电影 | 久久大香线蕉app | 日韩高清三区 | 国产精品igao视频网网址 | 国产一区高清在线 | 国产精品久久久久久久久久了 | 成人免费大片黄在线播放 | 日韩电影久久 | 国内久久精品 | 天堂中文在线视频 | 精品欧美一区二区在线观看 | 99r精品视频在线观看 | 免费v片 | 黄色www在线观看 | 激情久久综合网 | 久久久国产精品久久久 | 98福利在线 | 丁香av在线 | 热久久国产 | 久久久2o19精品 | 视频一区二区三区视频 | av中文在线播放 | 99精品国产一区二区三区麻豆 | 一级a性色生活片久久毛片波多野 | 国产精品不卡在线 | 欧美精品日韩 | 日批在线看| 精久久久久| 日韩成人高清在线 | 天天射天天做 | 欧美视频不卡 | 国产成人精品电影久久久 | 一区二区三区四区五区在线 | 最新99热 | 国产视频综合在线 | 91完整版在线观看 | 日韩一级电影在线观看 | 欧美大片在线观看一区 | 99re亚洲国产精品 | a特级毛片| 狠狠干2018 | 久久草网| 婷婷综合导航 | 久久精品aaa | 99精品视频网| 中文字幕日韩国产 | 久久久www成人免费精品 | 五月婷婷狠狠 | av黄网站 | 99视频99| 在线精品播放 | 中文字幕在线看视频国产 | 免费的国产精品 | www色综合| 91精品欧美一区二区三区 | 欧美视屏一区二区 | 一区二区三区免费 | 日韩区欧美久久久无人区 | 99精品免费久久久久久久久日本 | 欧美日韩在线免费观看视频 | 久久亚洲影院 | 91在线你懂的 | 国产精品久久久av久久久 | 成人 亚洲 欧美 | 色在线高清| 成年免费在线视频 | 国产精品精品国产 | www.狠狠色.com | 久久久久久久久久久国产精品 | 日韩在线中文字幕视频 | www.操.com| 特级大胆西西4444www | 毛片在线网 | 免费看的黄色录像 | 中文字幕在线不卡国产视频 | 中文字幕在线不卡国产视频 | 99久视频| 国产精品观看视频 | 综合在线色| 国产欧美日韩一区 | 亚洲国产成人精品久久 | 97色国产 | 五月婷婷激情综合网 | 91喷水| 波多野结衣视频在线 | 在线激情影院一区 | 亚洲在线| 日韩高清不卡在线 | 国产五月色婷婷六月丁香视频 | 日韩在线视频网站 | 久久69精品久久久久久久电影好 | av线上免费观看 | 波多野结衣一区二区三区中文字幕 | 欧美一区二区在线看 | 伊人天天综合 | 国产91精品久久久久久 | 色吊丝在线永久观看最新版本 | 色多多在线观看 | 色黄视频免费观看 | 激情www| 国产成人精品三级 | 在线观看一级 | 国产 日韩 欧美 自拍 | 久久久黄色免费网站 | 九九在线精品视频 | 9999毛片 | 日本丰满少妇免费一区 | 9999精品免费视频 | 日日日天天天 | 精品a在线 | 免费看的黄网站软件 | 99色资源 | 国产一区二区三区免费在线 | 亚洲人人射 | 99热精品在线观看 | 色人久久| 九九九电影免费看 | 丁香网婷婷 | 国产在线最新 | 99热在线国产 | 国产在线播放一区二区 | 亚洲成人网av| 最新国产精品久久精品 | 色噜噜狠狠色综合中国 | 国产成人一区在线 | 亚洲综合日韩在线 | 狠狠色狠狠色综合系列 | 99在线精品视频观看 | 国产在线精品一区二区 | 日韩精品一区二区三区三炮视频 | 超碰97公开 | 久久小视频 | 天天爽天天碰狠狠添 | 97精品国产97久久久久久 | 在线免费观看视频 | 日韩中文字 | 在线视频第一页 | 婷婷视频 | 在线观看av大片 | 国产一区视频在线 | 免费观看完整版无人区 | 视频在线99| 国产精品自产拍在线观看中文 | 成片视频免费观看 | 国产色视频网站2 | 久久免费视频一区 | 欧美亚洲国产日韩 | 国产九色在线播放九色 | 婷婷九月激情 | 丁香六月婷婷开心婷婷网 | 免费在线a | 毛片美女网站 | 亚洲无在线| 午夜精品久久一牛影视 | www.夜夜 | 国产精品女人网站 | 人人草在线观看 | av免费在线网站 | 免费三级av | 国产黄色在线网站 | 日韩免费久久 | 亚洲欧洲成人精品av97 | 国产字幕在线播放 | 操操操日日 | 久久99九九99精品 | 美女黄频视频大全 | 美女视频国产 | 成年人在线观看视频免费 | 亚洲成人av影片 | 天天爽天天摸 | 黄视频色网站 | 手机av网站 | 国产精品久久久久高潮 | 成人在线观看资源 | 九九九视频在线 | 欧美另类z0zx | 久久精品在线免费观看 | 国产剧情在线一区 | 国产在线观看免费 | www.夜夜操.com | 日本韩国中文字幕 | 久久不卡免费视频 | 麻豆精品传媒视频 | 国产特级毛片aaaaaaa高清 | 蜜桃视频成人在线观看 | 综合久久久久久久 | 日韩精品首页 | 韩国av永久免费 | 日韩在线视频线视频免费网站 | 欧美精品黑人性xxxx | 亚洲国产精品成人综合 | 欧美日韩调教 | 亚洲成人动漫在线观看 | 日日夜夜狠狠 | 亚洲国产中文字幕在线观看 | 成人黄色av免费在线观看 | 96亚洲精品久久久蜜桃 | 欧美另类亚洲 | 99精品欧美一区二区蜜桃免费 | av性在线| 国产 日韩 在线 亚洲 字幕 中文 | 在线一级片 | 成人精品福利 | www天天干 | 一区在线电影 | www.玖玖玖 | 九九久| 国产日产精品一区二区三区四区 | 日日操天天射 | 日韩精品免费在线观看 | 欧美a级成人淫片免费看 | 99免费精品视频 | 最近中文字幕在线 | 色五月色开心色婷婷色丁香 | 黄色不卡av | 久久男人视频 | 久久国产精品影片 | 色姑娘综合天天 | 高清中文字幕av | 91 在线视频 | 日韩一区正在播放 | 99热都是精品 | 久草网视频在线观看 | 久久久久久中文字幕 | 一级欧美黄| 亚洲情感电影大片 | 免费麻豆 | 天天射天天 | 亚洲高清精品在线 | 久久久久久久久久久精 | 热久久影视 | 日本精品免费看 | 97在线成人| 欧美狠狠操 | 激情图片区 | 精精国产xxxx视频在线播放 | 狠狠干电影| 亚洲成人精品av | 亚洲2019精品 | 国内精品久久久久久久影视麻豆 | 91视频电影 | 天天操人人要 | 精品人妖videos欧美人妖 | 久久一视频 | 日韩欧美视频一区二区 | 91视视频在线直接观看在线看网页在线看 | 欧美日比视频 | 久久免费观看少妇a级毛片 久久久久成人免费 | 天天射色综合 | 国产三级香港三韩国三级 | 久久只有精品 | 黄色特级一级片 | 日韩精品免费一区 | 日韩黄色一级电影 | 日韩精品1区2区 | 日韩欧在线| 黄污在线观看 | 精品国产欧美一区二区三区不卡 | 日韩在线| av播放在线 | 在线观看日韩中文字幕 | 免费看麻豆 | 久久久国产一区二区 | 天天·日日日干 | 91伊人久久大香线蕉蜜芽人口 | 亚洲精品久久久久久久蜜桃 | 91久久人澡人人添人人爽欧美 | 欧美aa级 | 国产成人久久精品亚洲 | 亚洲精品日韩在线观看 | 久久精品成人欧美大片古装 | 久久精品一区二区三区四区 | 福利视频| 日韩亚洲欧美中文字幕 | 我爱av激情网 | 久久久国产精品麻豆 | 狠狠插狠狠操 | 久久久久国产免费免费 | 中文不卡视频 | 青青草国产成人99久久 | 国产成人在线免费观看 | 日韩欧美在线中文字幕 | 五月婷色| 国产精品乱码久久久久 | 亚洲视频axxx | 人人插人人射 | 成人av一级片 | 亚洲成av人影院 | 亚洲综合涩| 国产精品第三页 | 日韩在线观看小视频 | 日本在线观看中文字幕无线观看 | 国产精品精品久久久久久 | 日本高清免费中文字幕 | 国产亚洲欧洲 | 久久精品综合 | 一本一本久久a久久精品综合小说 | 亚洲成人资源在线观看 | 久久国产精品影视 | 国产成人久久 | 91最新网址在线观看 | 久久99热国产 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 亚洲涩涩一区 | 五月婷婷综合在线观看 | 国产精品99免费看 | 国产亚洲一区二区在线观看 | 国产精品久久久久久久毛片 | 久久男人中文字幕资源站 | 最近更新中文字幕 | 欧美在线18| 99免费精品 | 亚洲精品国产精品99久久 | 欧美视频99 | 亚洲影院天堂 | 久久久国际精品 | 久久久资源网 | 国产一级精品绿帽视频 | 国产一区二区不卡在线 | 国产在线观看二区 | 中字幕视频在线永久在线观看免费 | 最近最新中文字幕视频 | 精品视频在线观看 | 亚洲欧美在线视频免费 | 超碰九九 | 天天干天天想 | 欧美在线视频一区二区三区 | 久久久国产一区 | 欧美国产不卡 | 精品国产1区二区 | 精品国模一区二区三区 | 一二区av | 欧美精品乱码久久久久久 | 国产精品18久久久久白浆 | 亚洲精品乱码久久久久久写真 | 二区三区中文字幕 | 91精品对白一区国产伦 | 日韩欧美xxxx | 欧美精品中文字幕亚洲专区 | 黄色av一区二区 | 亚洲欧美日韩国产一区二区 | 93久久精品日日躁夜夜躁欧美 | 国内精品视频在线 | 亚洲精品综合一二三区在线观看 | av久久在线 | 欧美精品v国产精品v日韩精品 | 欧美国产日韩激情 | 久久视频免费 | 日韩手机视频 | 成年人免费看 | 成人国产精品免费 | 国产中文字幕视频在线观看 | 日韩理论片中文字幕 | 九九免费精品视频在线观看 | 一区二区伦理 | 久久毛片高清国产 | 日本免费久久高清视频 | 成人免费视频在线观看 | 国产xxxxx在线观看 | 久久精品日韩 | 亚洲欧美国产精品va在线观看 | 日韩av网站在线播放 | 91麻豆精品91久久久久同性 | 亚洲精品久久视频 | 911国产在线观看 | www.伊人网| 少妇性色午夜淫片aaaze | 日韩v欧美v日本v亚洲v国产v | 一区二区三区免费在线观看视频 | 久久99免费 | 日韩精品一区二区三区高清免费 | 精品高清美女精品国产区 | 久久久精品 | 日韩在线三级 | 999ZYZ玖玖资源站永久 | 国产成人精品亚洲a | 欧美日韩一区二区视频在线观看 | 国产 日韩 欧美 中文 在线播放 | 麻豆精品视频 | 九九久久婷婷 | a天堂中文在线 | 国产美女免费观看 | 日本在线观看一区 | 中文字幕视频三区 | 91精彩视频在线观看 | 91成人精品一区在线播放 | 国产欧美久久久精品影院 | 在线观看视频黄 | 婷婷五天天在线视频 | 国产高清在线免费 | 免费观看av网站 | 国产精品21区 | 久久久99精品免费观看乱色 | www.com久久久 | 亚洲精品中文字幕视频 | 在线观看你懂的网站 | 嫩草伊人久久精品少妇av | 91在线观 | 五月婷婷激情综合 | 久久伊人八月婷婷综合激情 | 久久久久观看 | 国产专区视频在线 | 久久视频热| 免费特级黄毛片 | 不卡中文字幕在线 | 欧美作爱视频 | 人人澡人人爱 | 依人成人综合网 | 日日久视频 | 911av视频| www.天天射 | 91日韩免费| 欧美日韩视频在线观看一区二区 | av成人免费网站 | 美女在线免费观看视频 | 国产99久久九九精品免费 | 午夜精品区| 男女视频国产 | 在线播放精品一区二区三区 | 日韩一区二区三区在线看 | 国产成人久久 | 特级西西人体444是什么意思 | 在线观看免费视频你懂的 | 精品视频区 | 国产精品一级在线 | 亚洲 成人 欧美 | 久久丁香网| 国产精品久久久久久久久岛 | 在线看的av网站 | 蜜桃麻豆www久久囤产精品 | 美女久久久久久久 | 国产精品美女久久久久久久网站 | 伊人手机在线 | 欧美一级片免费在线观看 | 热re99久久精品国产99热 | 亚洲成av人影院 | 91视频久久 | 天天射,天天干 | 欧美激情在线看 | 国模一二三区 | 久久激情综合网 | 亚洲精品综合一区二区 | 国产精品久久一区二区三区不卡 | 人人澡人人澡人人 | 91av片 | 国产麻豆剧果冻传媒视频播放量 | 日韩精品欧美精品 | 成人高清在线观看 | 黄色在线观看www | 日韩免费在线播放 | 婷婷色狠狠 | 免费看一级片 | a级片韩国 | 一区二区三区四区不卡 | 婷婷伊人网 | 国产在线高清视频 | 日韩久久网站 | 综合国产在线 | av中文在线播放 | 97国产视频 | 精品国精品自拍自在线 | 97超碰中文 | 久久久精品欧美一区二区免费 | 黄色影院在线免费观看 | 97精产国品一二三产区在线 | 黄色高清视频在线观看 | 天天操天天摸天天射 | 久要激情网 | 亚洲天天在线日亚洲洲精 | 日韩精品视频一二三 | 国产 一区二区三区 在线 | 国产伦精品一区二区三区照片91 | 97色涩 | 精品国产电影一区 | 五月天,com | 天堂av官网 | 久久久久免费网 | 国产色婷婷精品综合在线手机播放 | 91av视频观看 | 亚洲色图 校园春色 | 97在线免费观看视频 | 亚洲美女精品区人人人人 | 国产精品一区二区三区免费看 | 日本中文一级片 | avcom在线 | 婷婷在线精品视频 | 香蕉手机在线 | 激情综合六月 | 操久久网| 狠狠狠色丁香综合久久天下网 | 国产成人在线观看免费 | 亚洲精品1区2区3区 超碰成人网 | 中国成人一区 | 欧美日韩大片在线观看 | 国产一区二区三区免费在线观看 | 玖玖玖影院 | 天天天天天天天操 | 久久午夜免费视频 | 在线 影视 一区 | 久久久精品综合 | 欧美在线aaa | 欧美91成人网 | 日韩视频中文字幕在线观看 | 久久电影网站中文字幕 | 色综合综合 | 国产成视频在线观看 | 国产精品网站一区二区三区 | 97看片网| 欧美欧美 | 日韩精品短视频 | 免费a级毛片在线看 | 欧美成人91 | 亚洲国产wwwccc36天堂 | www.亚洲精品 | 久久看片网站 | 97av视频| 99人久久精品视频最新地址 | 91在线看黄 | 午夜视频在线观看一区二区三区 | 黄色三级网站 | 在线天堂中文在线资源网 | 日韩中文字幕国产精品 | 成人中文字幕在线观看 | avhd高清在线谜片 | 亚洲国产合集 | 国产视频精品免费 | 99欧美| 日韩天堂在线观看 | 久久久免费精品国产一区二区 | 久久久福利视频 | 国产精品久久久久四虎 | www.夜夜骑.com | 日韩精品一区二区三区在线视频 | 亚洲视频专区在线 | 久久久精品成人 | 最近中文字幕完整高清 | 美女视频黄网站 | 91在线免费播放视频 | 1024手机在线看 | 88av视频 | 日本一区二区不卡高清 | 精品国偷自产在线 | 激情五月婷婷综合网 | 天天射综合网站 | 黄色电影小说 | 天堂av色婷婷一区二区三区 | 成人av网址大全 | 在线精品亚洲 | 欧美日韩国产mv | 国产又粗又猛又黄又爽 | 久久久久久久久久久国产精品 | 2023av在线 | 伊甸园av在线 | 欧美日韩精品国产 | 国产免费黄视频在线观看 | 亚洲伦理中文字幕 | 香蕉视频最新网址 | 免费观看一区二区三区视频 | 最新一区二区三区 | 91成人免费观看视频 | 亚洲日韩中文字幕在线播放 | 成人一级免费视频 | 欧美美女激情18p | 欧美日韩免费观看一区=区三区 | 色的网站在线观看 | 99精品在线视频观看 | 97伊人网| 亚洲精品88欧美一区二区 | 日韩三级在线观看 | 五月婷婷综合在线观看 | 99视频精品视频高清免费 | 日日夜夜精品网站 | 五月婷婷视频 | 日韩av电影免费观看 | 日韩成人精品一区二区 | 久久精品电影网 | 亚洲精品国产欧美在线观看 | 视频99爱| 日本激情视频中文字幕 | 免费在线观看av网址 | 亚洲狠狠婷婷综合久久久 | 在线电影日韩 | 99精品国产一区二区三区麻豆 | 国产码电影 | 中文字幕在线播放一区二区 | 深夜免费小视频 | 97在线观看免费高清完整版在线观看 | 在线观看精品国产 | 99国产精品一区 | 久久精品电影网 | 国产在线播放不卡 | 国产午夜精品久久 | 99久久久久国产精品免费 | 高清在线观看av | 免费精品视频在线 | 亚洲专区路线二 | 免费看国产曰批40分钟 | zzijzzij亚洲日本少妇熟睡 | 有码中文字幕在线观看 | 另类五月激情 | 天天拍夜夜拍 | aaa毛片视频 | 看全黄大色黄大片 | 亚洲国产成人精品电影在线观看 | 91大神电影 | 久久私人影院 | 四虎成人精品永久免费av | 久久精品在线 | 国产xvideos免费视频播放 | 国产精品6999成人免费视频 | 亚洲精品久久久久久久不卡四虎 | 婷婷丁香六月 | 国产精品96久久久久久吹潮 | 国产精品久久久久永久免费观看 | 久久只精品99品免费久23小说 | 亚洲视频aaa | 国产视频2区 | 国产色 在线 | 天天草天天操 | 国产福利一区二区在线 | 国产精品6999成人免费视频 | 丁香网五月天 | 亚洲一区二区三区四区精品 | 国产成人精品免费在线观看 | 免费久久99精品国产 | 免费在线观看午夜视频 | 天天综合亚洲 | 久久国产精品色婷婷 | 99久久婷婷国产综合精品 | 免费激情网 | 国产高清不卡一区二区三区 | 久久人视频| 欧美动漫一区二区三区 | 日韩手机视频 | av在线等| 久久久久| 欧美污在线观看 | 91av99| 国产精品一区免费观看 | 草久热 | 成人国产精品一区二区 | 天海翼一区二区三区免费 | 日韩av资源站 | 99热这里有 | 成人在线一区二区 | 最近的中文字幕大全免费版 | 性色av免费看 | 中文字幕制服丝袜av久久 | 深爱激情综合网 | 99精品久久久久久久久久综合 | 国产精彩在线视频 | 爱爱一区 | 麻豆视频免费播放 | 国产精品久一 | 999视频在线播放 | 国产伦理一区 | 麻豆视频91 | 国产手机视频在线观看 | 亚洲美女免费精品视频在线观看 | 在线播放日韩 | 欧美日韩视频在线 | 国产无吗一区二区三区在线欢 | 97精品免费视频 | 免费日韩视频 | 天天草网站 | 日本精品一二区 | 日韩丝袜 | 亚洲二区精品 | 国产99一区 | 国产欧美日韩精品一区二区免费 | 日韩av中文在线观看 | 日本一区二区高清不卡 | 日韩美精品视频 | 99久久精品国产毛片 | 色婷婷综合久久久久中文字幕1 | 91成人精品国产刺激国语对白 | 欧美成人免费在线 | 亚洲精品理论片 | 在线视频精品播放 | 欧美日韩国产一区二区在线观看 | 久久草草热国产精品直播 | 亚洲视频专区在线 | 色婷婷国产在线 |