前言
在 Android 逆向分析与安全测试领域,网络请求调试是永恒的核心课题。传统的抓包工具(如 Charles、Fiddler)在面对 SSL 证书绑定、自定义加密协议、底层 Native 网络库时往往束手无策。而 Frida 作为一款强大的动态插桩框架,能够直接注入到 App 进程中,从代码层面拦截和修改网络请求,成为逆向工程师的必备利器。
本文将从环境搭建开始,由浅入深地讲解如何使用 Frida Hook 技术对 Android App 的网络请求进行动态调试,覆盖 HttpURLConnection、OkHttp 等主流网络库,以及 SSL 绕过、请求篡改、加密参数分析等高级实战技巧。
一、环境搭建:从零开始配置 Frida
1.1 Frida 工作原理
Frida 采用 C/S 架构:PC 端作为客户端发送指令,Android 设备上运行 frida-server 作为服务端,二者通过 USB 或 TCP 通信。Frida 利用动态二进制插桩技术,在运行时向目标进程注入 JavaScript 脚本,实现对 Java 层和 Native 层函数的拦截与修改。
1.2 PC 端安装
确保已安装 Python 3.7 + 环境,执行以下命令:
bash
运行
# 安装稳定版本(推荐16.x系列) pip install frida==16.2.1 frida-tools==12.3.0 # 验证安装 frida --version1.3 Android 端配置
步骤 1:查看设备 CPU 架构
bash
运行
adb shell getprop ro.product.cpu.abi常见返回值:arm64-v8a、armeabi-v7a、x86_64
步骤 2:下载对应版本 frida-server前往 Frida Releases 下载与 PC 端版本一致的 frida-server。例如 arm64 设备下载:frida-server-16.2.1-android-arm64.xz
步骤 3:推送到设备并启动
bash
运行
# 解压后推送到设备 adb push frida-server-16.2.1-android-arm64 /data/local/tmp/frida-server # 赋予执行权限 adb shell chmod 755 /data/local/tmp/frida-server # 后台启动 adb shell "/data/local/tmp/frida-server &"步骤 4:验证连接
bash
运行
frida-ps -U成功列出设备进程即表示环境搭建完成。
1.4 两种启动模式
Attach 模式:Hook 已运行的进程,适合调试运行中状态
bash
运行
frida -U -n 进程名 -l hook.jsSpawn 模式:冷启动 App 并注入,适合 Hook 初始化逻辑
bash
运行
frida -U -f com.example.app -l hook.js --no-pause
二、入门篇:Hook HttpURLConnection
2.1 为什么从 HttpURLConnection 开始
HttpURLConnection 是 Android 系统原生的 HTTP 客户端,位于java.net包下,几乎所有网络库底层最终都会调用它。掌握它的 Hook 方法是网络调试的基础。
2.2 核心 Hook 点
表格
| 方法 | 作用 | Hook 时机 |
|---|---|---|
connect() | 建立连接 | 请求发出前 |
getInputStream() | 获取响应流 | 响应返回后 |
getOutputStream() | 获取请求流 | POST 数据写入时 |
2.3 完整 Hook 脚本
javascript
运行
Java.perform(function () { console.log("[*] HttpURLConnection Hook 已启动"); // Hook URL构造,捕获所有请求地址 var URL = Java.use("java.net.URL"); URL.$init.overload('java.lang.String').implementation = function (url) { console.log("[URL] " + url); return this.$init(url); }; // Hook connect方法,获取请求信息 var HttpURLConnection = Java.use("java.net.HttpURLConnection"); HttpURLConnection.connect.implementation = function () { console.log("\n=== 请求发起 ==="); console.log("URL: " + this.getURL().toString()); console.log("Method: " + this.getRequestMethod()); // 打印请求头 var headers = this.getRequestProperties(); console.log("请求头:"); var keys = headers.keySet().toArray(); for (var i = 0; i < keys.length; i++) { var key = keys[i]; console.log(" " + key + ": " + headers.get(key)); } // 打印调用栈,定位代码位置 var stack = Java.use("android.util.Log") .getStackTraceString(Java.use("java.lang.Exception").$new()); console.log("调用栈:\n" + stack); return this.connect(); }; // Hook getInputStream,捕获响应 HttpURLConnection.getInputStream.implementation = function () { var responseCode = this.getResponseCode(); console.log("\n=== 响应返回 ==="); console.log("状态码: " + responseCode); console.log("URL: " + this.getURL().toString()); return this.getInputStream(); }; });2.4 POST 请求体捕获
对于 POST 请求,请求体写入getOutputStream(),需要通过反射读取输出流内容:
javascript
运行
var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); HttpsURLConnection.getOutputStream.implementation = function () { var os = this.getOutputStream(); console.log("[POST] 准备写入请求体"); // 可通过ByteArrayOutputStream包装来捕获写入内容 return os; };注意:直接捕获 OutputStream 数据较为复杂,实际项目中更推荐 Hook 更高层的 OkHttp 库来获取请求体。
三、进阶篇:Hook OkHttp 网络库
3.1 OkHttp 的重要地位
OkHttp 是目前 Android 生态中使用最广泛的 HTTP 客户端,Retrofit、Glide 等框架均基于 OkHttp 实现。Hook OkHttp 能够获取最完整、最结构化的请求与响应信息。
3.2 核心 Hook 策略
OkHttp 有多个 Hook 切入点,各有优劣:
表格
| Hook 点 | 优点 | 缺点 |
|---|---|---|
Request.Builder.build() | 捕获所有请求构建 | 拿不到响应 |
CallServerInterceptor.intercept() | 请求响应都能拿到 | 依赖内部类路径 |
RealCall.execute() | 入口级 Hook | 信息需二次解析 |
3.3 实战脚本:完整捕获请求与响应
javascript
运行
Java.perform(function () { console.log("[*] OkHttp Hook 已启动"); // ==================== 捕获请求 ==================== var RequestBuilder = Java.use("okhttp3.Request$Builder"); RequestBuilder.build.implementation = function () { var request = this.build(); console.log("\n========== OkHttp 请求 =========="); console.log("URL: " + request.url().toString()); console.log("Method: " + request.method()); // 遍历请求头 console.log("--- Headers ---"); var headers = request.headers(); for (var i = 0; i < headers.size(); i++) { console.log(headers.name(i) + ": " + headers.value(i)); } // 读取请求体 var body = request.body(); if (body !== null) { console.log("--- Request Body ---"); try { var buffer = Java.use("okio.Buffer").$new(); body.writeTo(buffer); console.log(buffer.readUtf8()); } catch (e) { console.log("请求体读取失败: " + e.message); } } return request; }; // ==================== 捕获响应 ==================== var ResponseBuilder = Java.use("okhttp3.Response$Builder"); ResponseBuilder.build.implementation = function () { var response = this.build(); console.log("\n========== OkHttp 响应 =========="); console.log("Code: " + response.code()); console.log("URL: " + response.request().url().toString()); // 响应头 console.log("--- Response Headers ---"); var headers = response.headers(); for (var i = 0; i < headers.size(); i++) { console.log(headers.name(i) + ": " + headers.value(i)); } // 响应体(注意:只能读取一次,需克隆) var body = response.body(); if (body !== null) { try { var source = body.source(); source.request(9223372036854775807); // Long.MAX_VALUE var buffer = source.buffer().clone(); console.log("--- Response Body ---"); console.log(buffer.readUtf8()); } catch (e) { console.log("响应体读取失败: " + e.message); } } return response; }; });3.4 拦截器层面 Hook(更精准)
对于混淆过的 App,Request$Builder可能被重命名。此时可以 Hook 拦截器接口:
javascript
运行
var CallServerInterceptor = Java.use("okhttp3.internal.http.CallServerInterceptor"); CallServerInterceptor.intercept.implementation = function (chain) { var request = chain.request(); console.log("拦截到请求: " + request.url()); // 可以在这里修改请求 // var newRequest = request.newBuilder().url("新地址").build(); // var response = chain.proceed(newRequest); var response = chain.proceed(request); console.log("响应状态: " + response.code()); return response; };四、高级篇:SSL 证书绕过与请求篡改
4.1 通用 SSL Pinning 绕过
绝大多数 App 的 SSL 证书绑定都可以通过 Frida 绕过。以下是通用脚本:
javascript
运行
Java.perform(function () { console.log("[*] SSL Pinning 绕过脚本已加载"); // OkHttp 证书绑定绕过 try { var CertificatePinner = Java.use("okhttp3.CertificatePinner"); CertificatePinner.check.overload( 'java.lang.String', 'java.util.List' ).implementation = function () { console.log("[+] 绕过 OkHttp CertificatePinner"); return; }; console.log("[+] OkHttp CertificatePinner 已Hook"); } catch (e) {} // HttpsURLConnection 绕过 try { var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); HttpsURLConnection.setSSLSocketFactory.implementation = function () { console.log("[+] 绕过 HttpsURLConnection SSL"); return; }; } catch (e) {} // 信任所有证书 var TrustManager = Java.use("javax.net.ssl.X509TrustManager"); var SSLContext = Java.use("javax.net.ssl.SSLContext"); // 构造信任所有证书的TrustManager var TrustAllManager = Java.registerClass({ name: "com.example.TrustAllManager", implements: [TrustManager], methods: { checkClientTrusted: function () {}, checkServerTrusted: function () {}, getAcceptedIssuers: function () { return Java.use("java.security.cert.X509Certificate").array.newInstance(0); } } }); console.log("[+] SSL 绕过配置完成"); });4.2 动态修改请求参数
Hook 不仅可以观察,还可以修改请求数据:
javascript
运行
Java.perform(function () { var RequestBuilder = Java.use("okhttp3.Request$Builder"); RequestBuilder.url.overload('java.lang.String').implementation = function (url) { console.log("[原URL] " + url); // 替换指定接口 if (url.indexOf("/api/login") !== -1) { var newUrl = url.replace("/api/login", "/api/test_login"); console.log("[替换URL] " + newUrl); return this.url(newUrl); } return this.url(url); }; // 修改请求头 RequestBuilder.addHeader.implementation = function (name, value) { if (name === "User-Agent") { value = "Frida-Hooked/1.0"; } return this.addHeader(name, value); }; });4.3 响应数据篡改
修改服务器返回的数据,常用于绕过服务端校验:
javascript
运行
var ResponseBody = Java.use("okhttp3.ResponseBody"); var BufferedSource = Java.use("okio.BufferedSource"); var Buffer = Java.use("okio.Buffer"); // Hook ResponseBody.create 替换响应体 // 实际使用中建议在拦截器层面替换完整Response对象五、精通篇:实战技巧与进阶方案
5.1 加密参数分析技巧
当遇到请求参数加密时,按以下步骤定位加密函数:
- Hook 请求体输出密文
- 打印调用栈找到加密方法所在类
- Hook 加密函数,打印明文输入和密文输出
- 必要时主动调用加密函数生成自己的密文
示例:Hook 常见加密工具类
javascript
运行
Java.perform(function () { // Hook MD5 var MessageDigest = Java.use("java.security.MessageDigest"); MessageDigest.digest.overload('[B').implementation = function (input) { var data = Java.use("java.lang.String").$new(input); var result = this.digest(input); console.log("[MD5] input: " + data); return result; }; // Hook AES加密 var Cipher = Java.use("javax.crypto.Cipher"); Cipher.doFinal.overload('[B').implementation = function (input) { var opmode = this.getOpMode(); var data = bytesToHex(input); var result = this.doFinal(input); console.log("[AES] mode:" + opmode + " input:" + data); return result; }; }); function bytesToHex(bytes) { var hex = ""; for (var i = 0; i < bytes.length; i++) { var b = bytes[i] & 0xFF; hex += (b < 16 ? "0" : "") + b.toString(16); } return hex; }5.2 Native 层网络请求 Hook
如果 App 使用 Native 层发起网络请求(如 libcurl、直接调用 socket),需要 Hook Native 函数:
javascript
运行
// Hook send函数(libc.so) Interceptor.attach(Module.findExportByName("libc.so", "send"), { onEnter: function (args) { var fd = args[0].toInt32(); var buf = args[1]; var len = args[2].toInt32(); var data = Memory.readUtf8String(buf, len); console.log("[send] fd=" + fd + " len=" + len); console.log(data.substring(0, 500)); } }); // Hook recv函数 Interceptor.attach(Module.findExportByName("libc.so", "recv"), { onLeave: function (retval) { // 读取接收缓冲区数据 } });5.3 Frida 反检测与对抗
当 App 检测 Frida 时,可采用以下方案:
- 修改 frida-server 二进制名,避免默认端口
- 使用 frida-gadget注入到 APK 内部
- Hook 检测函数返回假结果
- 使用Objection框架集成反检测
基础反检测脚本:
javascript
运行
// 隐藏Frida端口 Interceptor.attach(Module.findExportByName("libc.so", "connect"), { onEnter: function (args) { // 检测27042端口连接并阻断 } }); // 绕过/proc/pid/maps检测 // 绕过frida特征字符串检测5.4 性能优化与批量 Hook
- 使用
Java.use()缓存类对象,避免重复查找 - 减少
console.log调用,使用批量输出 - 针对性 Hook,不要全量 Hook 所有方法
- 复杂逻辑使用 Python 端处理,JS 端只做数据采集
六、常用工具与框架推荐
6.1 Objection
基于 Frida 封装的高级工具,一键完成 SSL 绕过、内存漫游、类搜索等操作:
bash
运行
# 安装 pip install objection # 启动并自动SSL绕过 objection -g com.example.app explore6.2 r0capture
安卓应用层通杀脚本,支持 HttpURLConnection、OkHttp、Volley 等多种网络库:
bash
运行
frida -U -f com.example.app -l r0capture.js --no-pause6.3 OkHttpLogger-Frida
专门针对 OkHttp 的日志打印工具,自动适配不同版本 OkHttp,支持混淆识别。
七、常见问题排查
Failed to spawn: unable to find process
- 检查包名是否正确
- 确认 frida-server 已启动且版本匹配
- 尝试使用
-D指定设备 ID
Class not found
- 类可能被混淆,用 jadx 反编译确认类名
- 检查是否在正确进程中(多进程 App)
- 使用
Java.enumerateLoadedClasses()搜索
Hook 不生效
- 确认方法签名正确,使用
overload指定参数 - Spawn 模式下冷启动 App 再 Hook
- 检查是否有加固或脱壳问题
- 确认方法签名正确,使用
App 闪退
- 可能触发了反检测机制
- 检查脚本中是否有空指针异常
- 逐步注释定位问题代码
八、总结
Frida Hook 网络请求是 Android 逆向分析的核心技能,从简单的 URL 打印到复杂的加密参数分析,再到 Native 层 socket 拦截,技术深度层层递进。掌握本文介绍的 HttpURLConnection 基础 Hook、OkHttp 全链路拦截、SSL 证书绕过、请求响应篡改等技术,足以应对绝大多数 App 的网络调试需求。
学习 Frida 的关键在于实战。建议从简单的 Demo App 入手,逐步尝试 Hook 真实商业应用,在实践中积累对抗经验。同时要持续关注 Frida 官方更新和社区优秀脚本,不断完善自己的工具库。
最后提醒:Frida 技术仅可用于合法的安全测试与学习研究,请勿用于非法用途。