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文件枚举, 云资产清单, 数据可视化, 自定义脚本, 逆向工程