---
name: re-frida
description: >
  Frida 动态插桩（桌面+移动统一）。
  触发词：frida、hook、插桩、绕过、spawn
capabilities: [frida-instrumentation]
---

# Frida 动态插桩

## 何时使用 / 何时不用

- 用：需要 hook 函数（拦截 / 改参 / 改返回值）、枚举模块与类、绕过证书校验 / root 检测、观察运行时调用链
- 用：移动端（Android / iOS）与桌面端（Linux / macOS / Windows）统一的插桩需求
- 不用：只需静态分析（Android 走 [[re-apk]]，格式走 [[re-format-elf]] / [[re-format-macho]] 系列）
- 不用：需要系统调用级跟踪（走 [[re-tracing]]）
- 不用：需要完整调试器体验（断点 / 单步 / 内存，走 [[re-gdb]] / [[re-lldb]] / [[re-x64dbg]]）

## 工具准备

动态分析按 [[re-analyze/platform-tips]] 最高原则：移动端在受控设备 / 模拟器快照内执行，桌面端插桩前确认沙箱环境。所有工具先验证再使用。

### frida-tools —— 主机侧命令行工具

- 跨平台（Linux / macOS / Windows）: `pip install frida-tools`（Python 3.8+；建议 venv: `python3 -m venv venv && venv/bin/pip install frida-tools`）
- 验证: `frida --version`（输出 frida 版本号）；`frida-ps -U` / `frida-trace -h` 可用

### frida-server —— 移动端插桩代理

- 下载：GitHub release `https://github.com/frida/frida/releases`，选 `frida-server-<版本>-android-<架构>`（arm64 选 `-arm64`，32 位选 `-arm`，模拟器 x86_64 选 `-x86_64`）
- **客户端与 frida-server 至少须同 major**（对照 `frida --version`）；为避免功能/API 差异，推荐使用完全相同且最新的版本。架构与设备匹配，否则连接报协议错误（见坑 1）
- Android 推送与启动：
  ```sh
  adb push frida-server-xxx /data/local/tmp/frida-server
  adb shell "chmod 755 /data/local/tmp/frida-server"
  adb shell "su -c /data/local/tmp/frida-server" &   # 或 adb root 后直接运行
  ```
- iOS 越狱设备：Sileo / Cydia 添加源 `https://build.frida.re` 安装 frida deb，或 `ssh root@<设备IP>` 后安装
- 验证: 主机 `frida-ps -U` 能列出设备进程（`-U` = USB 设备，`-R` = 远程 ip:port）

### objection —— 免写 JS 的快速插桩

- 要求 Python >=3.10（与 frida 自身 binding 的 `>=3.7` 下限不同，需单独满足）: `python -m pip install -U objection`，建议另建 3.10+ 的 venv 或 pipx 隔离
- 验证: `objection --version`
- 用法: `objection -n <应用或 Bundle ID> start`，内置 `android hooking` / `ios hooking` 子命令（如 `android hooking list activities`、`ios sslpinning disable`）
- 1.12.x 起 `-g` / `explore` 已弃用（在 CLI 中隐藏，仅出弃用告警，旧写法仍可跑）；新写作用 `-n` + `start`

## 操作步骤

按顺序执行，每步记下结果。

1. **spawn vs attach 选择**：
   ```sh
   frida -U -f com.target.app              # spawn：从零启动应用，能抓启动早期逻辑（解密 / 初始化）
   frida -U com.target.app                 # attach：附加到已运行进程（不重启）
   frida-trace -U -f com.target.app -i "Java!*"   # 启动即跟踪 Java 方法调用
   ```
   规则：抓启动逻辑 / 绕过早期检测用 spawn；只是观察现状用 attach。attach 晚于启动，可能错过已执行完的早期逻辑（见坑 2）。

2. **JS hook 编写（拦截 / 改参 / 返回值）**：
   ```js
   // hook.js —— 拦截、改参数、改返回值（Android Java 层）
   Java.perform(function () {
     var cls = Java.use("com.example.Target");
     cls.doLogin.implementation = function (user, pass) {
       console.log("doLogin(" + user + ", " + pass + ")");
       return this.doLogin("hacked", "pass123");   // 改参
     };
     cls.isLicensed.implementation = function () {
       return true;                                // 改返回值
     };
   });
   ```
   ```sh
   frida -U -f com.target.app -l hook.js
   ```
   原生函数用 `Interceptor.attach(Process.getModuleByName("libfoo.so").findExportByName("func"), { onEnter: ..., onLeave: ... })` 拦截（onEnter 改参数、onLeave 用 `retval.replace()` 改返回值）。桌面端同样本：`frida -p <pid> -l hook.js`。

3. **枚举与调用（enumerateModules / Java.perform）**：
   ```js
   Java.perform(function () {
     Java.enumerateLoadedClasses({
       onMatch: function (c) { if (c.indexOf("target") >= 0) console.log(c); },
       onComplete: function () {}
     });
   });
   ```
   ```js
   // 模块与导出枚举（原生层）
   Process.enumerateModules().forEach(function (m) { console.log(m.name + " " + m.base); });
   Process.getModuleByName("libfoo.so").enumerateExports().forEach(function (e) { console.log(e.name); });
   ```
   运行: `frida -U -f com.target.app -l enum.js`。定位到目标后直接主动调用：`Java.use("com.x").method(...)` / 原生导出函数。

4. **绕过证书校验 / 检测（常见模板）**：
   ```js
   // Android SSL 绕过模板（配合抓包工具）
   Java.perform(function () {
     var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
     var TrustAll = Java.registerClass({
       name: "com.bypass.TrustAll",
       implements: [X509TrustManager],
       methods: { checkClientTrusted: function () {}, checkServerTrusted: function () {},
                  getAcceptedIssuers: function () { return []; } }
     });
     var SSLContext = Java.use("javax.net.ssl.SSLContext");
     SSLContext.init.implementation = function (km, tm, sr) {
       this.init(km, [TrustAll.$new()], sr);
     };
   });
   ```
   ```js
   // iOS 证书绕过：hook SecTrustEvaluateWithError 返回值
   var SecTrust = Module.findGlobalExportByName("SecTrustEvaluateWithError");   // Frida 17+：全局符号静态查找
   Interceptor.attach(SecTrust, { onLeave: function (r) { this.context.x0 = 0; } }); // arm64 返回寄存器 x0；x86_64 用 rdi 场景先验证
   ```
   现成命令：`objection -n com.target.app start` → `android sslpinning disable` / `ios sslpinning disable`。

5. **反检测对抗（隐藏 frida-server、改名）**：
   - 常见检测点：frida-server 默认端口 27042、`/data/local/tmp/frida-server` 路径、`gum-js-loop` / `gmain` 线程名、`/proc/self/maps` 中的 frida 特征、`frida` 字符串
   - 对策：
     ```sh
     # 1) 二进制改名后启动（避开路径检测）
     cp frida-server-xxx /data/local/tmp/fridad
     adb shell "su -c 'chmod 755 /data/local/tmp/fridad && /data/local/tmp/fridad' &"
     # 2) 换端口 + adb 端口转发，主机用 -H 连接
     adb shell "su -c '/data/local/tmp/fridad -l 0.0.0.0:27142' &"
     adb forward tcp:27142 tcp:27142
     frida -H 127.0.0.1:27142 -f com.target.app
     ```
   - root 检测对抗：先 hook 检测函数改返回值再插桩目标：`Java.use("com.target.rootcheck").isRooted.implementation = function () { return false; };`
   - 仍被检测 → frida-gadget 注入 App 进程（gadget listen（interactive）模式），配合 [[re-apk]] 的 smali 补丁加载 libgadget.so

7. **脚本模板与对抗方法论**：常用脚本骨架见 [[frida-scripts]]（TLS keylog / DEX/SO dump / JNI 注册还原 / 加密拦截 / 检测绕过表）；崩溃迭代法与检测面对照表见 [[re-analyze/anti-dynamic-workflow]]——先基线跑看裸崩，再定点 hook，不预置绕过全家桶。

## 跨域联合
- [[re-address-space]]：运行时基址换算（`Process.getModuleByName(...).base` 与链接地址对齐）

- [[re-mobile]]：工作流第 3 步动态插桩固定调用本技能
- [[re-apk]] / [[re-ios]]：静态分析后需要运行时行为（解密 / hook / 绕过 / 脱壳执行）时调用本技能
- 系统调用级跟踪互补 → [[re-tracing]]；运行时内存提取 → [[re-memdump]]
- 本技能被 [[re-analyze]] 的 triage「移动 App 分析」路径调用（re-mobile → re-frida）
- 脚本生成：目标特征 → 模板选择 → 改写验证 → [[re-frida-script-author]]（模板素材 [[frida-scripts]]）

## 常见坑与陷阱

- **版本不匹配 → 协议错误**：现象——`frida-ps -U` 报 `unable to communicate with the frida server` / 协议错误；原因——客户端与 frida-server major 不一致，或所用的功能在目标版本上不存在；对策——先查 major 是否匹配（`frida --version` 对照 frida-server 版本，见工具准备），再确认该功能是否被对应版本支持；升级侧用 `pip install -U frida-tools`
- **Frida 17 移除静态 Module 查找/枚举 API**：现象——脚本报 `TypeError: Module.findExportByName is not a function`（或 getExportByName / enumerateExports / findBaseAddress 同类）；原因——17.0.0 起静态查找与枚举 API 全部移除，静态只余 `Module.load` / `Module.findGlobalExportByName` / `Module.getGlobalExportByName`，其余改为**模块实例方法**；对策——先取实例再查：`Process.getModuleByName("libfoo.so").findExportByName("func")`（find 返 null，get 抛异常）、基址用 `.base`、枚举用 `.enumerateExports()`；全局符号（原 `null` 模块参数）改用 `Module.findGlobalExportByName(...)`；注意 `Process.getModuleByName` 在模块未加载时**抛异常**（旧 `Module.findExportByName` 返 null）——不确定时先用 `Process.findModuleByName` 判空再查；动笔前 `frida --version` 确认版本再选写法（迁移清单见 [[re-frida-script-author]] 版本相关组）
- **spawn 时机晚 → 错过早期逻辑**：现象——attach 后 hook 不触发或早期解密已完成；原因——应用启动即解密 / 校验，attach 时已过；对策——用 `-f` spawn 模式起步即插桩；仍错过则 hook `dlopen` / `ClassLoader.loadClass` 这类更早的执行点
- **目标检测 frida（端口 / 特征）**：现象——spawn 后应用闪退 / 卡死 / 行为异常；原因——应用扫描 27042 端口、frida-server 路径、`frida` 线程名或 maps 特征；对策——步骤 5 改名 + 换端口；仍检测用 frida-gadget 注入（隐藏于进程内）
- **root 检测拦插桩**：现象——frida 可连接但 hook 不生效或直接退出；原因——应用先做 root / 越狱检测，检测到环境直接退出；对策——先 hook 检测函数返回值（步骤 5 模板），过了检测再 hook 目标函数
- **JS 脚本静默失败**：现象——脚本加载无报错但 hook 无输出；原因——类名写错、`Java.perform` 外调用 Java API、模块名大小写不符；对策——用步骤 3 的枚举先核对名称，脚本内 `console.log` 打桩定位
- **hook 导出 API 被壳绕过（直 syscall / API 名哈希）**：现象——成功 hook 了 `IsDebuggerPresent`/`NtQueryInformationProcess` 等导出，反调试照样触发、进程退出；原因——加壳/加固目标不走导入表：自实现 `GetProcAddress`、API 名存哈希，或直接 syscall（内联 `Nt*` 直调），hook 点根本没经过；对策——hook 更深一层（ntdll 的 syscall 包装点）、跟踪 `GetProcAddress`/哈希解析处反推真实调用点，反调试与反 VM（`RegOpenKeyExA`/`GetSystemFirmwareTable`）API 一并 hook（Arkana 项目实战模板），先用 `Process.enumerateModules()`/`enumerateExports` 确认实际调用目标再插桩
- **检测点升级（memfd/JIT 池/内存字符串/管道名）**：现象——改名、换端口启动 frida-server 后仍被检测；原因——新版检测不止端口与路径：扫描 `/proc/self/fd` 的 memfd 名称、JIT 缓存池（pool-frida）、`frida_agent.so`/`frida_rpc` 等内存字符串与导出符号、管道/linjector 名称，甚至非标准端口也会被扫（Promon 式扫描）；对策——用 undetected-frida 补丁集（字符串/符号/线程/协议/memfd/JIT 池全量混淆，Magisk/KSU 模块形态），或 frida-gadget + 自编译隐藏版，缩小指纹面
- **强加固（Pairipcore 类）整体对抗 Frida**：现象——常见 hook 脚本（证书绕过/反检测/解密）全部失效，spawn 即闪退或行为异常，社区报告 frida-interception 类脚本无法绕过；原因——商业加固做 C++/Java 双层完整性校验 + 伪 VM 指令 + 自定义 dlopen/dlsym/syscall 动态导入混淆 + prctl/clone/waitpid/ptrace 反调试 + `/proc/self/maps`+`/proc/self/status` 进程监控 + 非标准端口扫描，整体防线而非单点；对策——先处理完整性校验与进程监控（hook 校验函数返回、patch 监控点）再过反调试，hook 落到自定义导入解析处而非导出 API，必要时结合 [[re-apk]] 静态改 smali + Native 层插桩配合
- **hook 出口函数多进程/多实现排查**：现象——hook `SocketOutputStream` 无结果，抓包却看到流量；原因——应用多进程（发流量的逻辑在子进程）或使用 Netty 的 `SocketChannelImpl`（不走 OutputStream 路径）；对策——排查顺序：hook 最外层出口 → 无果 `ps -e` 查子进程分别 hook → 再试 `SocketChannelImpl` 等替代实现；hook 成功后打印堆栈（Netty 的 `MessageToByteEncoder` 链）定位组装/加密代码；从"编解码器链"逐层向上追明文对象与加密产物
- **对称加密密钥传输追踪**：现象——抓到 RC4/AES 密文包，但密钥每次会话都变、静态搜不到；原因——对称加密密钥动态生成且在线传输（明文/加密后携带/协商）；对策——hook 密钥传入处（加密函数参数 `bArr` 类）拿当前密钥，再向上追踪密钥来源与传输路径，配合抓包对照（首包固定头如 `89 04 01 01` 可作协议锚点）
- **多层 TLS 校验栈**：现象——单点 hook 后部分请求仍 SSL 错误；原因——App 同时用 OkHttp/原生 HttpsURLConnection/Conscrypt 多套栈；对策——分层覆盖（OkHttp CertificatePinner + TrustManagerImpl.verifyChain + HostnameVerifier 同时 hook），全栈覆盖才算绕过完成
- **ProGuard 混淆后定位目标类**：现象——类名被改成短名；原因——混淆；对策——jadx 里 Find Usages 反查谁实例化关键 Builder，从实例化点反推原类
（来源：reverse-skill field-journal，MIT）
- **测试环境选错 → 动态假阴性 / 交付假阳性**：现象——同一环境里 hook 不触发、内存 patch 无效果，或模拟器一切正常但换真机就出问题；原因——x86 翻译层模拟器上 live Frida 不可用（`Process.arch==x64` 且找不到目标原生 so），运行时从未执行你的字节；部分目标在模拟器上校验宽松，「模拟器能跑」可能是降级假象；对策——按阶段选环境：静态 patch 与渲染回归可用任意模拟器（含 x86 翻译层），动态插桩必须原生 ARM，交付验证必须与目标匹配的真机；翻译层假象可证伪：把已知必需函数首指令 patch 成裸 `ret`，UI 照常渲染即运行时没执行你的字节，立即停掉该环境的 live 工作（盘上静态 patch 下次启动重新翻译仍可用）；真机采集前 `svc power stayon true` 并唤醒，防睡眠屏造成「空白」假截图
（来源：reverse-skills（inliver233），MIT）
- **redroid 容器搭建与使用坑**：现象——guest 静默挂死 / servicemanager 空转、vold 崩溃、adb 反复掉线、容器无法重启；原因——binder 挂载方式错误（必须 bind-mount `/dev/binder`，不能用 `--device` 或 binderfs）、新内核无 ashmem 时未加 `androidboot.use_memfd=1`、设了 `ro.secure=1 ro.debuggable=0` 杀掉 root adb、停掉的容器 binder 已消失无法 docker restart、adbd 高负载下崩溃；对策——binder 用 bind-mount、新内核强制 use_memfd、保留 root adb（防篡改用保持签名有效而非藏 root）、重建容器后重注入 adb 公钥（`echo $PUBKEY > /data/misc/adb/adb_keys`）、大文件用 docker cp 而非 adb push、动态分析用 spawn 模式（`frida -U -f`，attach 在防篡改下常 0 命中）；定位认知——它是数据采集工具而非迭代环境，采完动态数据回稳定环境做静态 patch 回归
（来源：reverse-skills（inliver233），MIT）
- **AOT 目标上 hook 静默不触发 / 找不到调用者**：现象——hook 无输出、目标函数静态 callers=0、门字段未知无法下针；原因——目标 so 晚于引擎加载（attach 时未加载）、虚方法经 cid 分派墙（不经 BL 调用，静态无捷径）、guard 字段位置未知；对策——等目标 so 加载后再 hook：200ms 轮询 `findModuleByName` 的 waitForLibapp 包装器，起步优先 spawn 模式；破 cid 墙：目标函数 onEnter 记 `lr`（returnAddress）+ `Thread.backtrace(Backtracer.ACCURATE)` 前 20 帧，一次回溯定位分派点；guard 定位三件套：Stalker.follow transform 里对 `ldr` 类指令放 putCallout（首个命中 PC = guard）、MemoryAccessMonitor.enable 等重建触发（arm64 支持不一需回退）、候选 hook 扫射；**运行时翻转验证前置**：live `writePointer` 翻转候选门字段 → 触发重建 → 组件消失即确认门字段，确认后再投静态 patch，省掉反复重打包；frida 17 起 Java bridge 不再内置于底层 GumJS runtime，但 `frida` CLI/REPL 与 `frida-trace` 仍自带三个 bridge，普通 `frida -l` 脚本无需改动；仅自建 agent / 走 bindings 注入脚本时需显式安装 `frida-java-bridge`（必要时用 frida-compile 打包）
（来源：reverse-skills（inliver233），MIT）
