为什么需要「模板」
Frida 的 API 不算多,但每次写脚本都要重新想「怎么 hook 构造函数」「怎么改返回值」「怎么处理重载」,效率很低。
更麻烦的是,网上抄来的脚本经常跑不通——不是语法错,而是方法签名不对、类加载器找不到类、或者目标 App 用了 Kotlin 的伴生对象。
本文把最常见的 10 种 Hook 场景做成可直接改用的模板。所有模板都遵循同一个骨架,先记住这个:
Java.perform(function () {
var Cls = Java.use("com.example.app.Target");
Cls.method.implementation = function (arg1) {
console.log("called:", arg1);
var ret = this.method(arg1); // 调原方法
console.log("return:", ret);
return ret; // 原样返回
};
});
三件事:Java.perform 保证在 Java 运行时里执行、Java.use 拿到类、implementation 替换方法体。在替换体里一定要调用原方法(除非你就是要改行为),否则会破坏 App 逻辑。
环境准备(frida-server 部署、版本对齐)见《Frida 抓包实战》,本文不重复。
模板 1:打印参数与返回值(最常用)
Java.perform(function () {
var Cls = Java.use("com.example.app.LoginActivity");
Cls.checkPassword.implementation = function (user, pwd) {
console.log("[+] checkPassword(", user, ",", pwd, ")");
var ret = this.checkPassword(user, pwd);
console.log("[+] return:", ret);
return ret;
};
});
用途:快速搞清楚一个方法在什么时候被调用、传了什么、回了什么。这是所有逆向工作的起点。
模板 2:Hook 重载方法(最容易报错)
同名方法有多个签名时,必须用 overload 指定:
Java.perform(function () {
var Cls = Java.use("com.example.app.Net");
// 签名写法:基本类型用缩写,对象用全限定名
Cls.request.overload("java.lang.String", "int").implementation = function (url, timeout) {
console.log("[+] request:", url, timeout);
return this.request(url, timeout);
};
});
签名怎么查?两种办法:
// 在 Frida REPL 里直接打印
Java.use("com.example.app.Net").request.overloads.forEach(function (o) {
console.log(o.argumentTypes.join(", "));
});
或用 jadx 打开 APK,右键方法「Copy as frida snippet」,直接得到签名。
常见错误:overload not found。八成是基本类型写错——int 要写 "int",boolean 要写 "boolean",数组要写 "[B" 这种 JVM 描述符。
模板 3:Hook 构造函数
构造函数在 Frida 里叫 $init:
Java.perform(function () {
var Cls = Java.use("com.example.app.Session");
Cls.$init.overload("java.lang.String").implementation = function (token) {
console.log("[+] new Session token =", token);
return this.$init(token);
};
});
用途:抓对象创建时机,尤其是密钥、token、配置在构造时被赋值的场景。
模板 4:改返回值(绕过校验)
Java.perform(function () {
var Cls = Java.use("com.example.app.RootCheck");
Cls.isRooted.implementation = function () {
console.log("[+] isRooted() -> forced false");
return false; // 不调用原方法,直接返回
};
});
注意这里是故意不调原方法。改返回值的场景包括:root 检测、签名校验、VIP 判断、试用期判断。
提示:很多 App 会多处校验(Java 层 + native 层 + 服务端),改一处往往不够。
模板 5:读写字段
Java.perform(function () {
var Cls = Java.use("com.example.app.User");
// 读静态字段
console.log("[+] static field:", Cls.sToken.value);
// 改实例字段:先找到实例
Java.choose("com.example.app.User", {
onMatch: function (obj) {
console.log("[+] before:", obj.balance.value);
obj.balance.value = 99999;
console.log("[+] after:", obj.balance.value);
},
onComplete: function () { console.log("[+] done"); }
});
});
模板 6:遍历所有实例(Java.choose)
Java.perform(function () {
Java.choose("com.example.app.Order", {
onMatch: function (instance) {
console.log("[+] order id:", instance.getId());
},
onComplete: function () { console.log("[+] total scanned"); }
});
});
警告:Java.choose 会扫描堆,对象多时非常慢甚至卡死。只在必要时用,能通过 Hook 拿到的就别扫堆。
模板 7:Hook 接口 / 抽象方法
Java.perform(function () {
var Iface = Java.use("com.example.app.Callback");
Iface.onResult.implementation = function (code, data) {
console.log("[+] onResult:", code, data);
return this.onResult(code, data);
};
});
Frida 可以 Hook 接口声明的方法,实际拦截的是实现类的该方法。如果实现类是匿名内部类,类名带 $1,可以用 Java.enumerateLoadedClasses 找出来。
模板 8:Hook 静态方法
Java.perform(function () {
var Cls = Java.use("com.example.app.Utils");
Cls.md5.implementation = function (input) {
console.log("[+] md5(", input, ")");
var ret = this.md5(input);
console.log("[+] ->", ret);
return ret;
};
});
静态方法的 this 是类包装对象,写法与实例方法一致,不需要特殊处理。
模板 9:Hook native 函数(.so)
Java.perform(function () {
// 方式一:按导出符号找
var addr = Module.findExportByName("libnative.so", "Java_com_example_Native_check");
if (addr === null) { console.log("[-] symbol not found"); return; }
Interceptor.attach(addr, {
onEnter: function (args) {
console.log("[+] arg0:", args[0]);
console.log("[+] arg1:", args[1].toInt32());
},
onLeave: function (retval) {
console.log("[+] ret:", retval.toInt32());
retval.replace(ptr(1)); // 强制返回 1
}
});
});
要点:
- JNI 函数的第一个参数永远是
JNIEnv*,第二个是jobject/jclass,业务参数从args[2]开始。 - 符号被 strip 掉时,
findExportByName会返回null,只能按偏移或特征码定位。 retval.replace()对指针/整数有效,对浮点要用retval.replace(1.0)的对应写法。
模板 10:打印调用栈(定位谁调了它)
Java.perform(function () {
var Cls = Java.use("com.example.app.Crypto");
Cls.encrypt.implementation = function (data) {
console.log("[+] encrypt called from:\n" +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join("\n"));
return this.encrypt(data);
};
});
用途:搞清楚「这个加密函数是谁触发的」,比盲目 Hook 一百个方法高效得多。
补充:类找不到怎么办
java.lang.ClassNotFoundException 是最高频的报错。原因是类在 App 自己的 ClassLoader 里,Frida 默认用系统 ClassLoader 找不到。
Java.perform(function () {
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
var factory = Java.ClassFactory.get(loader);
var Cls = factory.use("com.example.app.Hidden");
Cls.secret.implementation = function () {
console.log("[+] secret:", this.secret());
return this.secret();
};
console.log("[+] hooked with loader: " + loader);
} catch (e) { /* 换下一个 loader */ }
},
onComplete: function () { console.log("[+] scan done"); }
});
});
十种模板速查
| # | 场景 | 关键写法 |
|---|---|---|
| 1 | 看参数返回值 | Cls.m.implementation = function(...) |
| 2 | 重载方法 | .overload("java.lang.String","int") |
| 3 | 构造函数 | Cls.$init |
| 4 | 改返回值 | 直接 return false,不调原方法 |
| 5 | 读写字段 | obj.field.value |
| 6 | 遍历实例 | Java.choose |
| 7 | 接口方法 | Hook 接口名,拦截实现 |
| 8 | 静态方法 | 写法同实例方法 |
| 9 | native 函数 | Interceptor.attach |
| 10 | 调用栈 | Thread.backtrace |
常见错误对照
| 报错 | 原因 | 处理 |
|---|---|---|
ClassNotFoundException |
类在 App 的 ClassLoader | 用模板里的 Java.enumerateClassLoaders |
overload not found |
签名写错 | 打印 overloads 或从 jadx 复制 |
TypeError: not a function |
方法名写错,或不是 Java 方法 | 确认是 .method 而非字段 |
expected a pointer |
native 参数类型用错 | args[1].toInt32() 或 ptr() |
| Hook 成功但 App 崩溃 | 原方法没调用,破坏了状态 | 检查是否漏了 this.method(...) |
| 脚本没生效 | 注入时机太晚 | 用 frida -U -f 包名 -l script.js 从启动注入 |
小结
- 骨架就一个:
Java.perform→Java.use→implementation。 - 先打印,再修改。搞清楚调用关系之前不要急着改返回值。
- 签名是最大障碍,用 jadx 复制比手写靠谱。
- 类找不到就换 ClassLoader,这是 Android 逆向的必修课。
延伸阅读:
免责声明:本文技术内容仅用于自有 App 的调试、授权范围内的安全测试与逆向学习。对他人 App 进行破解、篡改或数据窃取可能违反《网络安全法》与《著作权法》,后果自负。
- 1、本网站名称: 星宇安全实验室
- 2、本站永久网址:https://bk.zhaozhiqiang.pw/
- 3、本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
- 4、本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
- 5、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
- 6、本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
- 7、文章版权归作者所有,未经允许请勿转载。
















暂无评论内容