萬物皆可Hook!重新撿起Hook神器-Xposed框架

  • 2019 年 11 月 5 日
  • 筆記

作者: Lateautumn4lin

來源:雲爬蟲技術研究筆記

首先注意!!這個Hook不是鄧紫棋要給你唱的Hook哦!

而是在程序界流傳的強大秘技-Hook函數,Hook原意是指鉤子,它表示的就是在某個函數的上下文做自定義的處理來實現我們想要的黑科技。 在很多技術領域都存在的這種Hook技術,比如下面這些:

  • PythonWeb框架中,如DjangoFlask都存在這種Hook技術,可以在請求的上下文應用的上下文做自定義操作。
  • Scrapy框架中,可以自定義MiddlerWare,在請求解析的時候做自定操作。
  • K8S編排框架中,我們也可以在執行某些函數的上下文中插入Hook函數,這也是和Web框架同理

而今天我們講解的是關於AndroidHook技術,而有一款神器能夠幫助我們快速地開發Hook模塊,也就是Xposed框架。

其實網上的關於Xposed模塊編寫的教程可謂是一抓一大把。但由於時間的推移,很多工具和方法都發生了變化(如Eclipse退出安卓編程舞台,AndroidStudio 不斷升級導致其一些設置也隨之變化等)也正因此,網上的教程往往有一些時限性,比如現如今 provide 這個關鍵字已經被捨棄了卻仍有人在用,還有些說要把jar包放到lib文件夾而非libs文件夾……種種錯誤或者落伍的教程對新手產生了很大的誤導。

之前也搞過一陣子Xposed框架,而今天在重新部署環境的時候參考某些教程的時候也遇到了很多的坑,所以想重新結合最新的配套工具寫個小教程,主要講解的以下兩個方面:

  • Xposed框架介紹以及原理
  • Xposed框架實戰

Xposed框架介紹以及原理

XposedGithubrovo89大佬設計的一個針對Android平台的動態劫持項目,通過替換/system/bin/app_process程序控制Zygote進程,使得app_process在啟動過程中會加載XposedBridge.jar這個jar包,從而完成對Zygote進程及其創建的Dalvik虛擬機的劫持。

因為Xposed工作原理是在/system/bin目錄下替換文件,在install的時候需要root權限,但是運行時不需要root權限。

看到這裡很多人會很懵,什麼是Zygote?簡單來說在Android系統中,應用程序進程都是由Zygote進程孵化出來的,而Zygote進程是由Init進程啟動的。Zygote進程在啟動時會創建一個Dalvik虛擬機實例,每當它孵化一個新的應用程序進程時,都會將這個Dalvik虛擬機實例複製到新的應用程序進程裏面去,而一個應用程序進程被Zygote進程孵化出來的時候,不僅會獲得Zygote進程中的Dalvik虛擬機實例拷貝,還會與Zygote一起共享Java運行時庫。這也就是可以將XposedBridge這個jar包加載到每一個Android應用程序中的原因。XposedBridge有一個私有的Native(JNI)方法hookMethodNative,這個方法也在app_process中使用。這個函數提供一個方法對象利用JavaReflection機制來對內置方法覆寫。。。。等等這些都會借鑒各路大神的思路和分析,總而言之,就是從底層替換方法,可以讓我們在不修改APK源碼的情況下,通過自己編寫的模塊來影響程序運行的框架服務,實現類似於自動搶紅包、微信消息自動回復等功能。

其實,從本質上來講,Xposed模塊也是一個Android程序。但與普通程序不同的是,想要讓寫出的Android程序成為一個“Xposed 模塊,要額外多完成以下四個硬性任務:

硬性任務清單

1、讓手機上的xposed框架知道我們安裝的這個程序是個xposed模塊。

2、模塊里要包含有xposed的API的jar包,以實現下一步的hook操作

3、這個模塊裏面要有對目標程序進行hook操作的方法。

4、要讓手機上的xposed框架知道,我們編寫的xposed模塊中,哪一個方法是實現hook操作的。

這就引出我即將要介紹的四大件(與前四步一一對照):

四大件

1、AndroidManifest.xml

2、XposedBridgeApi-xx.jar 與 build.gradle

3、實現hook操作的具體代碼

4、xposed_Init

牢記以上四大件,按照順序一個一個實現,就能完成我們的第一個Xposed模塊編寫。以上的原理我們大致就介紹這麼多,下面我們實戰開始吧!

Xposed框架實戰

1. 邁開第一步,新建項目並編輯AndroidManifest.xml

我們使用的IDEAndroid Studio,首先打開AndroidStudio(以版本3.4.2為例,還在用老版本的請升級),建立一個工程,提示我們選擇「Activity」,那就選一個Empty Activity吧。(這個是模塊的界面,隨意選擇即可)。

2、快速運行模板的Xposed模塊

我們可以把項目查看方式設置為Project模式,以方便查看。然後在 「項目名稱/app/src/main/」目錄下找到AndroidManifest.xml,打開這個文件,並在指定位置插入以下三段代碼:

<meta-data            android:name="xposedmodule"              android:value="true" />          <meta-data              android:name="xposeddescription"              android:value="微信hook" />          <meta-data              android:name="xposedminversion"              android:value="53" />

效果如圖:

插入代碼之後,我們可以點擊Run運行App

不過,此時會出現如圖提示,也就是缺少Device設備來運行這個App

下一步我們要把手機連接Android Studio,連接的辦法很多,包括通過USB連接(物理連接)Wifi連接(也就是網絡連接),我們為了節省方法,就採用物理連接,Ps: 有關於遠程連接可以參考這篇文章,連接好我們的實體機之後我們點擊這裡

我們等待Android Studio連接手機,連接好我們就可以看到在Logcat選項裏面看到我們的手機運行的日誌報告。

有如圖所示的日誌打印之後我們就會發現我們就可以運行了,點擊Run之後會提示我們的手機安裝我們剛才剛寫的Apk,不過我的手機提示安裝時驗證超時,不能直接安裝,苦惱,以後選手機也要選個正常的。關於Android Studio安裝Apk失敗的原因可以參考這篇文章,既然我們不能直接安裝Apk,我們就使用adb直接來安裝

安裝好應用之後我們在Xposed框架中勾選我們剛才的模塊,然後我們重啟一下Xposed框架,就可以啦

這一步只是說明Xposed框架已經認出了我們寫的程序。但先別高興太早——雖然框架已經覺得他是一個Xposed模塊了,但我們自己心裏清楚,這個模塊還啥都不會幹呢。下一步,我們讓這個模塊長點本事。

3、搞定XposedBridgeApi-xx.jar 與 build.gradle

我們知道,Xposed模塊主要功能是用來Hook其他程序的各種函數。但是,如何讓前一步中的那個「一窮二白」的模塊長本事呢?那就要引入 XposedBridgeApi.jar 這個包,你可以理解為一把兵器,模塊有了這把寶刀才能施展出Hook本領。很多以前的老教程都需要手動下載諸如XposedBridgeApi-54.jarXposedBridgeApi-82.jarjar包,然後手工導入到libs目錄里,才能走下一步道路,而這些jar沒有官方的渠道來安裝,通常只是一個傳一個的,都不知道變成了什麼版本。其實在最新的AndroidStudio 3.1以後,我們完全不用這麼麻煩,只需要多寫一行代碼,就讓AndroidStuido自動給我們配置XposedBridgeApi.jar!下面操作開始:

「項目名稱/app/src/main/」目錄下找到build.gradle,在圖示位置加上:

repositories {        jcenter()    }  compileOnly 'de.robv.android.xposed:api:82'    compileOnly 'de.robv.android.xposed:api:82:sources'

這句代碼是告訴AndroidStuido使用jcenter作為代碼倉庫,從這個倉庫里遠程尋找 de.robv.android.xposed:api:82這個API。這個網上很少有Xposed教程介紹它的!(我們不用自己找XposedBridgeApi.jar了。注意!此處要用compileOnly這個修飾符!網上有些寫的是provide,但是現在已經停用了!坑人啊!)

寫完之後, build.gradle會提示文件已經修改,是否同步。點擊 「sync now」,同步即可:

等待依賴構建完成

【Ps:如果網絡不通,或者同步不暢,就不要進行第三步的repositories { jcenter()}這個步驟了,改做這個步驟:手動下載XposedBridgeApi-82.jar,拖放到「項目名稱/app/libs/」裏面(不是網上說的單獨建立lib文件夾,那是很久以前的故事了!),然後右鍵「Add As Library」自行添加這個jar包。而compileOnly 『de.robv.android.xposed:api:82′compileOnly 『de.robv.android.xposed:api:82:sources』這兩句仍然照常添加。】

好了,我們現在已經搞好了所有的準備工作。下一步,就要開始「施展刀法」(編寫hook代碼)了。

4、實現hook操作的具體代碼

在「施展刀法」(編寫hook代碼)之前,我們先要立一個靶子。在界面上畫一個按鈕,並在MainAcitiviy里寫代碼如下:

package com.example.wx;    import android.support.v7.app.AppCompatActivity;    import android.os.Bundle;    import android.view.View;    import android.widget.Button;    import android.widget.Toast;    public class MainActivity extends AppCompatActivity {        private Button button;        @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            setContentView(R.layout.activity_main);            button = (Button) findViewById(R.id.button);            button.setOnClickListener(new View.OnClickListener() {                public void onClick(View v) {                    Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show();                }            });        }        public String toastMessage() {            return "我未被劫持";        }    }

而在頁面布置的文件中,也就是activity_main.xml中增加如下紅框的代碼

這個靶子很簡單:MainActivity界面有個按鈕,點擊按鈕後會彈出一個toast提示,該提示的內容由toastMessage()方法提供,而toastMessage()的返回值為「我未被劫持」

現在,我們已經做好了我們的App了,下面我們正式開始「施展刀法」(編寫hook代碼) 來hook我們的MainActivity並修改這個類的toastMessage()方法,讓它的返回值為「你已被劫持」

5、在MainActivity的同級路徑下新建一個類「HookTest.java」

package com.example.wx;    import de.robv.android.xposed.IXposedHookLoadPackage;    import de.robv.android.xposed.XC_MethodHook;    import de.robv.android.xposed.XposedBridge;    import de.robv.android.xposed.XposedHelpers;    import de.robv.android.xposed.callbacks.XC_LoadPackage;    public class HookTest implements IXposedHookLoadPackage {        public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {            if (loadPackageParam.packageName.equals("com.example.root.xposd_hook_new")) {                XposedBridge.log(" has Hooked!");                Class clazz = loadPackageParam.classLoader.loadClass(                        "com.example.root.xposd_hook_new.MainActivity");                XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() {                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {                        super.beforeHookedMethod(param);                        //XposedBridge.log(" has Hooked!");                    }                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {                        param.setResult("你已被劫持");                    }                });            }        }    }

由代碼可知,我們是通過IXposedHookLoadPackage接口中的handleLoadPackage方法來實現Hook並篡改程序的輸出結果的。代碼中「com.example.wx」是目標程序的包名,」com.example.wx.MainActivity」是想要Hook的類,「toastMessage」是想要Hook的方法。我們在afterHookedMethod方法(用來定義Hook了目標方法之後的操作)中,修改了toastMessage()方法的返回值為「你已被劫持」。在完成代碼編寫之前,說一下為什麼要Hook toastMessage這個方法,我們先用Jadx查看一下我們Apk的源代碼

這個源碼是沒有經過混淆的,所以我們可以看到這個源碼和我們之前寫的一樣,我們根據項目結構可以判斷出我們需要Hook的函數。

OK,以上用來hook的代碼編寫完畢,讓我們進行下一步操作。

6、最後一步,添加入口點

右鍵點擊 「main 」文件夾 , 選擇new –> Folder –>Assets Folder,新建assets文件夾:

然後右鍵點擊 assets文件夾, new–> file,文件名為xposed_init(文件類型選text),並在其中寫上入口類的完整路徑(就是自己編寫的那一個Hook類),這樣,Xposed框架就能夠從這個xposed_init讀取信息來找到模塊的入口,然後進行Hook操作了:

好了,曙光就在前面!最後選擇禁用Instant Run:單擊 File -> Settings -> Build, Execution, Deployment -> Instant Run,把勾全部去掉。 我們重新之前的安裝Xposed模塊的方法,運行模塊,點擊,奇蹟出現~

大功告成!!!

結語

從上面的實戰中我們可以發現Hook的基本原理以及步驟,重新看看我們之前說的四大步,Hook的關鍵其實我們需要知道針對哪個模塊的哪個方法進行Hook

像我們這個例子很簡單,沒有特意的進行代碼混淆以及程序入口改寫等等,我們尋找還是很簡單的,一般市面上的App都是有很多反Xposed的行為,我們其實要學習的還有很多,這個小教程就當做個小入門吧。

下一篇文章和最近工作上的需求有關係,針對的是2019.10.28之後搜狗微信關閉了在某些公眾號內搜索的功能,所以我們想要獲取最新的公眾號文章就不能採取搜狗微信這個渠道了,網上有很多教程都在談論其他的方法,相比較來說,還是Hook這個渠道是最實際的,我們將會在之後的文章里詳細談論,大家可以期待一下~

注意:項目已經完成,想要獲得源碼可以關注下面的微信號,回復「hook入門」即可獲得項目地址以及現成的Apk