leolovenet/frida-android-hook-utils
GitHub: leolovenet/frida-android-hook-utils
Frida Android Java 层 hook 辅助工具库,提供方法 hook、日志输出、对象检查和调用栈打印等常用调试功能的封装。
Stars: 0 | Forks: 0
# frida-android-hook-utils
`frida-android-hook-utils` 是一个面向 Android Java 层调试的 Frida 辅助脚本。
项目核心文件是 `hook.js`。它会在 Frida 运行时导出一组常用工具函数,用来减少重复的 hook 样板代码,自动打印方法参数、返回值、`this` 对象、调用栈,并提供 Java 类加载、对象检查、重载选择、返回值替换等辅助能力。
本项目不是 npm 包,也不需要构建。使用时将 `hook.js` 作为 Frida 预加载脚本放在目标脚本之前加载即可。
## 文件
hook.js # Frida Android Java hook helper
README.md # Usage and API documentation
## 快速开始
将 `hook.js` 放在你的目标脚本之前加载:
frida -U -f com.example.app -l hook.js -l target-script.js
或 attach 到已运行进程:
frida -U -n com.example.app -l hook.js -l target-script.js
加载顺序很重要:`hook.js` 必须在 `target-script.js` 之前加载,这样目标脚本里才能直接使用 `hook`、`hooks`、`JavaUse`、`inspectVal` 等函数。
`target-script.js` 示例:
Java.perform(function () {
hook("com.example.demo.UserService#getUser(java.lang.String)");
});
`hook.js` 会导出两种调用方式:
hook(...);
Hook.hook(...);
常用函数也会直接挂到全局:
hook;
hooks;
JavaUse;
inspectVal;
完整工具集合挂在 `globalThis.Hook` 上。
## 核心函数:hook
`hook(opts)` 是本库的核心函数,用于安装 Java 方法 hook。
### 最简用法
Java.perform(function () {
hook("com.example.demo.UserService#getUser(java.lang.String)");
});
这个写法会 hook:
com.example.demo.UserService#getUser(java.lang.String)
默认行为包括:
- 调用原方法。
- 打印方法参数。
- 打印返回值。
- 打印 `this` 对象和字段。
- 打印 Java 调用栈。
- 返回原方法返回值。
### 对象写法
Java.perform(function () {
hook({
C: "com.example.demo.UserService",
Method: "getUser",
Overload: "(java.lang.String)",
});
});
参数说明:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `C` | `string` 或 Frida class wrapper | 类名,或 `JavaUse()` / `Java.use()` 返回的类包装对象 |
| `Method` | `string` | 方法名 |
| `Overload` | `string` | 指定重载,格式为 `"(type1,type2,...)"` |
| `Message` | `string` | 日志标题;不传时使用 `Class#method` |
| `OnlyListOverloads` | `boolean` | 只列出重载,不安装 hook |
| `OverloadsCB` | `function` | 自定义选择某个 overload |
| `FnImpl` | `function` | 完全自定义 Frida implementation |
### hook 全部重载
不传 `Overload` 时,`hook` 会尝试 hook 该方法的全部重载:
Java.perform(function () {
hook({
C: "com.example.demo.UserService",
Method: "save",
});
});
如果方法重载很多,建议先列出重载,再指定目标重载。
### 只列出重载
Java.perform(function () {
hook({
C: "com.example.demo.UserService",
Method: "save",
OnlyListOverloads: true,
});
});
输出中会列出 Frida 识别到的 overload,之后可以把参数类型填入 `Overload`:
hook({
C: "com.example.demo.UserService",
Method: "save",
Overload: "(java.lang.String,int)",
});
### 字符串简写格式
`hook` 支持把类名、方法名、重载写在一个字符串里:
hook("com.example.demo.UserService#save(java.lang.String,int)");
格式:
#(,,...)
无参方法使用空括号:
hook("com.example.demo.UserService#refresh()");
### 控制默认输出
`hook` 默认使用 `wrap(opts)` 生成 implementation。下面这些参数会传给 `wrap`,用于控制日志输出:
| 参数 | 说明 |
| --- | --- |
| `NoOutput` | 关闭所有默认日志输出 |
| `NoArgsOutput` | 不打印参数 |
| `NoRetvalOutput` | 不打印返回值 |
| `NoThisOutput` | 不打印 `this` |
| `NoStackTraceOutput` | 不打印调用栈 |
| `WithSuperClass` | 检查对象字段时包含父类字段 |
示例:只看参数和返回值,不看 `this` 和调用栈:
hook({
C: "com.example.demo.UserService",
Method: "getUser",
Overload: "(java.lang.String)",
NoThisOutput: true,
NoStackTraceOutput: true,
});
示例:只安装 hook,不打印默认日志:
hook({
C: "com.example.demo.UserService",
Method: "getUser",
Overload: "(java.lang.String)",
NoOutput: true,
});
### 过滤输出
`OutputFilter(thisObj, args, ret, message)` 可以决定本次调用是否打印日志。
返回 `true` 打印,返回 `false` 不打印。
hook({
C: "com.example.demo.UserService",
Method: "getUser",
Overload: "(java.lang.String)",
OutputFilter: function (thisObj, args, ret, message) {
return args[0] && args[0].toString() === "10001";
},
});
### 在调用前后插入逻辑
`Callback1` 在调用原方法之前执行。
`Callback2` 在调用原方法之后执行。
它们的签名相同:
function (thisObj, args, ret, buf) {}
其中 `buf` 是当前日志缓冲数组,可以向里面追加自定义日志。
hook({
C: "com.example.demo.UserService",
Method: "getUser",
Overload: "(java.lang.String)",
Callback1: function (thisObj, args, ret, buf) {
buf.push("before call: " + args[0]);
},
Callback2: function (thisObj, args, ret, buf) {
buf.push("after call: " + ret);
},
});
### 修改返回值
`RetVal` 可以替换最终返回值。
传入固定值:
hook({
C: "com.example.demo.FeatureSwitch",
Method: "isEnabled",
Overload: "()",
RetVal: true,
});
传入函数:
hook({
C: "com.example.demo.UserService",
Method: "getLevel",
Overload: "()",
RetVal: function (originRet, args) {
return originRet + 1;
},
});
### 完全自定义 implementation
如果默认 `wrap` 不够用,可以传入 `FnImpl`。
注意:Frida 的 Java method implementation 不要使用箭头函数,否则 `this` 绑定会不正确。
hook({
C: "com.example.demo.UserService",
Method: "getUser",
Overload: "(java.lang.String)",
FnImpl: function (id) {
console.log("getUser id =", id);
var ret = this.getUser(id);
console.log("origin ret =", ret);
return ret;
},
});
### 先调用原方法,再打印参数
默认顺序是:
1. 打印参数。
2. 调用原方法。
3. 打印返回值。
如果参数对象会被原方法修改,可以使用 `ArgsAfterCall`,让原方法先执行,再打印参数:
hook({
C: "com.example.demo.Parser",
Method: "parse",
Overload: "(java.util.Map)",
ArgsAfterCall: true,
});
## 重要辅助函数
### hooks
`hooks(opts, methodArr)` 用于批量 hook 同一个类里的多个方法。
Java.perform(function () {
hooks(
{
C: "com.example.demo.UserService",
NoThisOutput: true,
NoStackTraceOutput: true,
},
["getUser", "saveUser", "deleteUser"],
);
});
`hooks` 会复用同一个类包装对象,适合快速观察一个类中的多个方法。
### wrap
`wrap(opts)` 返回一个 Frida Java method implementation。
通常不需要直接调用它,因为 `hook(opts)` 默认会使用 `wrap(opts)`。当你已经手动拿到某个 overload,希望复用本库默认日志逻辑时,可以直接使用:
Java.perform(function () {
var UserService = JavaUse("com.example.demo.UserService");
var overload = UserService.getUser.overload("java.lang.String");
overload.implementation = wrap({
Method: "getUser",
Message: "UserService#getUser",
NoThisOutput: true,
});
});
常用参数:
| 参数 | 说明 |
| --- | --- |
| `Method` | 原方法名,`wrap` 会通过 `this[Method].apply(this, arguments)` 调用原方法 |
| `Message` | 日志标题 |
| `NoApply` | 不调用原方法 |
| `NoOutput` | 关闭默认日志 |
| `Callback1` | 原方法调用前执行 |
| `Callback2` | 原方法调用后执行 |
| `OutputFilter` | 控制是否打印 |
| `RetVal` | 替换返回值 |
| `ArgsAfterCall` | 原方法执行后再打印参数 |
### JavaUse
`JavaUse(className, loaderOrOpts)` 是 `Java.use` 的增强版。
它会先尝试默认 ClassLoader,失败后枚举 ClassLoader 查找目标类,并缓存查找结果。
Java.perform(function () {
var UserService = JavaUse("com.example.demo.UserService");
if (!UserService) {
console.log("class not found");
return;
}
console.log(UserService);
});
指定 ClassLoader:
var clazz = JavaUse("com.example.demo.UserService", someClassLoader);
刷新缓存:
var clazz = JavaUse("com.example.demo.UserService", { refresh: true });
清理缓存:
JavaUse.clearCache("com.example.demo.UserService");
Hook.clearJavaUseCache("com.example.demo.UserService");
### inspectVal
`inspectVal(v, msg, withSuperclass)` 用于把 Java/JS 值格式化为更适合调试的日志。
它会对常见类型做增强输出,例如:
- Java 基础包装类型和字符串。
- `java.util.Set`
- `java.util.List`
- `java.util.Map`
- `android.content.Intent`
- 普通 Java 对象字段。
- `okhttp3.FormBody`
- `okhttp3.OkHttpClient`
hook({
C: "com.example.demo.UserService",
Method: "save",
Overload: "(java.util.Map)",
Callback1: function (thisObj, args, ret, buf) {
buf.push(inspectVal(args[0], "payload: ", true));
},
});
### inspectInstance
`inspectInstance(className, msg, opts)` 使用 `Java.choose` 枚举堆中的实例。
Java.perform(function () {
inspectInstance("com.example.demo.UserSession", "session: ", {
maxMatches: 5,
});
});
参数:
| 参数 | 说明 |
| --- | --- |
| `maxMatches` | 最多输出多少个实例,默认不限制 |
| `noInspect` | 只输出对象本身,不展开字段 |
### stackTrace / stackTraceAdv
输出当前 Java 调用栈。
console.log(stackTraceAdv());
`stackTraceAdv()` 会额外尝试显示每一层方法的 overload 数量,更适合定位 hook 点。
如果你使用 Android Studio 查看反编译源码或本地源码,推荐配合
[leolovenet/LeoTerminalLinkFilter](https://github.com/leolovenet/LeoTerminalLinkFilter)
使用。这个 Android Studio 插件专门识别 Frida 风格的 stack trace 日志,可以把 `stackTraceAdv()` 输出中的 `sources/com/example/Foo.java`、`com.example.Foo#bar` 和 overload 标记转换为可点击链接,方便在 Terminal / Console 中一键跳转到对应源码位置。
## 其他工具函数
| 函数 | 说明 |
| --- | --- |
| `currentProcessName()` | 返回当前 Android 进程名 |
| `currentApplication()` | 返回当前 `Application` |
| `str2Bytes(str)` | Java UTF-8 字符串转 byte array |
| `bytes2Str(bytes)` | Java byte array 转 UTF-8 字符串 |
| `toInt16(i)` | 将无符号 16 位值按有符号值解释 |
| `toInt32(i)` | 将无符号 32 位值按有符号值解释 |
| `toInt64(i)` | 将无符号 64 位值按有符号 BigInt 解释 |
| `toInt64Number(i)` | 将 64 位值转为安全的 JS number,超出安全范围会抛错 |
| `inspectObj(obj, msg, withSuperclass)` | 展开 Java 对象字段 |
| `inspectList(obj, msg)` | 展开 Java List |
| `inspectSet(obj, msg)` | 展开 Java Set |
| `inspectMap(obj, msg)` | 展开 Java Map |
| `inspectIntent(intent, msg)` | 输出 Intent data 和 extras |
| `sleep(ms)` | Promise 形式的延迟 |
| `runJava(fn)` | `Java.perform(fn)` 的简写 |
## 环境辅助函数
下面这些函数会修改目标进程中的部分行为,使用前请确认符合你的调试目的。
| 函数 | 说明 |
| --- | --- |
| `setIsDebuggerConnectedFalse()` | 让 `android.os.Debug.isDebuggerConnected()` 返回 `false` |
| `setIsApplicationDebugEnabledTrue()` | 尝试给当前应用打开 `FLAG_DEBUGGABLE` |
| `disableProxyDetection()` | 尝试隐藏常见系统代理配置 |
示例:
Java.perform(function () {
setIsDebuggerConnectedFalse();
disableProxyDetection();
});
## 使用建议
- 先用 `OnlyListOverloads` 看清楚重载,再写精确的 `Overload`。
- 日志太多时优先关闭 `NoThisOutput` 和 `NoStackTraceOutput`。
- 需要长期保留的脚本建议使用对象写法,可读性更好。
- 自定义 implementation 时使用 `function () {}`,不要使用箭头函数。
- `hook.js` 应始终放在目标脚本之前加载。
标签:Android, Docker支持, DSL, Frida, Java Hook, JS文件枚举, 云资产清单, 数据可视化, 自定义脚本, 逆向工程