Android逆向学习(九) Xposed快速上手(下)
一、写在前面
这是吾爱破解正己大大教程的第九个作业,然后我的系统还是ubuntu,android测试机器用的是已经root的Redmi K30su。
上个博客介绍了xposed的配置方法和简单举例,本博客主要讲解关于xposed的其他用法。
二、任务目标
实现学习使用xposed常用API,并上手尝试,查看效果,了解xposed基本功能
本次博客量大管饱
三、实现方法
Xposed常用API
这些hook的实例来自于正己大大给出的apk中的com.zj.wuaipojie.Demo文件,可以通过APKLab逆向得到具体信息,APKLab的使用方法可以自行google或者看我的第一篇博客。
APKlab解码之后的代码是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| package com.zj.wuaipojie;
import android.util.Log; import java.util.ArrayList; import java.util.HashMap; import kotlin.Metadata; import kotlin.jvm.internal.DefaultConstructorMarker;
@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)
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静态变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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 )
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到这个变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 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成功了。

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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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}") } } } )
} }
|

4. 主动调用
主动调用就是通过LSposed插件直接调用应用中的一个函数
主要是通过getDeclaredConstructor().newInstance()创建demo实例
通过getDeclaredMethod(“a”, String::class.java)获取方法
通过invoke(demoInstance, “主动调用”)去调用这个方法,后面哪个是方法使用的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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 类,准备调用方法")
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逆向过来的源码是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.zj.wuaipojie;
import android.util.Log; import kotlin.Metadata;
@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)
public final class Demo$InnerClass { final 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的方法就是下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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 { val clazz = cl.loadClass("com.zj.wuaipojie.Demo\$InnerClass") 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这类方法就要使用反射的操作,在正己大大给出的例子中就包含了这样一个私有方法。
1 2 3
| private final void refl() { Log.d(Tag, "this is fanshe publicInt " + this.publicInt); }
|
如果没有使用反射的话,日志会输出:
查询日志的方法是在电脑上输入

如果使用反射的话,LSposed反射的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 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 { val demoClass = Class.forName("com.zj.wuaipojie.Demo", false, lpparam.classLoader) val demoInstance = demoClass.getDeclaredConstructor().newInstance() val reflMethod = demoClass.getDeclaredMethod("refl") reflMethod.isAccessible = true reflMethod.invoke(demoInstance) } catch (t: Throwable) { XposedBridge.log("反射失败: " + t.message) } } } )
} }
|
这时候查看日志会发现:

出现了反射的调用例子。
7. 历所有类下的所有方法
正己大大的代码不知为啥一跑就报错,所以自己写了个,可以输出加载的所有类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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) 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:
1
| adb logcat | grep XposedHook
|

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

hook的代码如下,这串代码可以检测到文本,并且打印出相应的堆栈。:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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}") } } }
|
启动后还是在电脑上使用这个命令:
1
| adb logcat | grep XposedHook
|
之后的结果是:

就可以看到这个文本是怎么来的了
点击事件监听
如果我们想要知道点击的按钮都触发了那些操作,就可以通过这种方式实现,代码如下:
原理就是hook “performClick” 这个方法,看会进行那些操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| 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}") } } }
|
效果就是:

改写布局
通过Hook onCreate,然后使用afterHookedMethod是指等页面 onCreate() 执行完之后我们再插手干预。
通过反射调用findViewById()方法,查找 ID 为 0x7f0800de 的 View
将这个控件设为“不可见”状态,即隐藏掉。
代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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查询日志:

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

hook后:

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

直接转化过来就可以:

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