Xposed模块编写学习

Xposed原理

Xposed是一款可以在不修改APK的情况下影响程序运行的框架,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。在这个框架下,我们可以编写并加载自己编写的插件APP,实现对目标apk的注入拦截等。

用自己实现的app_process替换掉了系统原本提供的app_process,加载一个额外的jar包,入口从原来的: **com.android.internal.osZygoteInit.main()被替换成了: de.robv.android.xposed.XposedBridge.main()**,
创建的Zygote进程就变成Hook的Zygote进程了,从而完成对zygote进程及其创建的Dalvik/ART虚拟机的劫持(zytoge注入)

image-20240318232358856

从本质上来讲,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

XposedBridgeApi

下载地址:https://github.com/bywhat/XposedBridgeApi 这里有54、82、89版本的

作为项目依赖

项目创建

Android Studio启动!

新建项目

那就选一个No Activity吧。(这个是模块的界面,随意选择即可)

image-20240318233452006

选java

image-20240319094624104

添加依赖

选择Project 并创建一个libs文件夹,然后将api添加为库

image-20240319095714801

修改xml

这里修改AndroidManifest.xml

image-20240319100100625

<application>标签下添加下面内容:

<!-- 是否是xposed模块,xposed根据这个来判断是否是模块 -->
<meta-data
    android:name="xposedmodule"
    android:value="true" />
<!-- 模块描述,显示在xposed模块列表那里第二行 -->
<meta-data
    android:name="xposeddescription"
    android:value="这是一个Xposed模块" />
<!-- 最低xposed版本号(lib文件名可知) -->
<meta-data
    android:name="xposedminversion"
    android:value="89" />

修改后:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Xposeddemo"
        tools:targetApi="31" >

        <!-- 是否是xposed模块,xposed根据这个来判断是否是模块 -->
        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <!-- 模块描述,显示在xposed模块列表那里第二行 -->
        <meta-data
            android:name="xposeddescription"
            android:value="这是一个Xposed模块" />
        <!-- 最低xposed版本号(lib文件名可知) -->
        <meta-data
            android:name="xposedminversion"
            android:value="89" />
    </application>

</manifest>

修改build.gradle

将此处修改为compileOnly 默认的是implementation

implementation 使用该方式依赖的库将会参与编译和打包
compileOnly 只在编译时有效,不会参与打包

image-20240319100742458

新建assets目录

image-20240319101145420

创建文件夹后在这个文件夹下创建一个名为xposed_init的文件,这个文件用于声明模块入口

例如:

com.example.xposeddemo.Hook

image-20240319101702038

创建Hook类

实现IXposedHookLoadPackage接口,然后在handleLoadPackage函数内编写Hook逻辑

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class Hook implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

    }
}

常用的API

  • beforeHookedMethod: 这个方法允许你在目标方法执行前对参数进行修改,或者在方法执行前进行一些其他操作。通常用于在目标方法执行前进行一些准备工作,或者改变即将传入目标方法的参数。
  • afterHookedMethod: 相比之下,这个方法则是在目标方法执行完毕后进行操作。你可以在这里获取到目标方法的返回值,并且可以对返回值进行修改或者进行一些其他操作。这个方法通常用于在目标方法执行后处理返回结果。

Hook普通方法

修改返回值

XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo", classLoader, "getPublicInt", new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        param.setResult(999);
        
    }
});

修改参数

XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo", classLoader, "setPublicInt", int.class, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        param.args[0] = 999;
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

Hook复杂&自定义参数

Class a = loadPackageParam.classLoader.loadClass("com.zj.wuaipojie.Demo");
XposedBridge.hookAllMethods(a, "Inner", new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        
        }
});

Hook替换函数

Class a = classLoader.loadClass("类名")
XposedBridge.hookAllMethods(a,"getId",new XC_MethodReplacement() {  
    @Override  
    protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {  
        return "";  
    }  
});

Hook加固通杀

使用android.app.Application的attach方法获取classloader

image-20240319120356610

XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        Context context = (Context) param.args[0];  
        ClassLoader classLoader = context.getClassLoader();  //获取classloader
        
        ...
    }  
});

Hook变量

静态变量与实例变量:

  • 静态变量(static):类被初始化,同步进行初始化
  • 非静态变量:类被实例化(产生一个对象的时候),进行初始化

静态变量

final Class clazz = XposedHelpers.findClass("类名", classLoader);  
XposedHelpers.setStaticIntField(clazz, "变量名", 999);

实例变量

final Class clazz = XposedHelpers.findClass("类名", classLoader);  
XposedBridge.hookAllConstructors(clazz, new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        super.afterHookedMethod(param);  
        //param.thisObject获取当前所属的对象
        Object ob = param.thisObject;  
        XposedHelpers.setIntField(ob,"变量名",9999);  
    }  
});

Hook构造函数

无参构造函数

XposedHelpers.findAndHookConstructor("com.zj.wuaipojie.Demo", classLoader, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

有参构造函数

XposedHelpers.findAndHookConstructor("com.zj.wuaipojie.Demo", classLoader, String.class, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

Hook multiDex方法

XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        ClassLoader cl= ((Context)param.args[0]).getClassLoader();  
        Class<?> hookclass=null;  
        try {  
            hookclass=cl.loadClass("类名");  
        }catch (Exception e){  
            Log.e("zj2595","未找到类",e);  
            return;        
        }  
        XposedHelpers.findAndHookMethod(hookclass, "方法名", new XC_MethodHook() {  
            @Override  
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
            }        
        });  
    }  
});

主动调用

静态方法:

Class clazz = XposedHelpers.findClass("类名",lpparam.classLoader);
XposedHelpers.callStaticMethod(clazz,"方法名",参数(非必须));

实例方法:

Class clazz = XposedHelpers.findClass("类名",lpparam.classLoader);
XposedHelpers.callMethod(clazz.newInstance(),"方法名",参数(非必须));

Hook内部类

内部类:类里还有一个类class

XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo$InnerClass", lpparam.classLoader, "innerFunc",String.class,  new XC_MethodHook() {  
    @Override  
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {  
        super.beforeHookedMethod(param);  

    }  
});

反射大法

Class clazz = XposedHelpers.findClass("com.zj.wuaipojie.Demo", lpparam.classLoader);
XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo$InnerClass", lpparam.classLoader, "innerFunc",String.class,  new XC_MethodHook() {  
    @Override  
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {  
        super.beforeHookedMethod(param);  
        //第一步找到类
        //找到方法,如果是私有方法就要setAccessible设置访问权限
        //invoke主动调用或者set修改值(变量)
        Class democlass = Class.forName("com.zj.wuaipojie.Demo",false,lpparam.classLoader);  
        Method demomethod = democlass.getDeclaredMethod("refl");  
        demomethod.setAccessible(true);  
        demomethod.invoke(clazz.newInstance());  
    }  
});

遍历所有类下的所有方法

XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        super.afterHookedMethod(param);  
        Class clazz = (Class) param.getResult();  
        String clazzName = clazz.getName();  
        //排除非包名的类  
        if(clazzName.contains("com.zj.wuaipojie")){  
            Method[] mds = clazz.getDeclaredMethods();  
            for(int i =0;i<mds.length;i++){  
                final Method md = mds[i];  
                int mod = mds[i].getModifiers();  
                //去除抽象、native、接口方法  
                if(!Modifier.isAbstract(mod)  
                    && !Modifier.isNative(mod)  
                    &&!Modifier.isAbstract(mod)){  
                    XposedBridge.hookMethod(mds[i], new XC_MethodHook() {  
                        @Override  
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {  
                            super.beforeHookedMethod(param);  
                            Log.d("zj2595",md.toString());  
                        }  
                    });  
                }  
  
           }  
        }  
  
    }  
});

Xposed妙用

字符串赋值定位:

XposedHelpers.findAndHookMethod("android.widget.TextView", lpparam.classLoader, "setText", CharSequence.class, new XC_MethodHook() {  
    @Override  
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {  
        super.beforeHookedMethod(param);  
        Log.d("zj2595",param.args[0].toString());  
        if(param.args[0].equals("已过期")){  
            printStackTrace();  
        }
    }  
});
private static void printStackTrace() {  
    Throwable ex = new Throwable();  
    StackTraceElement[] stackElements = ex.getStackTrace();  
    for (int i = 0; i < stackElements.length; i++) {  
        StackTraceElement element = stackElements[i];  
        Log.d("zj2595","at " + element.getClassName() + "." + element.getMethodName() + "(" + element.getFileName() + ":" + element.getLineNumber() + ")");  
    }  
}

点击事件监听:

Class clazz = XposedHelpers.findClass("android.view.View", lpparam.classLoader);
XposedBridge.hookAllMethods(clazz, "performClick", new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        super.afterHookedMethod(param);  
        Object listenerInfoObject = XposedHelpers.getObjectField(param.thisObject, "mListenerInfo");  
        Object mOnClickListenerObject = XposedHelpers.getObjectField(listenerInfoObject, "mOnClickListener");  
        String callbackType = mOnClickListenerObject.getClass().getName();  
        Log.d("zj2595",callbackType);  
    }  
});

改写布局:

XposedHelpers.findAndHookMethod("com.zj.wuaipojie.ui.ChallengeSixth", lpparam.classLoader,  
        "onCreate", Bundle.class, new XC_MethodHook() {  
    @Override  
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
        super.afterHookedMethod(param);  
        View img = (View)XposedHelpers.callMethod(param.thisObject,  
                "findViewById", 0x7f0800de);  
        img.setVisibility(View.GONE);  
  
    }  
});

大佬文章: https://forum.butian.net/share/2248

例子

hook的目标是这个a方法

//类:com.zj.wuaipojie.Demo

 public final String a(String str) {
        return "这是一个" + str + "方法";
    }

它被调用时传参为普通

Log.d(Tag, a("普通"));

image-20240319113625582

下面是获取传入的参数和修改参数的例子

首先先判断包名,xposed会遍历所有的包,如果不是这个包就return掉:

if (!loadPackageParam.packageName.equals("com.zj.wuaipojie")) { //指定包名
            return;
  }

Tips: 在jadx中可直接选中方法,右键,复制为xposed片段。也可以自己写

XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo", classLoader, "a", java.lang.String.class, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

把其中的classLoader修改为loadPackageParam.classLoader

image-20240319114048393

在方法调用前,使用日志的方式打印出参数值,建议使用Log.e(),因为其显示的日志是红色的,显眼,容易区分,也可以使用XposedBridge.log()

并且修改掉参数

protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
    super.beforeHookedMethod(param);
    Log.e("zj2595",param.args[0].toString());

    param.args[0] = "——————Hook——————"; //修改参数值

    Log.e("zj2595",param.args[0].toString());
}

编译运行:

第一次运行需要在手机或模拟器操作一下,会有xposed模块激活提示:

image-20240319115152999

启动模块,选择系统框架、选择要Hook的App

然后再重新打开被Hook的app:

image-20240319115339120

可用看到参数已经被修改

修改返回值,将a方法的返回修改掉:

protected void afterHookedMethod(MethodHookParam param) throws Throwable {
    super.afterHookedMethod(param);

    param.setResult("AAAAAAAAAAAAAAA");
}

image-20240319115717839

也可以通过param.getResult()获取返回值

其他的就不演示了

END