在互聯網上,關於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;
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的提示已經變了?
本次教程就到這里,謝謝!















