創建xposed模塊
新增默認帶Empty Activity的Module
在Module下的build.gradle文件添加依賴包
在Module下的AndroidManifest.xml文件添加xposed相關meta標簽,用於框架識別
添加hook類,繼承IXposedHookLoadPackage實現hook方法
在模塊下的src/assets/xposed_init(沒有則新建)文件添加完整的hook類名
安裝測試app
安裝xposed模塊
hook測試app
對於加殼的情況
第一次hook
第二次hook
其他示例
目標代碼
hook模塊
總結
參考
Xposed: https://github.com/rovo89/Xposed
Xposed Installer: https://forum.xda-developers.com/showthread.php?t=3034811
api文檔: https://api.xposed.info/reference/packages.html
Maven包: https://mvnrepository.com/artifact/de.robv.android.xposed/api
注:安裝原生xposed框架需root,若無法root可以考慮使用virtualXposed
https://github.com/android-hacker/VirtualXposed
創建測試項目
為了便於實踐,我們直接創建一個名為test的Empty Activity項目即可。
接着在MainActivity.java中,新增一個Log方法,我們后面用xposed框架來hook這個方法。
package com.example.test;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "xposedText";
private TextView tv_hook;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_hook = (TextView) findViewById(R.id.tv_hook);
tv_hook.setText("快來hook我");
Log("init", new Random().nextInt(100));
}
public void Log(String msg, int code) {
Log.i(TAG, "Log: " + msg + code);
}
}
創建xposed模塊
新增默認帶Empty Activity的Module
在Module下的build.gradle文件添加依賴包
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//xposed依賴包
compileOnly 'de.robv.android.xposed:api:82'
}
然后sync,如果比較慢建議科學·上網。
在Module下的AndroidManifest.xml文件添加xposed相關meta標簽,用於框架識別
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.xposed">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--模塊申明,true表示申明為xposed模塊-->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!--模塊說明,一般為模塊的功能描述-->
<meta-data
android:name="xposeddescription"
android:value="這個模塊是用來劫持登錄的" />
<!--模塊兼容版本-->
<meta-data
android:name="xposedminversion"
android:value="54" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
添加hook類,繼承IXposedHookLoadPackage實現hook方法
package com.example.xposed;
import android.content.Context;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HookLog implements IXposedHookLoadPackage {
private static final String TAG = "XposedHook";
private Context hookContext = null;
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
//過濾非目標包
if (lpparam == null) {
return;
}
Log.e(TAG, "Load app packageName:" + lpparam.packageName);
//目標app的包名,既目標apk的AndroidManifest.xml文件manifest標簽中的package屬性值
if (!"com.example.test".equals(lpparam.packageName)) {
return;
}
//獲取context,用於Toast提示
XposedHelpers.findAndHookMethod(
//填寫目標方法所在的完整類名
"android.content.ContextWrapper",
//默認classLoader
lpparam.classLoader,
//目標方法
"getApplicationContext",
// Hook回調
new XC_MethodHook() {
protected void afterHookedMethod(MethodHookParam param) {
if (hookContext != null)
return;
hookContext = (Context) param.getResult();
Log.i(TAG + "hookContext", hookContext.getPackageCodePath());
}
}
);
//進行hook
XposedHelpers.findAndHookMethod(
//填寫目標方法所在的完整類名
"com.example.test.MainActivity",
//默認classLoader
lpparam.classLoader,
//目標方法
"Log",
//方法參數,有幾個寫幾個
String.class,
// 注意,要做到與目標方法參數對應,這里不能用Integer.class。
int.class,
// Hook回調
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Exception {
Log.i(TAG, "劫持開始");
String msg = (String) param.args[0];
int code = (int) param.args[1];
Log.i(TAG, "原code:" + code);
Toast.makeText(hookContext, "原code:" + code, Toast.LENGTH_LONG).show();
//修改方法參數,設為54,這樣就不隨機了。
param.args[1] = 54;
TextView tv = (TextView) XposedHelpers.findField(param.thisObject.getClass(), "tv_hook").get(param.thisObject);
tv.setText("隨機數被我固定為54了");
}
protected void afterHookedMethod(MethodHookParam param) {
Log.i(TAG, "劫持結束");
}
}
);
}
}
在模塊下的src/assets/xposed_init(沒有則新建)文件添加完整的hook類名
com.example.xposed.HookLog
用於提示XposedBridge加載哪一個類。
安裝測試app
正常情況下,長這樣。
安裝xposed模塊
安裝完成之后,Xposed Installer下就會出現我們開發的模塊了。
打勾啟用,然后軟重啟手機。
hook測試app
接着我們直接運行測試app,xposed將啟用我們寫好的模塊進行hook
05-13 14:49:17.429 25902-25902/? E/XposedHook: Load app packageName:com.example.xposed
05-13 14:52:00.527 820-820/? I/XposedHookhookContext: /data/app/com.example.test-1.apk
05-13 14:49:37.069 23232-23232/? I/XposedHook: 劫持開始
05-13 14:49:37.069 23232-23232/? I/XposedHook: 原code:22
05-13 14:49:37.069 23232-23232/? I/xposedText: Log: init54
05-13 14:49:37.069 23232-23232/? I/XposedHook: 劫持結束
最終我們攔截了參數,使其固定為54,並且改寫了tv_hook的值
對於加殼的情況
若需目標app加殼,我們就不能直接findAndHookMethod了,因為真正app代碼是在殼內動態加載的,所以要經過兩次hook。
我們以360的殼為例。
第一次hook
需要分析殼代碼實際加載app的方法,然后注入獲取到真正的ClassLoader
XposedHelpers.findAndHookMethod(
"com.stub.StubApp",
lpparam.classLoader,
"attachBaseContext",
Context.class,
// Hook回調
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Exception {
Log.e(TAG, "劫持開始");
}
protected void afterHookedMethod(MethodHookParam param) {
//獲取到Context對象,通過這個對象來獲取classloader
Context context = (Context) param.args[0];
//獲取classloader,之后hook加固后的就使用這個classloader
ClassLoader realClassLoader = context.getClassLoader();
//下面就是將classloader修改成殼的classloader就可以成功的hook了
realXposedHook(realClassLoader);
Log.e(TAG, "劫持結束");
}
}
);
第二次hook
取到真正的ClassLoader,我們就可以愉快的hook了
private void realXposedHook(final ClassLoader classLoader) {
//固定格式
XposedHelpers.findAndHookMethod(
"com.target.class",
classLoader,
"targetMethod",
// Hook回調
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Exception {
Log.e(TAG, "real劫持開始");
}
protected void afterHookedMethod(MethodHookParam param) {
Log.e(TAG, "real劫持結束");
}
}
);
}
其他示例
目標代碼
abstract class Animal{
int anonymoutInt = 500;
public abstract void eatFunc(String value);
}
public class HookDemo {
private String Tag = "HookDemo";
private static int staticInt = 100;
public int publicInt = 200;
private int privateInt = 300;
public HookDemo(){
this("NOHook");
Log.d(Tag, "HookDemo() was called|||");
}
private HookDemo(String str){
Log.d(Tag, "HookDemo(String str) was called|||" + str);
}
public void hookDemoTest(){
Log.d(Tag, "staticInt = " + staticInt);
Log.d(Tag, "PublicInt = " + publicInt);
Log.d(Tag, "privateInt = " + privateInt);
publicFunc("NOHook");
Log.d(Tag, "PublicInt = " + publicInt);
Log.d(Tag, "privateInt = " + privateInt);
privateFunc("NOHook");
staticPrivateFunc("NOHook");
String[][] str = new String[1][2];
Map map = new HashMap<String, String>();
map.put("key", "value");
ArrayList arrayList = new ArrayList();
arrayList.add("listValue");
complexParameterFunc("NOHook", str, map, arrayList);
repleaceFunc();
anonymousInner(new Animal() {
@Override
public void eatFunc(String value) {
Log.d(Tag, "eatFunc(String value) was called|||" + value);
Log.d(Tag, "anonymoutInt = " + anonymoutInt);
}
}, "NOHook");
InnerClass innerClass = new InnerClass();
innerClass.InnerFunc("NOHook");
}
public void publicFunc(String value){
Log.d(Tag, "publicFunc(String value) was called|||" + value);
}
private void privateFunc(String value){
Log.d(Tag, "privateFunc(String value) was called|||" + value);
}
static private void staticPrivateFunc(String value){
Log.d("HookDemo", "staticPrivateFunc(Strin value) was called|||" + value);
}
private void complexParameterFunc(String value, String[][] str, Map<String,String> map, ArrayList arrayList)
{
Log.d("HookDemo", "complexParameter(Strin value) was called|||" + value);
}
private void repleaceFunc(){
Log.d(Tag, "repleaceFunc will be replace|||");
}
public void anonymousInner(Animal dog, String value){
Log.d(Tag, "anonymousInner was called|||" + value);
dog.eatFunc("NOHook");
}
private void hideFunc(String value){
Log.d(Tag, "hideFunc was called|||" + value);
}
class InnerClass{
public int innerPublicInt = 10;
private int innerPrivateInt = 20;
public InnerClass(){
Log.d(Tag, "InnerClass constructed func was called");
}
public void InnerFunc(String value){
Log.d(Tag, "InnerFunc(String value) was called|||" + value);
Log.d(Tag, "innerPublicInt = " + innerPublicInt);
Log.d(Tag, "innerPrivateInt = " + innerPrivateInt);
}
}
}
hook模塊
public class XposedHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (loadPackageParam.packageName.equals("com.example.xposedhooktarget")) {
final Class<?> clazz = XposedHelpers.findClass("com.example.xposedhooktarget.HookDemo", loadPackageParam.classLoader);
//getClassInfo(clazz);
//不需要獲取類對象,即可直接修改類中的私有靜態變量staticInt
XposedHelpers.setStaticIntField(clazz, "staticInt", 99);
//Hook無參構造函數,啥也不干。。。。
XposedHelpers.findAndHookConstructor(clazz, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("Haha, HookDemo constructed was hooked" );
//大坑,此時對象還沒有建立,即不能獲取對象,也不能修改非靜態變量的值
//XposedHelpers.setIntField(param.thisObject, "publicInt", 199);
//XposedHelpers.setIntField(param.thisObject, "privateInt", 299);
}
});
//Hook有參構造函數,修改參數
XposedHelpers.findAndHookConstructor(clazz, String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "Haha, HookDemo(str) are hooked";
}
});
//Hook有參構造函數,修改參數------不能使用XC_MethodReplacement()替換構造函數內容,
//XposedHelpers.findAndHookConstructor(clazz, String.class, new XC_MethodReplacement() {
// @Override
// protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
// Log.d("HookDemo" , "HookDemo(str) was replace");
// }
//});
//Hook公有方法publicFunc,
// 1、修改參數
// 2、修改下publicInt和privateInt的值
// 3、再順便調用一下隱藏函數hideFunc
//XposedHelpers.findAndHookMethod("com.example.xposedhooktarget.HookDemo", clazz.getClassLoader(), "publicFunc", String.class, new XC_MethodHook()
XposedHelpers.findAndHookMethod(clazz, "publicFunc", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "Haha, publicFunc are hooked";
XposedHelpers.setIntField(param.thisObject, "publicInt", 199);
XposedHelpers.setIntField(param.thisObject, "privateInt", 299);
// 讓hook的對象本身去執行流程
Method md = clazz.getDeclaredMethod("hideFunc", String.class);
md.setAccessible(true);
//md.invoke(param.thisObject, "Haha, hideFunc was hooked");
XposedHelpers.callMethod(param.thisObject, "hideFunc", "Haha, hideFunc was hooked");
//實例化對象,然后再調用HideFunc方法
//Constructor constructor = clazz.getConstructor();
//XposedHelpers.callMethod(constructor.newInstance(), "hideFunc", "Haha, hideFunc was hooked");
}
});
//Hook私有方法privateFunc,修改參數
//XposedHelpers.findAndHookMethod("com.example.xposedhooktarget.HookDemo", clazz.getClassLoader(), "privateFunc", String.class, new XC_MethodHook()
XposedHelpers.findAndHookMethod(clazz, "privateFunc", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "Haha, privateFunc are hooked";
}
});
//Hook私有靜態方法staticPrivateFunc, 修改參數
XposedHelpers.findAndHookMethod(clazz, "staticPrivateFunc", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "Haha, staticPrivateFunc are hooked";
}
});
//Hook復雜參數函數complexParameterFunc
Class fclass1 = XposedHelpers.findClass("java.util.Map", loadPackageParam.classLoader);
Class fclass2 = XposedHelpers.findClass("java.util.ArrayList", loadPackageParam.classLoader);
XposedHelpers.findAndHookMethod(clazz, "complexParameterFunc", String.class,
"[[Ljava.lang.String;", fclass1, fclass2, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "Haha, complexParameterFunc are hooked";
}
});
//Hook私有方法repleaceFunc, 替換打印內容
XposedHelpers.findAndHookMethod(clazz, "repleaceFunc", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
Log.d("HookDemo", "Haha, repleaceFunc are replaced");
return null;
}
});
//Hook方法, anonymousInner, 參數是抽象類,先加載所需要的類即可
Class animalClazz = loadPackageParam.classLoader.loadClass("com.example.xposedhooktarget.Animal");
XposedHelpers.findAndHookMethod(clazz, "anonymousInner", animalClazz, String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("HookDemo This is test");
param.args[1] = "Haha, anonymousInner are hooked";
}
});
//Hook匿名類的eatFunc方法,修改參數,順便修改類中的anonymoutInt變量
XposedHelpers.findAndHookMethod("com.example.xposedhooktarget.HookDemo$1", clazz.getClassLoader(),
"eatFunc", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "Haha, eatFunc are hooked";
XposedHelpers.setIntField(param.thisObject, "anonymoutInt", 499);
}
});
//hook內部類的構造方法失敗,且會導致hook內部類的InnerFunc方法也失敗,原因不明
// XposedHelpers.findAndHookConstructor(clazz1, new XC_MethodHook() {
// @Override
// protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// XposedBridge.log("Haha, InnerClass constructed was hooked" );
// }
// });
//Hook內部類InnerClass的InnerFunc方法,修改參數,順便修改類中的innerPublicInt和innerPrivateInt變量
final Class<?> clazz1 = XposedHelpers.findClass("com.example.xposedhooktarget.HookDemo$InnerClass", loadPackageParam.classLoader);
XposedHelpers.findAndHookMethod(clazz1, "InnerFunc", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "Haha, InnerFunc was hooked";
XposedHelpers.setIntField(param.thisObject, "innerPublicInt", 9);
XposedHelpers.setIntField(param.thisObject, "innerPrivateInt", 19);
}
});
}
}
private void getClassInfo(Class clazz) {
//getFields()與getDeclaredFields()區別:getFields()只能訪問類中聲明為公有的字段,私有的字段它無法訪問,
//能訪問從其它類繼承來的公有方法.getDeclaredFields()能訪問類中所有的字段,與public,private,protect無關,
//不能訪問從其它類繼承來的方法
//getMethods()與getDeclaredMethods()區別:getMethods()只能訪問類中聲明為公有的方法,私有的方法它無法訪問,
//能訪問從其它類繼承來的公有方法.getDeclaredFields()能訪問類中所有的字段,與public,private,protect無關,
//不能訪問從其它類繼承來的方法
//getConstructors()與getDeclaredConstructors()區別:getConstructors()只能訪問類中聲明為public的構造函數
//getDeclaredConstructors()能訪問類中所有的構造函數,與public,private,protect無關
//XposedHelpers.setStaticObjectField(clazz,"sMoney",110);
//Field sMoney = clazz.getDeclaredField("sMoney");
//sMoney.setAccessible(true);
Field[] fs;
Method[] md;
Constructor[] cl;
fs = clazz.getFields();
for (int i = 0; i < fs.length; i++) {
XposedBridge.log("HookDemo getFiled: " + Modifier.toString(fs[i].getModifiers()) + " " +
fs[i].getType().getName() + " " + fs[i].getName());
}
fs = clazz.getDeclaredFields();
for (int i = 0; i < fs.length; i++) {
XposedBridge.log("HookDemo getDeclaredFields: " + Modifier.toString(fs[i].getModifiers()) + " " +
fs[i].getType().getName() + " " + fs[i].getName());
}
md = clazz.getMethods();
for (int i = 0; i < md.length; i++) {
Class<?> returnType = md[i].getReturnType();
XposedBridge.log("HookDemo getMethods: " + Modifier.toString(md[i].getModifiers()) + " " +
returnType.getName() + " " + md[i].getName());
//獲取參數
//Class<?> para[] = md[i].getParameterTypes();
//for (int j = 0; j < para.length; ++j) {
//System.out.print(para[j].getName() + " " + "arg" + j);
//if (j < para.length - 1) {
// System.out.print(",");
//}
//}
}
md = clazz.getDeclaredMethods();
for (int i = 0; i < md.length; i++) {
Class<?> returnType = md[i].getReturnType();
XposedBridge.log("HookDemo getDeclaredMethods: " + Modifier.toString(md[i].getModifiers()) + " " +
returnType.getName() + " " + md[i].getName());
}
cl = clazz.getConstructors();
for (int i = 0; i < cl.length; i++) {
XposedBridge.log("HookDemo getConstructors: " + Modifier.toString(cl[i].getModifiers()) + " " +
md[i].getName());
}
cl = clazz.getDeclaredConstructors();
for (int i = 0; i < cl.length; i++) {
XposedBridge.log("HookDemo getDeclaredConstructors: " + Modifier.toString(cl[i].getModifiers()) + " " +
md[i].getName());
}
}
}
總結
1、hook的難點在於逆向分析,花的時間比較多。
2、理論上可以基於xposed做任何事。
3、若目標app改版升級什么的,需要重新適配,很麻煩。
demo地址:
鏈接:https://pan.baidu.com/s/1g8dEy3OgE-vcSDUj7CHyaQ 密碼:bvww
參考
https://github.com/rovo89/XposedBridge/wiki/Development-tutorial
https://www.cnblogs.com/gordon0918/p/6732100.html