3.Xposed框架入门指南:深入解析Hook内部类与匿名类的实现技巧
在Xposed模块的开发中,我们经常需要Hook各种各样的类和方法来实现特定功能。对于普通的类,大家可能已经驾轻就熟,但当遇到内部类和匿名类时,事情就变得稍微复杂一些。它们的类名表示方式比较特殊,常规的Hook方法可能无法直接奏效。
本文将深入探讨如何使用Xposed框架,精准地Hook Java中的内部类和匿名类,帮助大家在逆向和模块开发的道路上更进一步。
什么是内部类和匿名类?
在开始实战之前,我们先快速回顾一下基础知识。
- 内部类 (Inner Class):定义在另一个类内部的类。它能够访问外部类的所有成员,包括私有成员。在编译后,内部类的
.class文件名通常会是外部类名$内部类名.class。 - 匿名类 (Anonymous Class):没有名字的类。它通常在创建对象的时候,以
new关键字后跟一个接口或类的形式直接定义和实例化。匿名类常用于简化代码,尤其是在实现事件监听等场景。编译后,它的.class文件名通常是外部类名$1.class,$2.class,以此类推,数字代表它是外部类中定义的第几个匿名类。
正是因为它们在编译后特殊的命名方式,导致了我们在Hook时也需要采用特殊的类名表示。
Hook内部类:$符号是关键
Hook内部类的核心在于正确地书写其类名。我们需要使用 $ 符号来连接外部类和内部类。
场景示例
假设我们有如下的Person类,其中包含一个名为People的内部类:
// com/example/hookdemo/Person.java
package com.example.hookdemo;import android.util.Log;public class Person {public Person() {new People(this, "初始名字").say();}// 内部类public class People {private String name;public People(Person person, String name) {this.name = name;}public void say() {Log.d("muyang", "My name is " + this.name);}}
}
我们的目标是Hook People类的构造函数,修改传入的name参数。
Hook代码实现
要Hook内部类的构造函数,我们需要使用 外部类名$内部类名 的格式来定位它。此外,由于非静态内部类的构造函数会隐式地持有一个外部类的引用,所以在声明构造函数参数时,需要将外部类作为第一个参数。
package com.example.hookproject;import android.util.Log;
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 HookEntry implements IXposedHookLoadPackage {@Overridepublic void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {if (!lpparam.packageName.equals("com.example.hookdemo")) {return;}Log.d("muyang", "成功加载目标App");// 获取目标应用的ClassLoaderClassLoader classLoader = lpparam.classLoader;try {// 1. 获取外部类的Class对象Class<?> personClazz = classLoader.loadClass("com.example.hookdemo.Person");// 2. Hook内部类的构造函数// 内部类的完整类名是 "com.example.hookdemo.Person$People"// 非静态内部类的构造函数第一个参数是外部类的实例XposedHelpers.findAndHookConstructor("com.example.hookdemo.Person$People", // 目标内部类名classLoader, // ClassLoaderpersonClazz, // 构造函数第一个参数:外部类实例String.class, // 构造函数第二个参数new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) throws Throwable {super.beforeHookedMethod(param);Log.d("muyang", "成功进入内部类构造函数Hook点");// 修改构造函数的第二个参数 (args数组索引从0开始)param.args[1] = "lsp->小沐";Log.d("muyang", "参数已修改为: " + param.args[1]);}});} catch (Exception e) {Log.e("muyang", "Hook失败", e);}}
}
代码解析:
- 类名: 我们使用
"com.example.hookdemo.Person$People"来精确定位到People这个内部类。 - 构造函数参数:
findAndHookConstructor的参数列表里,除了ClassLoader,我们还需要依次填入构造函数的参数类型。对于People这个非静态内部类,它的构造函数People(Person person, String name)实际上有两个参数,第一个是编译器隐式添加的外部类Person的引用,第二个才是我们代码里看到的String。因此,参数类型要写成personClazz,String.class。 - 修改参数: 在
beforeHookedMethod中,param.args数组包含了调用该构造函数时传入的所有参数。param.args[0]是Person的实例,param.args[1]是String类型的名字,我们将其修改为"lsp->小沐"。
当目标App运行时,Logcat将会输出 My name is lsp->小沐,证明我们的Hook成功了!
Hook匿名类:$数字定位法
Hook匿名类比内部类更棘手一些,因为它们没有明确的类名。我们需要依赖编译器生成的$数字格式的名称来定位它们。
场景示例
假设Person类中有一个创建Runnable匿名类并执行的方法:
// com/example/hookdemo/Person.java (新增部分)
package com.example.hookdemo;import android.util.Log;public class Person {// ... (原有代码)public void doEat() {new Thread(new Runnable() { // 这是Person类中的第一个匿名类@Overridepublic void run() {eatFunc("apple");}public void eatFunc(String food) {Log.d("muyang", "Eating " + food);}}).start();}
}
我们的目标是Hook这个匿名类中的eatFunc方法,将其参数"apple"修改为"hotDog"。
Hook代码实现
这个Runnable是Person类中定义的第一个匿名类,因此它编译后的类名会是 com.example.hookdemo.Person$1。
// 在HookEntry的handleLoadPackage方法中追加以下代码try {// Hook匿名类的方法// 匿名类的完整类名是 "外部类名$数字"XposedHelpers.findAndHookMethod("com.example.hookdemo.Person$1", // 目标匿名类名lpparam.classLoader, // ClassLoader"eatFunc", // 目标方法名String.class, // 方法参数类型new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) throws Throwable {super.beforeHookedMethod(param);Log.d("muyang", "成功进入匿名类方法Hook点");// 修改方法的第一个参数param.args[0] = "hotDog";Log.d("muyang", "匿名类方法参数已修改为: " + param.args[0]);}});
} catch (Exception e) {Log.e("muyang", "Hook匿名类失败", e);
}
代码解析:
- 类名: 我们使用
"com.example.hookdemo.Person$1"来定位这个匿名类。数字1是因为它是Person类中从上到下遇到的第一个匿名类。如果有第二个,那它就是...Person$2,以此类推。 - 定位方法: 使用
findAndHookMethod,并传入完整类名、ClassLoader、方法名"eatFunc"以及它的参数类型String.class。 - 修改参数: 同样在
beforeHookedMethod中,通过param.args[0]访问并修改eatFunc的第一个参数。
当目标App调用doEat()方法时,我们的Hook会生效,Logcat将输出 Eating hotDog。
总结
通过本篇文章,我们掌握了Xposed中两个非常实用的进阶技巧:
- Hook内部类:关键在于使用
外部类名$内部类名的格式,并且在处理非静态内部类的构造函数时,不要忘记第一个隐式参数——外部类的实例。 - Hook匿名类:关键在于使用
外部类名$数字的格式来定位,数字代表了匿名类在外部类中出现的顺序。
掌握了这两个技巧,能让我们在面对一些代码结构复杂的应用时更加从容。希望这篇文章能对大家有所帮助,在Xposed的世界里探索更多可能!
