Android逆向学习(九) Xposed快速上手(下)
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<Object, Object> 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<Object, Object> 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}")}}
}
2. hook实例变量
需要注意的是,hook实例变量的话需要等类加载后才可以hook到
通过对源码的查询发现,是在实例中第六关中import了这个类,所以在hook的过程中,需要点击第六关才可以hook到这个变量。
package com.example.lsposedhookimport de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.callbacks.XC_LoadPackage
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpersclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returntry {XposedHelpers.findAndHookConstructor("com.zj.wuaipojie.Demo",lpparam.classLoader,object : de.robv.android.xposed.XC_MethodHook() {override fun afterHookedMethod(param: MethodHookParam) {val instance = param.thisObjectval clazz = instance.javaClassval fields = clazz.declaredFieldsXposedBridge.log("Demo 实例创建完成,打印字段:")for (field in fields) {try {field.isAccessible = trueval 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成功了。
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_LoadPackageclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returnXposedBridge.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 Contextval cl = context.classLoadertry {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}")}}})}
}
4. 主动调用
主动调用就是通过LSposed插件直接调用应用中的一个函数
主要是通过getDeclaredConstructor().newInstance()创建demo实例
通过getDeclaredMethod(“a”, String::class.java)获取方法
通过invoke(demoInstance, “主动调用”)去调用这个方法,后面哪个是方法使用的参数
package com.example.lsposedhookimport 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_LoadPackageclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returnXposedBridge.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 Contextval classLoader = context.classLoadertry {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}")}}})}
}
5. Hook内部类
内部类就是定义在一个类里面的类,下图是逆向出来的代码,可以看到出现类InnerClass,对应的smali代码就是Demo$InnerClass.smali,
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.lsposedhookimport 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_LoadPackageclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returnXposedBridge.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 Contextval cl = context.classLoadertry {// 1. 获取内部类 classval 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这个,可以获得这个方法调用时的输入。
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
如果使用反射的话,LSposed反射的代码如下:
package com.example.lsposedhookimport 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_LoadPackageclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returnXposedBridge.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 = truereflMethod.invoke(demoInstance)} catch (t: Throwable) {XposedBridge.log("反射失败: " + t.message)}}})}
}
这时候查看日志会发现:
出现了反射的调用例子。
7. 历所有类下的所有方法
正己大大的代码不知为啥一跑就报错,所以自己写了个,可以输出加载的所有类:
package com.example.lsposedhookimport android.util.Log
import dalvik.system.DexFile
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParamclass HookEntry : IXposedHookLoadPackage {@Throws(Throwable::class)override fun handleLoadPackage(lpparam: LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returnLog.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.declaredMethodsfor (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
xposed其他妙用
字符串赋值定位:
加入我们遇到这种情况,就是发现了一个Text,想知道到底这个Text是怎么出现的,如何定位到这个Text的位置。那就可以通过字符串赋值的方法进行定位:
以挑战6为例,想要知道这个到期时间是怎么来的:
hook的代码如下,这串代码可以检测到文本,并且打印出相应的堆栈。:
package com.example.lsposedhookimport 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_LoadPackageclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returntry {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) returnval 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
之后的结果是:
就可以看到这个文本是怎么来的了
点击事件监听
如果我们想要知道点击的按钮都触发了那些操作,就可以通过这种方式实现,代码如下:
原理就是hook “performClick” 这个方法,看会进行那些操作
package com.example.lsposedhookimport 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_LoadPackageclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returntry {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.thisObjecttry {val listenerInfo = XposedHelpers.getObjectField(viewObj, "mListenerInfo")val onClickListener =XposedHelpers.getObjectField(listenerInfo, "mOnClickListener")if (onClickListener != null) {val callbackClass = onClickListener.javaClass.nameLog.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}")}}
}
效果就是:
改写布局
通过Hook onCreate,然后使用afterHookedMethod是指等页面 onCreate() 执行完之后我们再插手干预。
通过反射调用findViewById()方法,查找 ID 为 0x7f0800de 的 View
将这个控件设为“不可见”状态,即隐藏掉。
代码为:
package com.example.lsposedhookimport 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_LoadPackageclass HookEntry : IXposedHookLoadPackage {override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {if (lpparam.packageName != "com.zj.wuaipojie") returntry {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.thisObjecttry {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.thisObjectval view = XposedHelpers.callMethod(activity,"findViewById",0x7f08005b) as Viewif (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查询日志:
实现的效果前后对比:
原图:
hook后:
其中一个问题是,如何获得对应的viewID?
其实方法很简单:
逆向出来后我们会发现这个资源的代码:
直接转化过来就可以:
或者通过查询res资源下的代码,也可以找到对应的ID