新手不要再被誤導!這是一篇最新的Xposed模組編寫教程
*本文作者:張召忠,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。
0×00 前言
在網際網路上,關於Xposed模組編寫的教程可謂是一抓一大把。但由於時間的推移,很多工具和方法都發生了變化(如Eclipse退出安卓程式設計舞臺,AndroidStudio 不斷升級導致其一些設定也隨之變化等)也正因此,網上的教程往往有一些時限性,比如現如今 provide 這個關鍵字已經被捨棄了卻仍有人在用,還有些說要把jar包放到lib資料夾而非libs資料夾……種種錯誤或者落伍的教程對新手產生了很大的誤導。 筆者近日收到過朋友初學Xposed模組編寫時的求助,看了一些他找的參考教程,覺得多多少少都存在點問題,因此萌生了寫一篇關於在最新AndriodStudio 開發環境下實現Xposed模組開發入門的文章。
0×01 Xposed 模組編寫簡介
Xposed 框架的原理就不多說了,它部署在ROOT後的安卓手機上,通過替換/system/bin/app_process程式控制zygote程序,使得app_process在啟動過程中會載入XposedBridge.jar這個jar包,從而完成對Zygote程序及其建立的Dalvik虛擬機器的劫持。可以讓我們在不修改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模組編寫。下面我們就開始吧!
0×02 邁開第一步,新建專案並編輯AndroidManifest.xml
1、首先開啟AndroidStudio(以版本3.1為例,還在用老版本的請升級),建立一個工程,提示我們選擇“Activity”,那就選一個Empty Activity吧。(這個是模組的介面,隨意選擇即可)。
2、我們可以把專案檢視方式設定為Project模式,以方便檢視。然後在 “專案名稱/app/src/main/”目錄下找到AndroidManifest.xml,雙擊之,並在指定位置插入以下三段程式碼:
<meta-data android:name="xposedmodule" android:value="true" /> <meta-data android:name="xposeddescription" android:value="這是一個Xposed例程" /> <meta-data android:name="xposedminversion" android:value="53" />
插入位置及程式碼說明如圖所示:
插入之後,如果你把手機連上AndroidStudio ,點選“編譯”或者“執行”的話,手機就會啟動剛剛編寫的這個程式。而在手機裡的Xposed框架中也會顯示出這個模組:
說明Xposed框架已經認出了我們寫的程式。但先別高興太早——雖然框架已經覺得他是一個Xposed模組了,但我們自己心裡清楚,這個模組還啥都不會幹呢。下一步,我們讓這個模組長點本事。
0×03 走出第二步,搞定XposedBridgeApi-xx.jar 與 build.gradle
我們知道,Xposed模組主要功能是用來Hook其他程式的各種函式。但是,如何讓前一步中的那個“一窮二白”的模組長本事呢?那就要引入 XposedBridgeApi.jar 這個包,你可以理解為一把兵器,模組有了這把寶刀才能施展出Hook本領。以前,都需要手動下載諸如XposedBridgeApi-54.jar 、 XposedBridgeApi-82.jar 等jar包,然後手工匯入到libs目錄裡,才能走下一步道路。其實在AndroidStudio 3.1裡面,我們完全不用這麼麻煩,只需要多寫一行程式碼,就讓AndroidStuido自動給我們配置XposedBridgeApi.jar !下面操作開始(序號接著上一節):
3、在 “專案名稱/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程式碼)了。
0×04 邁開第三步,實現hook操作的具體程式碼
4、在“施展刀法”(編寫hook程式碼)之前,我們先要立一個靶子。在介面上畫一個按鈕,並在MainAcitiviy裡寫程式碼如下:
package com.example.root.xposd_hook_new; 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 "我未被劫持"; } }
這個靶子很簡單:MainActivity介面有個按鈕,點選按鈕後會彈出一個toast提示,該提示的內容由 toastMessage() 方法提供,而toastMessage()的返回值為“我未被劫持”:
下面我們正式開始“施展刀法”(編寫hook程式碼) 來hook我們的MainActivity並修改這個類的toastMessage()方法,讓它的返回值為“你已被劫持”:
5、在MainActivity的同級路徑下新建一個類“HookTest.java”,程式碼如下:
package com.example.root.xposd_hook_new;
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.root.xposd_hook_new ”是目標程式的包名,”com.example.root.xposd_hook_new.MainActivity” 是想要Hook的類, “toastMessage”是想要Hook的方法。我們在afterHookedMethod方法(用來定義Hook了目標方法之後的操作)中,修改了toastMessage()方法的返回值為“你已被劫持”。
OK,以上用來hook的程式碼編寫完畢,讓我們進行下一步操作。
0×05 最後一步,新增入口點
右鍵點選 “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框架裡找到自己寫的模組,打上勾,重啟——點開自己的程式看看,是不是toast的提示已經變了?