配置 Xposed 模块的 AndroidManifest.xml:
XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.package.name">
<application
android:label="Your Xposed Module Name"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="这是一个用于监控字符串操作的 Xposed 模块" />
<meta-data
android:name="xposedminversion"
android:value="53" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />
</application>
</manifest>
在app/src/main/assets创建一个xposed_init文件 。
xposed_init 文件是 Xposed 模块必需的一个配置文件,它用来指定模块的入口类。这个文件需要包含你的 Xposed 模块的主类的完整类名(包含包名):
XML
your.package.name.MainHook
app/build.gradle配置一下:
XML
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
// Xposed Framework API
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
// 如果需要使用 LSPosed API(可选)
// compileOnly 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
}
LoadPackageParam (简称 lpparam) 是 Xposed 框架中的一个重要参数类,它包含了被加载的应用程序的相关信息。
LoadPackageParam 是 XC_LoadPackage 的一个内部类,以下是它的所有成员变量:
java
public static final class LoadPackageParam extends XCallback.Param {
/** 应用的包名 */
public String packageName;
/** 进程的名称 */
public String processName;
/** 应用的 ClassLoader */
public ClassLoader classLoader;
/** 应用的 Application 对象 */
public ApplicationInfo appInfo;
/** 是否是第一次加载 */
public boolean isFirstApplication;
/** 系统服务的进程名(如果是系统服务) */
public String[] initiatingPackages;
/** 系统服务的进程名(如果是系统服务) */
public String initiatingPackage;
}
java
// 获取当前加载的应用包名
String pkgName = lpparam.packageName;
// 例如: "com.android.chrome"
java
// 获取当前进程名
String procName = lpparam.processName;
// 可能是: "com.android.chrome"
// 或者: "com.android.chrome:sandbox"
类加载器
- 它负责找到并加载你需要的工具(类)
// 相当于说"帮我找到这个工具"
Class<?> targetClass = lpparam.classLoader.loadClass("com.example.Target");
// 找到后就可以使用这个类了
XposedHelpers.findAndHookMethod(targetClass, "方法名", ...);
java
// 获取应用的类加载器
ClassLoader loader = lpparam.classLoader;
// 用于加载目标应用中的类
Class<?> targetClass = loader.loadClass("com.example.Target");
java
// 获取应用的信息
ApplicationInfo info = lpparam.appInfo;
// 可以获取很多应用相关信息
String sourceDir = info.sourceDir; // APK 路径
String nativeLibDir = info.nativeLibraryDir; // native库路径
int targetSdkVersion = info.targetSdkVersion; // 目标SDK版本
XposedHelpers
查找和 Hook 方法
java
// 1. 基本的方法 Hook
XposedHelpers.findAndHookMethod(
"com.example.Class", // 类名
lpparam.classLoader, // 类加载器
"methodName", // 方法名
String.class, // 参数类型1
int.class, // 参数类型2
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
// 方法执行前的处理
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
// 方法执行后的处理
}
}
);
// 2. Hook 构造方法
XposedHelpers.findAndHookConstructor(
targetClass, // 类
String.class, // 参数类型
new XC_MethodHook() { ... }
);
获取/设置字段值
java
// 获取字段值
Object fieldValue = XposedHelpers.getObjectField(object, "fieldName");
int intValue = XposedHelpers.getIntField(object, "fieldName");
String stringValue = XposedHelpers.getStaticObjectField(class, "fieldName");
// 设置字段值
XposedHelpers.setObjectField(object, "fieldName", newValue);
XposedHelpers.setIntField(object, "fieldName", 123);
XposedHelpers.setStaticObjectField(class, "fieldName", newValue);
调用方法
java
// 调用实例方法
Object result = XposedHelpers.callMethod(object, "methodName", arg1, arg2);
// 调用静态方法
Object result = XposedHelpers.callStaticMethod(class, "methodName", arg1, arg2);
创建新实例
java
// 创建对象实例
Object newInstance = XposedHelpers.newInstance(class);
Object newInstance = XposedHelpers.newInstance(class, "构造参数1", "构造参数2");
查找类
java
// 查找类
Class<?> class = XposedHelpers.findClass("com.example.Class", lpparam.classLoader);
Hook Api
GravityBox
GravityBox 是一个非常著名的 Xposed 模块,它是一个系统级的调整工具箱。
-
更改赋予 Android 终端硬件按钮的功能
-
修改状态栏的外观和显示的选项
-
改变可以在手机或平板电脑屏幕上显示的亮度(这个是积极的,尤其是最小的,以节省电池)
-
直接应用程序分配给设备的触摸按钮
-
管理终端 RAM 的使用,了解此应用程序的消耗
Xposed Hook
- choose: 查找某个类的所有实例对象
- enumerateClassLoaders: 查找所有的类加载器
Java.choose() 和 Java.enumerateClassLoaders() 是 Frida 中两个不同的 API,它们的用途不同:
javascript
// 用于查找指定类的所有实例
Java.choose("com.example.TargetClass", {
onMatch: function(instance) {
// 每找到一个实例就会调用一次
console.log("找到实例:", instance);
console.log("实例字段值:", instance.fieldName);
},
onComplete: function() {
// 搜索完成时调用
console.log("搜索完成");
}
});
javascript
// 用于列举所有的类加载器
Java.enumerateClassLoaders({
onMatch: function(loader) {
// 每找到一个类加载器就会调用一次
console.log("类加载器:", loader);
// 可以尝试用这个加载器加载类
try {
loader.loadClass("com.example.TargetClass");
console.log("这个加载器可以加载目标类");
} catch(e) {
console.log("这个加载器无法加载目标类");
}
},
onComplete: function() {
console.log("搜索完成");
}
});
主要区别:
- choose: 查找某个类的所有实例对象
- enumerateClassLoaders: 查找所有的类加载器
简单说:
- 想找对象用 choose
- 想找类加载器用 enumerateClassLoaders
javascript
// 1. 最简单的使用方式
Java.perform(function() {
// 获取默认的类加载器
var targetClass = Java.use("com.example.TargetClass");
});
// 2. 当默认加载器找不到类时,可以遍历所有"图书管理员"
Java.enumerateClassLoaders({
onMatch: function(loader) {
try {
// 让每个"管理员"都尝试找这本"书"
loader.loadClass("com.example.TargetClass");
console.log("找到了!这个管理员可以找到这本书");
} catch(e) {
console.log("这个管理员找不到这本书");
}
}
});
让我用更简单的方式解释 IXposedHookLoadPackage:
想象你是一个保安,站在商场门口:
- 每当有人(应用)要进商场时,你都会被通知
- 你可以检查他们的身份(包名),决定要不要对他们做什么
javascript
// 你就是这个保安
public class MainHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
// 1. 检查是不是你要找的人
if (lpparam.packageName.equals("com.taobao.qianniu")) {
// 是千牛应用
XposedBridge.log("发现千牛启动了!");
// 2. 对这个应用做一些事
// 比如:监控它的某个方法
XposedHelpers.findAndHookMethod(
"com.taobao.qianniu.MainActivity", // 类名
lpparam.classLoader, // 类加载器
"onCreate", // 方法名
Bundle.class, // 参数类型
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log("千牛正在启动...");
}
}
);
}
}
}
handleLoadPackage 是 IXposedHookLoadPackage 接口中唯一需要实现的方法。它的作用是:
javascript
public class MainHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
// 这个方法会在每个应用启动时被调用
// 1. 可以获取应用的包名
String packageName = lpparam.packageName;
// 2. 可以获取应用的类加载器
ClassLoader classLoader = lpparam.classLoader;
// 3. 实际使用示例
if (packageName.equals("com.taobao.qianniu")) {
// 找到目标应用后,就可以开始 Hook 了
XposedBridge.log("找到千牛了!");
// Hook 示例
XposedHelpers.findAndHookMethod(
"目标类名",
classLoader,
"方法名",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
// 方法执行前的处理
}
}
);
}
}
}
// Xposed 模块中使用 XposedBridge.log
XposedBridge.log("发现目标应用:" + TARGET_PACKAGE); // 这是 Java 代码
// Frida 脚本中使用 console.log
console.log("发现目标应用:" + TARGET_PACKAGE); // 这是 JavaScript 代码
XC_MethodHook 是 Xposed 框架中用来 Hook 方法的核心类。它让你可以在方法执行前后添加自己的代码。
XposedHelpers 最常用的方法
java
// 1. Hook 相关
XposedHelpers.findAndHookMethod( // Hook 实例方法
className, // 类名
classLoader, // 类加载器
methodName, // 方法名
parameterTypes, // 参数类型
callback // 回调
);
XposedHelpers.findAndHookConstructor( // Hook 构造方法
className,
classLoader,
parameterTypes,
callback
);
// 2. 查找类
Class<?> cls = XposedHelpers.findClass(
"com.example.TargetClass",
classLoader
);
// 3. 调用方法
// 调用实例方法
Object result = XposedHelpers.callMethod(
object, // 对象
"methodName", // 方法名
params // 参数
);
// 调用静态方法
Object result = XposedHelpers.callStaticMethod(
className, // 类名
"methodName", // 方法名
params // 参数
);
// 4. 获取/设置字段
// 获取实例字段
Object value = XposedHelpers.getObjectField(
object, // 对象
"fieldName" // 字段名
);
// 获取静态字段
Object value = XposedHelpers.getStaticObjectField(
className, // 类名
"fieldName" // 字段名
);
// 设置实例字段
XposedHelpers.setObjectField(
object, // 对象
"fieldName", // 字段名
newValue // 新值
);
// 设置静态字段
XposedHelpers.setStaticObjectField(
className, // 类名
"fieldName", // 字段名
newValue // 新值
);
// 5. 创建新实例
Object newObj = XposedHelpers.newInstance(
className, // 类名
params // 构造参数
);
setAccessible 是 Java 反射中用来绕过访问限制的方法。它可以让你访问私有成员:
java
// 1. 基本用法
Field field = targetClass.getDeclaredField("privateField");
field.setAccessible(true); // 允许访问私有字段
Object value = field.get(object); // 现在可以访问了
// 2. 实际例子
try {
// 获取私有方法
Method method = targetClass.getDeclaredMethod("privateMethod");
method.setAccessible(true); // 设置可访问
method.invoke(object); // 调用私有方法
// 获取私有字段
Field field = targetClass.getDeclaredField("privateField");
field.setAccessible(true); // 设置可访问
field.set(object, newValue); // 修改私有字段值
} catch (Exception e) {
XposedBridge.log("访问失败: " + e);
}
- Java.use().$new(): 创建新的 Java 对象
- Java.cast(): 转换对象类型
- Java.choose(): 查找已存在的对象实例
$new() 里面的参数对应 Java 类的构造函数参数。让我用具体例子说明:
javascript
Java.perform(function() {
// 1. 无参数构造函数
var StringBuilder = Java.use("java.lang.StringBuilder");
var sb1 = StringBuilder.$new(); // 等同于 new StringBuilder()
// 2. 带参数构造函数
var sb2 = StringBuilder.$new("Hello"); // 等同于 new StringBuilder("Hello")
// 3. String 类例子
var String = Java.use("java.lang.String");
var str1 = String.$new(); // new String()
var str2 = String.$new("Hello"); // new String("Hello")
// 4. 自定义类例子
var MyClass = Java.use("com.example.MyClass");
// 如果 MyClass 构造函数需要两个参数:String 和 int
var myObj = MyClass.$new("参数1", 123); // new MyClass("参数1", 123)
});
下面是Java.cast(): 转换对象类型:
- View 就像是一个"容器"
- 通过 cast 告诉系统:"这个容器其实是个按钮"
- 转换后就可以用按钮特有的功能了
javascript
Java.perform(function() {
// 1. 找到一个普通的 View
Java.choose("com.example.MainActivity", {
onMatch: function(activity) {
// findViewById 返回的是 View 类型
var view = activity.findViewById(123); // 这时候只能用 View 的方法
// 把 View 转成 Button
var button = Java.cast(view, Java.use("android.widget.Button"));
// 现在可以用 Button 特有的方法了
button.setText("点击我"); // 设置按钮文字
button.setEnabled(true); // 设置按钮可点击
button.setOnClickListener(/* ... */); // 设置点击事件
}
});
});
javascript
// TextView 转换
var textView = Java.cast(view, Java.use("android.widget.TextView"));
textView.setText("这是文本");
// ImageView 转换
var imageView = Java.cast(view, Java.use("android.widget.ImageView"));
imageView.setImageResource(R.drawable.icon);
// EditText 转换
var editText = Java.cast(view, Java.use("android.widget.EditText"));
editText.setHint("请输入...");
主要区别:
- $new() 是 Frida (JavaScript) 的方法
- newInstance 是 Xposed (Java) 的方法
- 功能是一样的,都是创建新对象
简单说:
- 在 Frida 脚本中用 $new()
- 在 Xposed 模块中用 newInstance
Frida 的 $new()
javascript
Java.perform(function() {
// Frida 方式创建对象
var String = Java.use("java.lang.String");
var str = String.$new("Hello"); // 创建字符串
var ArrayList = Java.use("java.util.ArrayList");
var list = ArrayList.$new(); // 创建列表
});
Xposed 的 newInstance
java
// Xposed 方式创建对象
String str = (String) XposedHelpers.newInstance(
String.class, // 类
"Hello" // 参数
);
ArrayList list = (ArrayList) XposedHelpers.newInstance(
ArrayList.class // 类
);
简单说就是三步:
- 找到类 (findClass)
- 创建实例 (newInstance)
- 调用方法 (callMethod)
java
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
// 1. 首先查找类
Class<?> targetClass = XposedHelpers.findClass(
"com.example.TargetClass", // 类名
lpparam.classLoader // 类加载器
);
// 2. 创建类的实例
Object instance = XposedHelpers.newInstance(targetClass);
// 如果构造函数有参数
// Object instance = XposedHelpers.newInstance(targetClass, "参数1", 123);
// 3. 调用实例的方法
XposedHelpers.callMethod(
instance, // 实例对象
"methodName", // 方法名
"参数1", // 方法参数
123 // 更多参数
);
}
NanoHTTPD
NanoHTTPD 是一个轻量级的 HTTP 服务器库。