Frida Hook Java 的高级用法
Hook 构造函数
构造函数在 Java 中用 <init> 表示,是对象创建时的入口。通过 Hook 构造函数,我们可以在对象初始化之前或之后拦截和修改数据。
基本构造函数 Hook
Java.perform(function () {
var User = Java.use("com.example.app.model.User");
// Hook 构造函数 $init
User.$init.overload('java.lang.String', 'int').implementation = function (name, age) {
console.log("[构造函数] 创建 User 对象");
console.log(" 原始参数: name=" + name + ", age=" + age);
// 修改参数后调用原构造函数
this.$init(name, age);
// 修改构造后的字段值
this._vipLevel.value = 999;
console.log(" 已将 VIP 等级修改为: " + this._vipLevel.value);
};
});
Hook 多参数重载构造函数
当一个类有多个构造函数时,需要使用 overload() 指定参数类型。
Java.perform(function () {
var Config = Java.use("com.example.app.Config");
// Hook 无参构造
Config.$init.overload().implementation = function () {
console.log("[Config] 无参构造被调用");
this.$init();
};
// Hook 带 String 参数的构造
Config.$init.overload('java.lang.String').implementation = function (path) {
console.log("[Config] 带参构造: " + path);
this.$init(path);
console.log("[Config] 初始化后配置项数: " + this._items.value.size());
};
});
Hook 重载方法(Overload)
Java 方法重载(Overload)允许同名方法拥有不同参数列表。Frida 中必须通过 overload() 指定参数类型来精确匹配目标方法。
精确匹配重载方法
Java.perform(function () {
var Crypto = Java.use("com.example.app.Crypto");
// 方法签名为: String encrypt(String data)
Crypto.encrypt.overload('java.lang.String').implementation = function (data) {
console.log("[encrypt] String 版本被调用");
console.log(" 输入: " + data);
var result = this.encrypt(data);
console.log(" 输出: " + result);
return result;
};
// 方法签名为: String encrypt(String data, String key)
Crypto.encrypt.overload('java.lang.String', 'java.lang.String')
.implementation = function (data, key) {
console.log("[encrypt] 双参数版本被调用");
console.log(" data: " + data);
console.log(" key: " + key);
var result = this.encrypt(data, key);
console.log(" 输出: " + result);
return result;
};
// 方法签名为: byte[] encrypt(byte[] data, byte[] key, int mode)
Crypto.encrypt.overload('[B', '[B', 'int')
.implementation = function (data, key, mode) {
console.log("[encrypt] 字节数组版本被调用");
console.log(" mode: " + mode);
// 将 byte[] 转为可读的 hex 字符串
var dataBytes = Java.array('byte', data);
var hex = "";
for (var i = 0; i < dataBytes.length; i++) {
hex += ("0" + (dataBytes[i] & 0xFF).toString(16)).slice(-2);
}
console.log(" data(hex): " + hex);
var result = this.encrypt(data, key, mode);
return result;
};
});
使用 Java.available() 检查方法签名
当不确定方法有哪些重载时,可以通过反射获取方法的参数类型信息。
Java.perform(function () {
var Target = Java.use("com.example.app.Target");
// 获取类的所有方法
var methods = Target.class.getDeclaredMethods();
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
console.log("方法: " + method.getName());
var paramTypes = method.getParameterTypes();
for (var j = 0; j < paramTypes.length; j++) {
console.log(" 参数" + j + ": " + paramTypes[j].getName());
}
console.log(" 返回值: " + method.getReturnType().getName());
console.log("---");
}
});
枚举所有已加载的类和方法
在逆向分析时,我们经常需要浏览某个包下有哪些类和方法,找到潜在的 Hook 目标。
枚举特定包下的所有类
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (className) {
// 过滤特定包名
if (className.indexOf("com.example.app.security") !== -1) {
console.log("[类] " + className);
}
},
onComplete: function () {
console.log("[*] 类枚举完成");
}
});
});
枚举类的所有方法
Java.perform(function () {
var Target = Java.use("com.example.app.security.Signature");
// 获取所有已声明的方法
var methods = Target.class.getDeclaredMethods();
console.log("[*] " + Target.class.getName() + " 的方法列表:");
for (var i = 0; i < methods.length; i++) {
var m = methods[i];
var modifiers = Java.use("java.lang.reflect.Modifier");
var isPrivate = modifiers.isPrivate(m.getModifiers());
console.log(" " + (isPrivate ? "[private] " : "") + m.getName() +
"(" + m.getParameterTypes().length + "个参数) -> " +
m.getReturnType().getName());
}
});
Hook 动态加载的类
许多 APP 使用 DexClassLoader 或 PathClassLoader 动态加载 DEX 文件,这些类在 APP 启动时并不存在。Hook 动态加载的类需要解决时机问题。
解决时机问题
// 方法一:延迟执行 Hook
setTimeout(function () {
Java.perform(function () {
try {
var DynamicClass = Java.use("com.dynamic.loaded.PluginClass");
DynamicClass.execute.implementation = function () {
console.log("[动态类] execute 被调用");
return this.execute();
};
console.log("[+] 动态类 Hook 成功");
} catch (e) {
console.log("[-] 动态类尚未加载: " + e);
}
});
}, 5000); // 延迟 5 秒,等待动态 DEX 加载
// 方法二:Hook ClassLoader 监控新类的加载
Java.perform(function () {
var ClassLoader = Java.use("java.lang.ClassLoader");
ClassLoader.loadClass.overload('java.lang.String').implementation = function (name) {
var clazz = this.loadClass(name);
if (name.indexOf("com.dynamic") !== -1) {
console.log("[ClassLoader] 加载类: " + name);
// 类加载后立即 Hook
try {
var LoadedClass = Java.use(name);
if (LoadedClass.process) {
LoadedClass.process.implementation = function (data) {
console.log("[动态类 Hook] process: " + data);
return this.process(data);
};
}
} catch (e) {
// ignore
}
}
return clazz;
};
});
Hook 匿名内部类
匿名内部类在编译后以 $数字 的形式命名,常见于回调接口的实现(如 OnClickListener、Runnable 等)。
Java.perform(function () {
// 先枚举找到目标匿名类
var targetClasses = [];
Java.enumerateLoadedClasses({
onMatch: function (name) {
// 匹配特定外部类的匿名内部类
if (name.indexOf("com.example.app.LoginActivity$") !== -1 &&
name.indexOf("$Adapter") === -1) {
targetClasses.push(name);
}
},
onComplete: function () {}
});
// 逐个尝试 Hook
targetClasses.forEach(function (className) {
try {
var AnonClass = Java.use(className);
// 检查是否包含目标方法
if (AnonClass.onResponse) {
AnonClass.onResponse.implementation = function (response) {
console.log("[匿名类] " + className + " onResponse");
console.log(" 响应: " + response);
return this.onResponse(response);
};
console.log("[+] 已 Hook: " + className);
}
} catch (e) {
// 忽略不匹配的类
}
});
});
Hook 系统类(Activity/Service)
Hook Android 框架层的系统类是分析 APP 行为的重要手段。
Hook Activity 生命周期
Java.perform(function () {
var Activity = Java.use("android.app.Activity");
// Hook onCreate
Activity.onCreate.implementation = function (savedInstanceState) {
console.log("[Activity] onCreate: " + this.getClass().getName());
this.onCreate(savedInstanceState);
};
// Hook onResume(每次页面可见都会调用)
Activity.onResume.implementation = function () {
var activityName = this.getClass().getName();
console.log("[Activity] onResume: " + activityName);
this.onResume();
};
// Hook startActivityForResult(追踪页面跳转)
Activity.startActivityForResult.overload(
'android.content.Intent', 'int'
).implementation = function (intent, requestCode) {
console.log("[Activity] startActivityForResult");
console.log(" 目标: " + intent.getComponent());
console.log(" requestCode: " + requestCode);
this.startActivityForResult(intent, requestCode);
};
});
Hook Service
Java.perform(function () {
var Service = Java.use("android.app.Service");
Service.onCreate.implementation = function () {
console.log("[Service] onCreate: " + this.getClass().getName());
this.onCreate();
};
Service.onStartCommand.implementation = function (intent, flags, startId) {
console.log("[Service] onStartCommand: " + this.getClass().getName());
if (intent !== null) {
console.log(" Action: " + intent.getAction());
}
return this.onStartCommand(intent, flags, startId);
};
});
Hook 第三方 SDK
Hook 微信 SDK 示例
Java.perform(function () {
// Hook 微信支付回调
try {
var WXPayEntryActivity = Java.use(
"com.tencent.mm.opensdk.pay.WXPayEntryActivity"
);
console.log("[+] 找到微信支付入口 Activity");
// Hook onActivityResult 捕获支付结果
WXPayEntryActivity.onActivityResult.implementation = function (
requestCode, resultCode, data) {
console.log("[微信支付] requestCode=" + requestCode +
" resultCode=" + resultCode);
if (data) {
console.log("[微信支付] data: " + data.getExtras());
}
this.onActivityResult(requestCode, resultCode, data);
};
} catch (e) {
console.log("[-] 未找到微信 SDK: " + e);
}
});
Hook 支付宝 SDK 示例
Java.perform(function () {
try {
var PayTask = Java.use("com.alipay.sdk.app.PayTask");
// Hook 支付方法,截获订单信息
PayTask.payV2.overload('java.lang.String', 'boolean')
.implementation = function (orderInfo, showLoading) {
console.log("[支付宝] payV2 被调用");
console.log("[支付宝] 订单信息: " + orderInfo);
console.log("[支付宝] 显示加载: " + showLoading);
var result = this.payV2(orderInfo, showLoading);
console.log("[支付宝] 支付结果: " + result);
return result;
};
} catch (e) {
console.log("[-] 未找到支付宝 SDK: " + e);
}
});
线程安全的 Hook 回调
在多线程环境中,Hook 回调可能在不同线程上被触发,需要确保线程安全。
线程安全模式
Java.perform(function () {
var NetworkUtil = Java.use("com.example.app.util.NetworkUtil");
NetworkUtil.sendRequest.implementation = function (url, params) {
// 获取当前线程信息
var Thread = Java.use("java.lang.Thread");
var threadName = Thread.currentThread().getName();
console.log("[线程: " + threadName + "] sendRequest");
console.log(" URL: " + url);
// 使用 Java 方法在主线程执行敏感操作
var mainHandler = Java.use("android.os.Handler");
var Looper = Java.use("android.os.Looper");
Java.scheduleOnMainThread(function () {
// 主线程中安全地执行 Java 操作
var result = NetworkUtil.parseResponse("{\"status\":\"ok\"}");
console.log("[主线程] 解析结果: " + result);
});
// 当前线程继续正常执行
return this.sendRequest(url, params);
};
});
防止重复 Hook
多次执行 Frida 脚本可能导致重复 Hook,需要做防护。
var hookedMethods = {};
function safeHook(className, methodName, callback) {
var key = className + "." + methodName;
if (hookedMethods[key]) {
console.log("[跳过] 已经 Hook 过: " + key);
return;
}
Java.perform(function () {
try {
var Clazz = Java.use(className);
Clazz[methodName].implementation = callback;
hookedMethods[key] = true;
console.log("[+] Hook 成功: " + key);
} catch (e) {
console.log("[-] Hook 失败: " + key + " - " + e);
}
});
}
// 使用安全 Hook
safeHook("com.example.app.Login", "verify",
function (token) {
console.log("[verify] token=" + token);
return this.verify(token);
}
);
总结
本文深入介绍了 Frida Hook Java 的高级用法,从构造函数和重载方法的精确匹配,到动态类加载的时机处理,再到系统类和第三方 SDK 的实战 Hook。掌握这些技术后,你可以在逆向分析中灵活地拦截和修改任何 Java 层的逻辑。下一篇文章将探讨如何利用 Frida 辅助分析 OLLVM 混淆的 Native 代码。