Android逆向学习(九) Xposed快速上手(下)

查看 40|回复 7
作者:Rytter   
Android逆向学习(九) Xposed快速上手(下)
一、写在前面
这是吾爱破解正己大大教程的第九个作业,然后我的系统还是ubuntu,android测试机器用的是已经root的Redmi K30su。
上个博客介绍了xposed的配置方法和简单举例,本博客主要讲解关于xposed的其他用法。
二、任务目标
实现学习使用xposed常用API,并上手尝试,查看效果,了解xposed基本功能
本次博客量大管饱
三、实现方法
Xposed常用API
这些hook的实例来自于正己大大给出的apk中的com.zj.wuaipojie.Demo文件,可以通过APKLab逆向得到具体信息,APKLab的使用方法可以自行google或者看我的第一篇博客。
APKlab解码之后的代码是:
package com.zj.wuaipojie;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
/* compiled from: Demo.kt */
@Metadata(d1 = {"\u00006\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0006\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\t\u0018\u0000 \u001c2\u00020\u0001:\u0003\u001b\u001c\u001dB\u0007\b\u0016¢\u0006\u0002\u0010\u0002B\u000f\b\u0002\u0012\u0006\u0010\u0003\u001a\u00020\u0004¢\u0006\u0002\u0010\u0005J\u001a\u0010\r\u001a\u00020\u000e2\n\u0010\u000f\u001a\u00060\u0010R\u00020\u00002\u0006\u0010\u0003\u001a\u00020\u0004J\u000e\u0010\u0011\u001a\u00020\u00042\u0006\u0010\u0003\u001a\u00020\u0004J4\u0010\u0012\u001a\u00020\u000e2\u0006\u0010\u0003\u001a\u00020\u00042\"\u0010\u0013\u001a\u001e\u0012\u0004\u0012\u00020\u0001\u0012\u0004\u0012\u00020\u00010\u0014j\u000e\u0012\u0004\u0012\u00020\u0001\u0012\u0004\u0012\u00020\u0001`\u0015H\u0002J\u0010\u0010\u0016\u001a\u00020\u000e2\u0006\u0010\u0003\u001a\u00020\u0004H\u0002J\b\u0010\u0017\u001a\u00020\u000eH\u0002J\b\u0010\u0018\u001a\u00020\u000eH\u0002J\u0010\u0010\u0019\u001a\u00020\u000e2\u0006\u0010\u0003\u001a\u00020\u0004H\u0002J\u0006\u0010\u001a\u001a\u00020\u000eR\u000e\u0010\u0006\u001a\u00020\u0007X\u0082D¢\u0006\u0002\n\u0000R\u001a\u0010\b\u001a\u00020\u0007X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\t\u0010\n\"\u0004\b\u000b\u0010\f¨\u0006\u001e"}, d2 = {"Lcom/zj/wuaipojie/Demo;", "", "()V", "str", "", "(Ljava/lang/String;)V", "privateInt", "", "publicInt", "getPublicInt", "()I", "setPublicInt", "(I)V", "Inner", "", "animal", "Lcom/zj/wuaipojie/Demo$Animal;", "a", "complexParameterFunc", "map", "Ljava/util/HashMap;", "Lkotlin/collections/HashMap;", "privateFunc", "refl", "repleaceFunc", "staticPrivateFunc", "test", "Animal", "Companion", "InnerClass", "app_release"}, k = 1, mv = {1, 7, 1}, xi = 48)
/* loaded from: /tmp/jadx-14754525062353381193.dex */
public final class Demo {
    private static final String Tag = "zj2595";
    private final int privateInt;
    private int publicInt;
    public static final Companion Companion = new Companion((DefaultConstructorMarker) null);
    private static final String staticField = "我是静态变量";
    private Demo(String str) {
        this.privateInt = 300;
        this.publicInt = 200;
        Log.d(Tag, "这是有参构造函数 || " + str);
    }
    public final int getPublicInt() {
        return this.publicInt;
    }
    public final void setPublicInt(int i) {
        this.publicInt = i;
    }
    public Demo() {
        this(Tag);
        Log.d(Tag, "这是无参构造函数");
    }
    public final String a(String str) {
        return "这是一个" + str + "方法";
    }
    public final void test() {
        Log.d(Tag, a("普通"));
        Log.d(Tag, "staticInt = " + staticField);
        Log.d(Tag, "publicInt = " + this.publicInt);
        Log.d(Tag, "privateInt = " + this.privateInt);
        Log.d(Tag, "privateInt = " + this.privateInt);
        privateFunc("wuaipojie");
        staticPrivateFunc("wuaipojie");
        HashMap hashMap = new HashMap();
        hashMap.put("key", "value");
        new ArrayList().add("listValue");
        complexParameterFunc("wuaipojie", hashMap);
        repleaceFunc();
        Inner((Animal) new test.1(this), "wuaipojie");
        new InnerClass(this).innerFunc("wuaipojie");
    }
    private final void privateFunc(String str) {
        Log.d(Tag, "这是私有变量方法 || " + str);
    }
    private final void staticPrivateFunc(String str) {
        Log.d(Tag, "这是静态私有变量方法 || " + str);
    }
    private final void complexParameterFunc(String str, HashMap map) {
        Log.d(Tag, "这是复杂参数方法 || " + str);
    }
    private final void repleaceFunc() {
        Log.d(Tag, "这是替换函数");
    }
    public final void Inner(Animal animal, String str) {
        Log.d(Tag, "这是自定义参数 ||" + str);
        animal.eatFunc("wuaipojie");
    }
    private final void refl() {
        Log.d(Tag, "this is fanshe publicInt " + this.publicInt);
    }
}
1. hook静态变量
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return // 只处理目标应用
        XposedBridge.log("程序开始执行")
        try {
            val clazz = XposedHelpers.findClass(
                "com.zj.wuaipojie.Demo", // 目标类名
                lpparam.classLoader
            )
            // 读取静态字段 staticField 的原始值
            val originalValue = XposedHelpers.getStaticObjectField(clazz, "staticField")
            XposedBridge.log("原始 staticField 值为: $originalValue")
            // 修改静态字段的值
            XposedHelpers.setStaticObjectField(clazz, "staticField", "我被 Hook 改掉了")
            // 再次读取确认已修改
            val newValue = XposedHelpers.getStaticObjectField(clazz, "staticField")
            XposedBridge.log("修改后 staticField 值为: $newValue")
        } catch (e: Throwable) {
            XposedBridge.log("处理 staticField 失败: ${e.message}")
        }
    }
}


8657464ab1fec9960ac6f69dd8af3a5a.jpg (52.87 KB, 下载次数: 0)
下载附件
2025-5-12 11:40 上传

2. hook实例变量
需要注意的是,hook实例变量的话需要等类加载后才可以hook到
通过对源码的查询发现,是在实例中第六关中import了这个类,所以在hook的过程中,需要点击第六关才可以hook到这个变量。
package com.example.lsposedhook
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.callbacks.XC_LoadPackage
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        try {
            XposedHelpers.findAndHookConstructor(
                "com.zj.wuaipojie.Demo",
                lpparam.classLoader,
                object : de.robv.android.xposed.XC_MethodHook() {
                    override fun afterHookedMethod(param: MethodHookParam) {
                        val instance = param.thisObject
                        val clazz = instance.javaClass
                        val fields = clazz.declaredFields
                        XposedBridge.log("Demo 实例创建完成,打印字段:")
                        for (field in fields) {
                            try {
                                field.isAccessible = true
                                val value = field.get(instance)
                                XposedBridge.log("${field.name} = $value")
                            } catch (e: Throwable) {
                                XposedBridge.log("读取字段 ${field.name} 失败: ${e.message}")
                            }
                        }
                    }
                }
            )
        } catch (e: Throwable) {
            XposedBridge.log("Hook Demo 构造函数失败: ${e.message}")
        }
    }
}
同样查看LSposed日志,可以发现已经hook成功了。


4f3065468e350c0aef93187e7f268c62.jpg (67.98 KB, 下载次数: 0)
下载附件
2025-5-12 11:40 上传

3. Hook multiDex方法
首先解释一下什么是multiDex
android apk文件中,会有一个classes.dex文件,这个文件里面会包含所有的可执行代码,也就是在编写android应用的时候,所有的java或kotlin代码都会被编译成classes.dex文件,但是classes.dex文件有个特性,就是每个classes.dex里面只能包含65536个方法,如果代码太多了,构建工具gradle在构建的时候会把代码拆分成多个dex文件,这就是multiDex。
multiDex文件的特点就是,多个dex文件不一定会同时加载,比如现在只加载第一个classes.dex文件,剩下的classes2.dex还没加载,如果此时我们直接hook一个方法的话,不一定能找到这个方法,因为它很有可能在classes2.dex中,而这个没有加载,系统不知道这个方法在哪里。
为了应对这种情况,可以使用延迟hook的方式,也就是等到类实际加载时再进行 Hook。
package com.example.lsposedhook
import android.app.Application
import android.content.Context
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
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        XposedBridge.log("开始 Hook ${lpparam.packageName}")
        XposedHelpers.findAndHookMethod(
            Application::class.java,
            "attach",
            Context::class.java,
            object : XC_MethodHook() {
                override fun afterHookedMethod(param: MethodHookParam) {
                    val context = param.args[0] as Context
                    val cl = context.classLoader
                    try {
                        val clazz = cl.loadClass("com.zj.wuaipojie.Demo")
                        XposedBridge.log("找到 Demo 类,准备 Hook 方法 a()")
                        XposedHelpers.findAndHookMethod(
                            clazz,
                            "a",
                            String::class.java,
                            object : XC_MethodHook() {
                                override fun beforeHookedMethod(param: MethodHookParam) {
                                    XposedBridge.log("调用前参数: ${param.args[0]}")
                                }
                                override fun afterHookedMethod(param: MethodHookParam) {
                                    param.result = "Hook 返回值"
                                    XposedBridge.log("返回值已修改")
                                }
                            }
                        )
                    } catch (e: Exception) {
                        XposedBridge.log("找不到 Demo 类: ${e.message}")
                    }
                }
            }
        )
    }
}


aabda53837cc8641e07597b9c02f3df0.jpg (61.47 KB, 下载次数: 0)
下载附件
2025-5-12 11:40 上传

4. 主动调用
主动调用就是通过LSposed插件直接调用应用中的一个函数
主要是通过getDeclaredConstructor().newInstance()创建demo实例
通过getDeclaredMethod("a", String::class.java)获取方法
通过invoke(demoInstance, "主动调用")去调用这个方法,后面哪个是方法使用的参数
package com.example.lsposedhook
import android.app.Application
import android.content.Context
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        XposedBridge.log("开始 Hook ${lpparam.packageName}")
        XposedHelpers.findAndHookMethod(
            Application::class.java,
            "attach",
            Context::class.java,
            object : XC_MethodHook() {
                override fun afterHookedMethod(param: MethodHookParam) {
                    val context = param.args[0] as Context
                    val classLoader = context.classLoader
                    try {
                        val clazz = classLoader.loadClass("com.zj.wuaipojie.Demo")
                        XposedBridge.log("找到 Demo 类,准备调用方法")
                        // 创建 Demo 实例
                        val demoInstance = clazz.getDeclaredConstructor().newInstance()
                        // 获取方法
                        val method = clazz.getDeclaredMethod("a", String::class.java)
                        // 调用方法
                        val result = method.invoke(demoInstance, "主动调用")
                        XposedBridge.log("调用结果: $result")
                    } catch (e: Throwable) {
                        XposedBridge.log("主动调用失败: ${e.message}")
                    }
                }
            }
        )
    }
}


f01fbda4e5f3052d9d8571cf4b7537c8.jpg (53.81 KB, 下载次数: 0)
下载附件
2025-5-12 11:40 上传

5. Hook内部类
内部类就是定义在一个类里面的类,下图是逆向出来的代码,可以看到出现类InnerClass,对应的smali代码就是Demo$InnerClass.smali,


f043de8385f7b1f02e46a6ab85825e6b.png (77.17 KB, 下载次数: 0)
下载附件
2025-5-12 11:40 上传

innterClass逆向过来的源码是
package com.zj.wuaipojie;
import android.util.Log;
import kotlin.Metadata;
/* compiled from: Demo.kt */
@Metadata(d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0006\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\b\u0080\u0004\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u000e\u0010\n\u001a\u00020\u000b2\u0006\u0010\f\u001a\u00020\rR\u000e\u0010\u0003\u001a\u00020\u0004X\u0082D¢\u0006\u0002\n\u0000R\u001a\u0010\u0005\u001a\u00020\u0004X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\u0006\u0010\u0007\"\u0004\b\b\u0010\t¨\u0006\u000e"}, d2 = {"Lcom/zj/wuaipojie/Demo$InnerClass;", "", "(Lcom/zj/wuaipojie/Demo;)V", "innerPrivateInt", "", "innerPublicInt", "getInnerPublicInt", "()I", "setInnerPublicInt", "(I)V", "innerFunc", "", "str", "", "app_release"}, k = 1, mv = {1, 7, 1}, xi = 48)
/* loaded from: /tmp/jadx-11594512525937970463.dex */
public final class Demo$InnerClass {
    final /* synthetic */ Demo this$0;
    private int innerPublicInt = 1000;
    private final int innerPrivateInt = 2000;
    public Demo$InnerClass(Demo demo) {
        this.this$0 = demo;
        Log.d("zj2595", "这是内部类构造函数");
    }
    public final int getInnerPublicInt() {
        return this.innerPublicInt;
    }
    public final void setInnerPublicInt(int i) {
        this.innerPublicInt = i;
    }
    public final void innerFunc(String str) {
        Log.d("zj2595", "这是内部类方法 || " + str);
        Log.d("zj2595", "内部类变量 = " + this.innerPublicInt);
        Log.d("zj2595", "内部类私有变量 = " + this.innerPrivateInt);
    }
}
hook的方法就是下面的代码:
package com.example.lsposedhook
import android.app.Application
import android.content.Context
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
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        XposedBridge.log("Hook 开始执行")
        XposedHelpers.findAndHookMethod(
            Application::class.java,
            "attach",
            Context::class.java,
            object : XC_MethodHook() {
                override fun afterHookedMethod(param: MethodHookParam) {
                    val context = param.args[0] as Context
                    val cl = context.classLoader
                    try {
                        // 1. 获取内部类 class
                        val clazz = cl.loadClass("com.zj.wuaipojie.Demo\$InnerClass")
                        // 2. Hook 方法 innerFunc(String)
                        XposedHelpers.findAndHookMethod(
                            clazz,
                            "innerFunc",
                            String::class.java,
                            object : XC_MethodHook() {
                                override fun beforeHookedMethod(param: MethodHookParam) {
                                    XposedBridge.log("innerFunc 调用前: ${param.args[0]}")
                                }
                                override fun afterHookedMethod(param: MethodHookParam) {
                                    XposedBridge.log("innerFunc 调用后")
                                }
                            }
                        )
                    } catch (e: Throwable) {
                        XposedBridge.log("Hook 内部类失败: ${e.message}")
                    }
                }
            }
        )
    }
}
通过hook这个,可以获得这个方法调用时的输入。


18d66ce5b229b2498c889d473dc8f4ca.jpg (49.48 KB, 下载次数: 0)
下载附件
2025-5-12 11:40 上传

6. 反射
Xposed 的 findAndHookMethod是利用底层 ART(Android Runtime)机制,在方法调用入口处“插钩子”(Hook),它默认不会搜索private方法,所以如果想Hook这类方法就要使用反射的操作,在正己大大给出的例子中就包含了这样一个私有方法。
private final void refl() {
        Log.d(Tag, "this is fanshe publicInt " + this.publicInt);
    }
如果没有使用反射的话,日志会输出:
查询日志的方法是在电脑上输入
adb logcat -s zj2595


28a5b1d91ea8547168508750edbf016e.png (48.92 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

如果使用反射的话,LSposed反射的代码如下:
package com.example.lsposedhook
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
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        XposedBridge.log("Hook 开始执行")
        XposedHelpers.findAndHookMethod(
            "com.zj.wuaipojie.Demo\$InnerClass",
            lpparam.classLoader,
            "innerFunc",
            String::class.java,
            object : XC_MethodHook() {
                @Throws(Throwable::class)
                override fun beforeHookedMethod(param: MethodHookParam) {
                    try {
                        // 1. 获取 Demo 类
                        val demoClass =
                            Class.forName("com.zj.wuaipojie.Demo", false, lpparam.classLoader)
                        // 2. 获取构造方法并创建实例
                        val demoInstance = demoClass.getDeclaredConstructor().newInstance()
                        // 3. 获取并调用私有方法
                        val reflMethod = demoClass.getDeclaredMethod("refl")
                        reflMethod.isAccessible = true
                        reflMethod.invoke(demoInstance)
                    } catch (t: Throwable) {
                        XposedBridge.log("反射失败: " + t.message)
                    }
                }
            }
        )
    }
}
这时候查看日志会发现:


5a2a7981aaeb6637036fa431324c6ab3.png (74.9 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

出现了反射的调用例子。
7. 历所有类下的所有方法
正己大大的代码不知为啥一跑就报错,所以自己写了个,可以输出加载的所有类:
package com.example.lsposedhook
import android.util.Log
import dalvik.system.DexFile
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam
class HookEntry : IXposedHookLoadPackage {
    @Throws(Throwable::class)
    override fun handleLoadPackage(lpparam: LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        Log.d(TAG, "Loaded package: " + lpparam.packageName)
        // 加载 Dex 文件
        val dexFile = DexFile(lpparam.appInfo.sourceDir)
        val classNames = dexFile.entries()
        while (classNames.hasMoreElements()) {
            val className = classNames.nextElement()
            try {
                val clazz = lpparam.classLoader.loadClass(className)
                Log.d(TAG, "类: $className")
                val methods = clazz.declaredMethods
                for (method in methods) {
                    method.isAccessible = true // 反射访问私有方法
                    Log.d(TAG, " ↳ 方法: " + method.name + " | 参数: " + method.parameterCount)
                }
            } catch (t: Throwable) {
                // 某些系统类加载会失败,忽略它们
                Log.e(TAG, "加载类失败: " + className + ", 原因: " + t.message)
            }
        }
    }
    companion object {
        private const val TAG = "XposedHook"
    }
}
查看的命令是通过adb:
adb logcat | grep XposedHook


4917258f4c02629cddd26a67e7b86a62.png (109.49 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

xposed其他妙用
字符串赋值定位:
加入我们遇到这种情况,就是发现了一个Text,想知道到底这个Text是怎么出现的,如何定位到这个Text的位置。那就可以通过字符串赋值的方法进行定位:
以挑战6为例,想要知道这个到期时间是怎么来的:


65300b95095f092c7407fbf713d1d848.jpg (46.93 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

hook的代码如下,这串代码可以检测到文本,并且打印出相应的堆栈。:
package com.example.lsposedhook
import android.util.Log
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
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        try {
            XposedHelpers.findAndHookMethod(
                "android.widget.TextView",
                lpparam.classLoader,
                "setText",
                CharSequence::class.java,
                object : XC_MethodHook() {
                    @Throws(Throwable::class)
                    override fun beforeHookedMethod(param: MethodHookParam) {
                        if (param.args[0] == null) return
                        val text = param.args[0].toString()
                        Log.d("XposedHook", "TextView.setText -> $text") //这个可以删掉,不然有点乱
                        // 关键词触发堆栈打印
                        if (text.contains("2023年03月16日")) {
                            Log.d("XposedHook", "检测到可疑文本: $text")
                            val ex = Throwable()
                            for (element in ex.stackTrace) {
                                Log.d(
                                    "XposedHook", "↳ at " + element.className + "." +
                                            element.methodName + "(" + element.fileName + ":" +
                                            element.lineNumber + ")"
                                )
                            }
                        }
                    }
                }
            )
        } catch (e: Throwable) {
            XposedBridge.log("Hook Demo 构造函数失败: ${e.message}")
        }
    }
}
启动后还是在电脑上使用这个命令:
adb logcat | grep XposedHook
之后的结果是:


1b5c4133e19463d11a67ae444fa25401.png (232.66 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

就可以看到这个文本是怎么来的了
点击事件监听
如果我们想要知道点击的按钮都触发了那些操作,就可以通过这种方式实现,代码如下:
原理就是hook "performClick" 这个方法,看会进行那些操作
package com.example.lsposedhook
import android.util.Log
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
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        try {
            val viewClass = XposedHelpers.findClass("android.view.View", lpparam.classLoader)
            XposedBridge.hookAllMethods(viewClass, "performClick", object : XC_MethodHook() {
                @Throws(Throwable::class)
                override fun afterHookedMethod(param: MethodHookParam) {
                    val viewObj = param.thisObject
                    try {
                        val listenerInfo = XposedHelpers.getObjectField(viewObj, "mListenerInfo")
                        val onClickListener =
                            XposedHelpers.getObjectField(listenerInfo, "mOnClickListener")
                        if (onClickListener != null) {
                            val callbackClass = onClickListener.javaClass.name
                            Log.d("XposedHook", "点击事件监听器: $callbackClass")
                            // 打印堆栈以分析点击来源
                            val ex = Throwable()
                            for (element in ex.stackTrace) {
                                Log.d(
                                    "XposedHook", "↳ at " + element.className + "." +
                                            element.methodName + "(" + element.fileName + ":" +
                                            element.lineNumber + ")"
                                )
                            }
                        }
                    } catch (t: Throwable) {
                        Log.w("XposedHook", "获取点击监听器失败: $t")
                    }
                }
            })
        } catch (e: Throwable) {
            XposedBridge.log("Hook Demo 构造函数失败: ${e.message}")
        }
    }
}
效果就是:


0a1774c8a162ecdb8ac2085ad083e0c0.png (458.9 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

改写布局
通过Hook onCreate,然后使用afterHookedMethod是指等页面 onCreate() 执行完之后我们再插手干预。
通过反射调用findViewById()方法,查找 ID 为 0x7f0800de 的 View
将这个控件设为“不可见”状态,即隐藏掉。
代码为:
package com.example.lsposedhook
import android.os.Bundle
import android.util.Log
import android.view.View
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
class HookEntry : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        if (lpparam.packageName != "com.zj.wuaipojie") return
        try {
            val viewClass = XposedHelpers.findClass("android.view.View", lpparam.classLoader)
            XposedBridge.hookAllMethods(viewClass, "performClick", object : XC_MethodHook() {
                @Throws(Throwable::class)
                override fun afterHookedMethod(param: MethodHookParam) {
                    val viewObj = param.thisObject
                    try {
                        XposedHelpers.findAndHookMethod(
                            "com.zj.wuaipojie.ui.ChallengeSixth",
                            lpparam.classLoader,
                            "onCreate",
                            Bundle::class.java,
                            object : XC_MethodHook() {
                                @Throws(Throwable::class)
                                override fun afterHookedMethod(param: MethodHookParam) {
                                    try {
                                        val activity = param.thisObject
                                        val view = XposedHelpers.callMethod(
                                            activity,
                                            "findViewById",
                                            0x7f08005b
                                        ) as View
                                        if (view != null) {
                                            Log.d("XposedHook", "找到View: " + view.javaClass.name)
                                            view.visibility = View.GONE
                                        } else {
                                            Log.w("XposedHook", "未找到指定 View")
                                        }
                                    } catch (t: Throwable) {
                                        Log.e("XposedHook", "隐藏控件失败: " + t.message)
                                    }
                                }
                            }
                        )
                    } catch (t: Throwable) {
                        Log.w("XposedHook", "获取点击监听器失败: $t")
                    }
                }
            })
        } catch (e: Throwable) {
            XposedBridge.log("Hook Demo 构造函数失败: ${e.message}")
        }
    }
}
adb查询日志:


6e3b0882214c2ae97e26a531b36445ed.png (35.96 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

实现的效果前后对比:
原图:


2770afa92b6fa86aba96eaa40c911625.jpg (33.98 KB, 下载次数: 0)
下载附件
2025-5-12 11:43 上传

hook后:


333c34d0c6c6fb32ad164e5802355f9d.jpg (32.9 KB, 下载次数: 0)
下载附件
2025-5-12 11:43 上传

其中一个问题是,如何获得对应的viewID?
其实方法很简单:
逆向出来后我们会发现这个资源的代码:


621973d34195814e623c91cb3b8486d0.png (175.02 KB, 下载次数: 0)
下载附件
2025-5-12 11:41 上传

直接转化过来就可以:


aa59465bdf804221514c5d89b21cdbeb.png (38.04 KB, 下载次数: 0)
下载附件
2025-5-12 11:43 上传

或者通过查询res资源下的代码,也可以找到对应的ID


6ad8a4378fb1bf4109336f990d29224d.png (380.73 KB, 下载次数: 0)
下载附件
2025-5-12 11:43 上传

方法, 下载次数

zhou2957   

学习了 最近正好想学习模块
pzdd   

真牛,不过看不懂
bendan2020   

值得学习,但是真的看不懂
YoungBai   

感谢分享教程.大概能看懂.
asi7   

模块哪里可以下载呢
Rytter
OP
  


asi7 发表于 2025-5-14 09:08
模块哪里可以下载呢

这些模块都是自己写的哦
jsncy   

真牛,谢谢分享。
您需要登录后才可以回帖 登录 | 立即注册

返回顶部