> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Frida 基础指南
- URL: https://blog.vercanti.com/frida-ji-chu-zhi-nan/
- Published: 2026-08-28T14:35:07.000Z
- Updated: 2026-08-28T14:58:07.000Z
- Description: 最后更新：2026-03-29 相关文档：js逆向调试技巧(/js-ni-xiang-diao-shi-ji-qiao/) 补环境(/bu-huan-jing/) 签名算法还原(/qian-ming-suan-fa-huan-yuan/) Frida 是一款动态插桩（Dynamic Instrumentation）框架，可以在运行时向 Native 程序（Android/iOS App、PC 程序）注入 JavaScript 代码，实现： 直接在 Java.perform 外调用 Java API 会报错。 方法有多个重载时，不指定 overload 会
- Author: yellowdog
- Tags: js逆向, 技巧步骤

> 官方文档：<https://frida.re/docs/>  
> 适用版本：Frida 16.x+（2026-05-08 核实）

最后更新：2026-03-29

相关文档：[js逆向调试技巧](https://blog.vercanti.com/js-ni-xiang-diao-shi-ji-qiao/) [补环境](https://blog.vercanti.com/bu-huan-jing/) [签名算法还原](https://blog.vercanti.com/qian-ming-suan-fa-huan-yuan/)

---

## 1\. 基础概念

### Frida 是什么

Frida 是一款动态插桩（Dynamic Instrumentation）框架，可以在运行时向 Native 程序（Android/iOS App、PC 程序）注入 JavaScript 代码，实现：

- Hook 任意函数（Java 层 / Native 层 / OC/Swift 层）
- 读写内存、修改参数和返回值
- 追踪函数调用链
- 绕过 SSL Pinning、Root 检测
- 辅助 App 逆向、接口分析

### 架构

```
PC（Frida Tools / Python）
  ↕ USB / TCP
手机（frida-server 运行在 Root 权限下）
  ↕ 进程注入
目标 App 进程（frida-agent 注入后运行 JS 脚本）

```

### 安装

```bash
# 安装 Python 工具
pip install frida-tools

# 验证安装
frida --version

# 下载对应版本的 frida-server（手机端，需 Root）
# 地址：https://github.com/frida/frida/releases
# 选择 frida-server-xx.xx.x-android-arm64.xz

```

### frida-server 部署（Android）

```bash
# 解压 frida-server
unxz frida-server-*.xz

# 推送到手机
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"

# 以 Root 权限启动
adb shell "su -c '/data/local/tmp/frida-server &'"

# 验证连接
frida-ps -U          # 列出手机上所有进程
frida-ps -Ua         # 只列出已安装的 App

```

---

## 2\. 基础使用

### 常用命令行工具

```bash
# 列出设备上的进程
frida-ps -U

# 附加到进程（进程名或 PID）
frida -U -n "com.example.app" -l script.js

# 附加到应用（包名）并在启动时注入（用于初始化阶段的 Hook）
frida -U -f "com.example.app" -l script.js --no-pause

# 交互式 REPL（调试用）
frida -U -n "com.example.app"

# 指定设备
frida -D <device-id> -n "com.example.app" -l script.js

```

### 脚本结构

```javascript
// script.js

// 等待 Java 虚拟机初始化完成
Java.perform(function () {
    console.log("[*] Frida 脚本已加载");

    // 在这里写 Hook 代码
});

```

---

## 3\. Java 层 Hook

### Hook 实例方法

```javascript
Java.perform(function () {
    // 获取目标类
    var TargetClass = Java.use("com.example.app.crypto.SignUtil");

    // Hook 方法（根据方法签名区分重载）
    TargetClass.sign.implementation = function (data) {
        // this：当前对象实例
        console.log("[sign] 被调用，入参: " + data);

        // 调用原始方法，获取原始返回值
        var result = this.sign(data);

        console.log("[sign] 返回值: " + result);

        // 修改返回值（可选）
        // return "fake_signature";

        return result;
    };
});

```

### Hook 重载方法

```javascript
Java.perform(function () {
    var Crypto = Java.use("com.example.Crypto");

    // 方法有多个重载时，用 overload 指定参数类型
    Crypto.encrypt.overload("java.lang.String").implementation = function (data) {
        console.log("[encrypt(String)] data: " + data);
        return this.encrypt(data);
    };

    Crypto.encrypt.overload("java.lang.String", "java.lang.String").implementation = function (data, key) {
        console.log("[encrypt(String, String)] data: " + data + ", key: " + key);
        return this.encrypt(data, key);
    };
});

```

### Hook 静态方法

```javascript
Java.perform(function () {
    var Utils = Java.use("com.example.Utils");

    // 静态方法也是一样的写法，用 this 调用原始方法（静态方法的 this 是类本身）
    Utils.md5.implementation = function (input) {
        var result = this.md5(input);
        console.log("[md5] " + input + " => " + result);
        return result;
    };
});

```

### Hook 构造函数

```javascript
Java.perform(function () {
    var OkHttpClient = Java.use("okhttp3.OkHttpClient$Builder");

    OkHttpClient.$init.implementation = function () {
        console.log("[OkHttpClient.Builder] 构造函数被调用");
        console.log(Java.use("android.util.Log").getStackTraceString(
            Java.use("java.lang.Exception").$new()
        ));
        return this.$init();
    };
});

```

---

## 4\. 常用 Hook 场景

### 打印函数调用栈

```javascript
Java.perform(function () {
    var TargetClass = Java.use("com.example.Sign");

    TargetClass.getSign.implementation = function () {
        var result = this.getSign();

        // 打印 Java 调用栈
        var stack = Java.use("android.util.Log")
            .getStackTraceString(Java.use("java.lang.Exception").$new());
        console.log("[getSign] 调用栈:\n" + stack);
        console.log("[getSign] 结果: " + result);

        return result;
    };
});

```

### Hook 所有加载的类的某个方法

```javascript
Java.perform(function () {
    Java.enumerateLoadedClasses({
        onMatch: function (className) {
            if (className.includes("Encrypt")) {
                try {
                    var clazz = Java.use(className);
                    var methods = clazz.class.getDeclaredMethods();
                    methods.forEach(function (method) {
                        console.log("[类] " + className + "." + method.getName());
                    });
                } catch (e) {}
            }
        },
        onComplete: function () {},
    });
});

```

### 主动调用方法

```javascript
Java.perform(function () {
    // 主动调用静态方法
    var CryptoUtil = Java.use("com.example.CryptoUtil");
    var result = CryptoUtil.md5("hello world");
    console.log("MD5 结果: " + result);

    // 枚举所有实例，主动调用实例方法
    Java.choose("com.example.TokenManager", {
        onMatch: function (instance) {
            console.log("token: " + instance.getToken());
        },
        onComplete: function () {},
    });
});

```

---

## 5\. 绕过常见检测

### 绕过 Root 检测

```javascript
Java.perform(function () {
    // Hook RootBeer 库
    try {
        var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
        RootBeer.isRooted.implementation = function () {
            console.log("[RootBeer] isRooted → false");
            return false;
        };
    } catch (e) {}

    // Hook 常见的文件检测
    var File = Java.use("java.io.File");
    File.exists.implementation = function () {
        var path = this.getAbsolutePath();
        if (path.includes("su") || path.includes("superuser")) {
            return false;
        }
        return this.exists();
    };
});

```

### 绕过 SSL Pinning

```javascript
Java.perform(function () {
    // 方法 1：Hook OkHttp3 CertificatePinner
    try {
        var CertificatePinner = Java.use("okhttp3.CertificatePinner");
        CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function (host, certs) {
            console.log("[SSL Pinning] 已绕过，host: " + host);
            return;  // 不调用原始方法，直接通过
        };
    } catch (e) {}

    // 方法 2：Hook TrustManager（信任所有证书）
    var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
    TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
        return untrustedChain;
    };
});

```

---

## 6\. Native 层 Hook

```javascript
// Hook so 库中的函数
var libso = Module.findBaseAddress("libcrypto.so");

if (libso) {
    // 按导出函数名 Hook
    var target = Module.findExportByName("libssl.so", "SSL_write");
    if (target) {
        Interceptor.attach(target, {
            onEnter: function (args) {
                console.log("[SSL_write] 调用");
                // 打印参数（buf）
                var buf = args[1];
                var num = parseInt(args[2]);
                console.log("[SSL_write] 数据: " + buf.readUtf8String(num));
            },
            onLeave: function (retval) {
                console.log("[SSL_write] 返回: " + retval);
            },
        });
    }
}

```

---

## 7\. Python API（脚本化）

```python
import frida
import sys
import json

PACKAGE_NAME = "com.example.app"

SCRIPT = """
Java.perform(function () {
    var Sign = Java.use("com.example.Sign");
    Sign.getSign.implementation = function(data) {
        var result = this.getSign(data);
        send({type: "sign", data: data, result: result});
        return result;
    };
});
"""

def on_message(message, data):
    if message["type"] == "send":
        payload = message["payload"]
        print(f"[+] 签名: {payload['data']} → {payload['result']}")
    elif message["type"] == "error":
        print(f"[!] 错误: {message['stack']}")

def main():
    device = frida.get_usb_device()
    session = device.attach(PACKAGE_NAME)
    script = session.create_script(SCRIPT)
    script.on("message", on_message)
    script.load()

    print(f"[*] 已附加到 {PACKAGE_NAME}，按 Ctrl+C 退出")
    sys.stdin.read()

if __name__ == "__main__":
    main()

```

---

## 8\. 常用代码段速查

### 打印 Byte 数组

```javascript
function bytes2hex(arr) {
    return Array.from(arr).map(b => ('0' + (b & 0xFF).toString(16)).slice(-2)).join('');
}

function bytes2str(arr) {
    return Java.use("java.lang.String").$new(arr, "UTF-8");
}

```

### 主动调用加密函数

```javascript
Java.perform(function () {
    var context = Java.use("android.app.ActivityThread")
        .currentApplication().getApplicationContext();

    var SharedPreferences = context.getSharedPreferences("config", 0);
    var token = SharedPreferences.getString("token", "");
    console.log("token: " + token);
});

```

---

## 9\. 踩坑与注意事项

### Java.perform 必须包裹所有 Java 操作

直接在 `Java.perform` 外调用 Java API 会报错。

### Hook 时机：frida -f 与 frida -n 的区别

- `-f`：在 App 启动前注入（适合 Hook 初始化阶段的加密，如 so 加载）
- `-n`：附加到已运行的进程（适合 Hook 业务逻辑）

### 重载选择错误

方法有多个重载时，不指定 `overload` 会只 Hook 第一个重载。若 Hook 后未触发，检查是否选对了重载。

### frida-server 版本必须与 frida-tools 版本一致

版本不一致会导致连接失败或奇怪的报错，更新时两端都要同步更新。

---

## 最佳实践

**attach 前确认进程名**：用 `frida-ps -U` 列出设备所有进程，确认目标 App 的进程名（或包名），避免 attach 到错误进程；对于同名进程，用 `-H` 或 `-D` 加设备序列号指定设备。

**Hook Java 方法时用 `overload` 明确指定参数类型**：同一方法名可能有多个重载，`Java.use('cls').method.overload('java.lang.String', 'int').implementation = ...` 精确匹配，避免意外 Hook 错重载。

**用 `Interceptor.attach` 的 `onLeave` 查看返回值**：不仅要在 `onEnter` 看输入参数，在 `onLeave(retval)` 中打印 `retval.toString()` 或 `hexdump(retval)` 可以看到函数输出，对追踪加密结果尤其有用。

**脚本调试用 `console.log` \+ `frida -U -l script.js`**：通过文件加载脚本比 `-e` 内联脚本更易维护；修改脚本后直接 Ctrl+C 重启，不需要重新 attach（脚本支持 `--no-pause` 直接运行）。

**`Memory.scan` 配合 `frida-dump` 提取内存中的明文密钥**：加密密钥有时只在内存中出现（从接口动态获取并解密），用 `Memory.scan` 在堆内存中搜索特征字节序列，找到密钥地址后读出。

---

## 常见陷阱

### 陷阱：`frida-server` 在设备重启后失效

**现象：** 重启 Android 设备后 Frida 连接失败，需要重新 push frida-server。  
**原因：** frida-server 放在 `/data/local/tmp/` 的可执行文件在重启后不自动运行，需要手动启动或配置自启。  
**解决：** 每次连接前先 `adb shell /data/local/tmp/frida-server &`；或使用 Magisk 模块自动启动 frida-server。

### 陷阱：Hook 后 App 直接崩溃

**现象：** 注入 Frida 脚本后 App 立即 crash，没有输出任何日志。  
**原因：** App 有反 Frida 检测（检测 frida-server 端口 27042、`/proc/self/maps` 中的 frida 库、ptrace 检测等），或 Hook 脚本语法错误导致异常。  
**解决：** 先用 `frida --no-pause` 只 attach 不注入脚本，确认 App 能运行；再用 `objection explore` 绕过常见反检测；或用 frida-gadget 模式替代 frida-server，减少检测特征。

### 陷阱：Java.use 的类在运行时找不到

**现象：** `Java.use('com.example.Crypto')` 报 `ClassNotFoundException`。  
**原因：** 目标类可能在非主线程或特定 ClassLoader 中加载（如插件 APK、热修复框架）。  
**解决：** 用 `Java.enumerateClassLoaders` 遍历所有 ClassLoader，找到包含目标类的 ClassLoader 后用 `Java.classFactory.loader = targetLoader` 切换。

---

## 参见

[mitmproxy完全指南](https://blog.vercanti.com/mitmproxy-wan-quan-zhi-nan/)  
[unidbg基础](https://blog.vercanti.com/unidbg-ji-chu/)  
[js逆向调试技巧](https://blog.vercanti.com/js-ni-xiang-diao-shi-ji-qiao/)