当前位置: 首页 > news >正文

【Frida Android】实战篇3:基于 OkHttp 库的 Hook 抓包

文章目录

  • 前言
  • 1. OkHttp 介绍
    • 1.1 作用
    • 1.2 常用类与方法
  • 2. 前置操作
    • 2.1 启动 frida-server
    • 2.2 启动脚本
  • 3. Hook 思路
    • 3.1 断点调试
    • 3.2 Hook 脚本
    • 3.3 脚本详解
  • 4. 技术总结

⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。

前言

上一章我们已掌握 HTTPS 中间人抓包与原生 HTTP 请求的 Hook 抓包技巧,而在实际企业级 Android 开发中,OkHttp 库因高效、稳定的特性被广泛应用于网络请求场景。

针对这类基于 OkHttp 实现的网络通信,传统抓包方式可能面临适配难题,因此本章将聚焦企业常用的 OkHttp 请求 Hook 抓包方案,通过拆解 OkHttp 核心类与方法、梳理 Hook 思路、编写实战脚本,帮助大家实现对 OkHttp 请求与响应信息的完整捕获,为逆向分析、接口调试等工作提供技术支撑。

本章节使用的示例 APK 和 APK 源码如下:

通过网盘分享的文件:
链接: https://pan.baidu.com/s/1y5rnZKsjKtwZkMP6K2zriA?pwd=m2qj
提取码: m2qj

1. OkHttp 介绍

1.1 作用

OkHttp 是一款由 Square 公司开发的高效 HTTP 客户端库,广泛应用于 Android 平台和 Java 项目中。它的核心作用是简化 HTTP 通信流程,支持 HTTP/1.1、HTTP/2 以及WebSocket 协议,提供了连接池管理、请求重试、缓存机制、拦截器等功能,能够显著提升网络请求的性能和稳定性。在移动应用开发中,OkHttp 常被用于与后端服务器进行数据交互,处理 GET、POST 等各类 HTTP 请求。

1.2 常用类与方法

在实际开发和逆向分析中,以下类和方法是核心关注点:

  • okhttp3.Request$Builder:请求构建器内部类,用于组装 HTTP 请求的各项参数(URL、请求方法、请求头、请求体等),核心方法 build() 用于生成最终的 Request 对象。
  • okhttp3.Request:表示一个 HTTP 请求,通过 url()method()headers()body() 等方法可获取请求的 URL、方法、头信息和体内容。
  • okhttp3.Response$Builder:响应构建器内部类,用于组装 HTTP 响应的各项参数(状态码、响应头、响应体等),核心方法 build() 用于生成最终的 Response 对象。
  • okhttp3.Response:表示一个 HTTP 响应,通过 code()message()headers()request() 等方法可获取响应状态码、消息、头信息和对应的请求对象。
  • okhttp3.ResponseBody:表示响应体内容,核心方法 string() 用于将响应体转换为字符串形式,是获取响应数据的关键。
  • okhttp3.OkHttpClient:HTTP 客户端实例,通过 newCall(Request) 方法创建一个 Call 对象,用于执行请求(同步 execute() 或异步 enqueue(Callback))。

2. 前置操作

与上一章节相同,只是替换了示例 APK 应用。

2.1 启动 frida-server

进入模拟器设备中启动 frida 服务

在这里插入图片描述

2.2 启动脚本

与和上一章相同:

import frida
import sys
import time
from datetime import datetime# 创建日志文件
log_file = open("frida_http_monitor.log", "w", encoding="utf-8")def on_message(message, data):if message['type'] == 'send':payload = message['payload']if isinstance(payload, dict) and payload.get('type') == 'http_log':# 写入日志文件log_msg = payload['message']log_file.write(log_msg + "\n")log_file.flush()  # 确保立即写入磁盘print(f"[Hook 日志] {log_msg}")else:print(f"[Hook 消息] {payload}")elif message['type'] == 'error':error_msg = f"[错误] {str(message)}"log_file.write(error_msg + "\n")log_file.flush()print(error_msg)# 目标应用包名
PACKAGE_NAME = "com.example.fridaapk"def main():try:device = frida.get_usb_device(timeout=10)print(f"已连接设备:{device.name}")print(f"启动进程 {PACKAGE_NAME}...")pid = device.spawn([PACKAGE_NAME])device.resume(pid)time.sleep(2)process = device.attach(pid)print(f"已附加到进程 PID: {pid}")with open("./js/compiled_hook.js", "r", encoding="utf-8") as f:js_code = f.read()script = process.create_script(js_code)script.on('message', on_message)script.load()time.sleep(2)print("JS 脚本注入成功,开始监控...(按 Ctrl+C 退出)")# 等待用户输入try:sys.stdin.read()except KeyboardInterrupt:print("\n正在退出...")except frida.TimedOutError:print("未找到USB设备")except frida.ProcessNotFoundError:print(f"应用 {PACKAGE_NAME} 未安装")except FileNotFoundError:print("未找到 js 脚本,请检查路径")except Exception as e:print(f"异常:{str(e)}")finally:# 关闭日志文件if 'log_file' in locals():log_file.close()if 'process' in locals():process.detach()print("程序退出")if __name__ == "__main__":main()

3. Hook 思路

3.1 断点调试

为捕获完整的请求信息,我们在请求构建的最终方法处设置断点。在下面 APK 源代码中,在 build() 方法处下断点:

val request = Request.Builder().url(url).post(requestBody).addHeader("Content-Type", "application/json").build()    // 在此处设置断点

在这里插入图片描述

启动应用并进入调试模式,当触发 POST 请求时,程序会暂停在该断点处。此时观察调用栈,首先会执行 okhttp3.RequestBody$Companion$toRequestBody$3@83519bf 相关逻辑,我们继续通过单步调试深入调用链。

在这里插入图片描述

按 F7 键步入内部调用后,可定位到 okhttp3.Request$Builder@f5109a2(其中 $BuilderRequest 类的内部类,@f5109a2 为实例内存地址,无需关注具体值)。查看该内部类的结构可知,Request.Builder 的核心作用是组装请求的各项参数,包括 URL、请求头(headers)、请求方法(method)等。

在这里插入图片描述

为获取响应信息,我们在 client.newCall(request).execute() 方法处补充断点。

在这里插入图片描述

经过多步调试后,可确定响应信息由 Response$Builder 实例构建。展开该实例可观察到其包含响应体(ResponseBody)、响应状态码(status code)、响应头(headers)等关键信息,这些均为我们需要捕获的核心内容。

在这里插入图片描述

进一步分析发现,Response$Builder 实例中,ResponseBody 字段关联了具体的响应体实现类,因此通过 Hook okhttp3.ResponseBody 类可更加简洁、直接获取响应体的详细内容。

3.2 Hook 脚本

在上一章节的断点调试结果,我们明确了OkHttp库的核心类结构与方法调用逻辑——这为后续设计Hook脚本提供了关键依据。

具体而言,调试过程让我们掌握了诸如okhttp3.Request$Builder这类内部类的命名规则,理清了Request.Builder().build()的执行链路,同时观察到okhttp3.RequestBodyokhttp3.Request$Builder之间的调用关系。

在Frida Hook技术中,针对此类底层内部类进行拦截是一种高效且常用的手段。基于上述调试所得的类结构与调用链信息,我们可以精准定位需要Hook的目标方法,从而实现对HTTP请求与响应信息的完整捕获。

import Java from "frida-java-bridge";// 统一日志函数
function log(message) {const timestamp = new Date().toISOString();const logStr = `[${timestamp}] ${message}`;console.log(logStr);send({ type: "http_log", message: logStr });
}// hook OkHttp请求/响应信息
Java.perform(function () {// Hook Request.Builder.build() 方法来捕获完整的请求信息var RequestBuilder = Java.use('okhttp3.Request$Builder');RequestBuilder.build.implementation = function () {var request = this.build();log('=== OkHttp Request Info ===');log('URL: ' + request.url().toString());log('Method: ' + request.method());// 读取请求头var headers = request.headers();log('Headers:');for (var i = 0; i < headers.size(); i++) {log('  ' + headers.name(i) + ': ' + headers.value(i));}// 读取请求体var requestBody = request.body();if (requestBody) {try {var buffer = Java.use('okio.Buffer').$new();requestBody.writeTo(buffer);var bodyContent = buffer.readUtf8();log('Request Body: ' + bodyContent);} catch (e) {log('Failed to read request body: ' + e);}}return request;};// Hook Response.Builder.build() 方法,在响应构建完成时记录完整信息var ResponseBuilder = Java.use('okhttp3.Response$Builder');ResponseBuilder.build.implementation = function () {var response = this.build();log('=== OkHttp Response Info ===');log('Status Code: ' + response.code());log('Message: ' + response.message());log('URL: ' + response.request().url().toString());// 记录响应头var headers = response.headers();log('Headers:');for (var i = 0; i < headers.size(); i++) {log('  ' + headers.name(i) + ': ' + headers.value(i));}return response;};// 记录响应体var ResponseBody = Java.use('okhttp3.ResponseBody');ResponseBody.string.implementation = function () {var result = this.string();log('Response Content: ' + result);return result;};
});

输出日志如下:

[2025-11-11T02:35:16.031Z] === OkHttp Request Info ===
[2025-11-11T02:35:16.032Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.032Z] Method: GET
[2025-11-11T02:35:16.033Z] Headers:
[2025-11-11T02:35:16.033Z]   Content-Type: application/json
[2025-11-11T02:35:16.033Z]   Cache-Control: max-age=3600
[2025-11-11T02:35:16.036Z] === OkHttp Request Info ===
[2025-11-11T02:35:16.036Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.036Z] Method: GET
[2025-11-11T02:35:16.037Z] Headers:
[2025-11-11T02:35:16.037Z]   Content-Type: application/json
[2025-11-11T02:35:16.037Z]   Cache-Control: max-age=3600
[2025-11-11T02:35:16.037Z]   Host: 192.168.10.6:3000
[2025-11-11T02:35:16.037Z]   Connection: Keep-Alive
[2025-11-11T02:35:16.037Z]   Accept-Encoding: gzip
[2025-11-11T02:35:16.038Z]   User-Agent: okhttp/5.3.0
[2025-11-11T02:35:16.069Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.070Z] Status Code: 200
[2025-11-11T02:35:16.070Z] Message: OK
[2025-11-11T02:35:16.070Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.071Z] Headers:
[2025-11-11T02:35:16.071Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.071Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.071Z]   Content-Type: application/json
[2025-11-11T02:35:16.072Z]   Content-Length: 172
[2025-11-11T02:35:16.072Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.072Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.072Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.073Z]   Connection: close
[2025-11-11T02:35:16.074Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.074Z] Status Code: 200
[2025-11-11T02:35:16.074Z] Message: OK
[2025-11-11T02:35:16.074Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.074Z] Headers:
[2025-11-11T02:35:16.074Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.074Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.074Z]   Content-Type: application/json
[2025-11-11T02:35:16.074Z]   Content-Length: 172
[2025-11-11T02:35:16.075Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.075Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.075Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.075Z]   Connection: close
[2025-11-11T02:35:16.076Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.076Z] Status Code: 200
[2025-11-11T02:35:16.076Z] Message: OK
[2025-11-11T02:35:16.077Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.077Z] Headers:
[2025-11-11T02:35:16.077Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.077Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.077Z]   Content-Type: application/json
[2025-11-11T02:35:16.077Z]   Content-Length: 172
[2025-11-11T02:35:16.078Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.078Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.078Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.078Z]   Connection: close
[2025-11-11T02:35:16.079Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.079Z] Status Code: 200
[2025-11-11T02:35:16.079Z] Message: OK
[2025-11-11T02:35:16.079Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.079Z] Headers:
[2025-11-11T02:35:16.079Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.080Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.080Z]   Content-Type: application/json
[2025-11-11T02:35:16.080Z]   Content-Length: 172
[2025-11-11T02:35:16.080Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.080Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.080Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.080Z]   Connection: close
[2025-11-11T02:35:16.081Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.081Z] Status Code: 200
[2025-11-11T02:35:16.081Z] Message: OK
[2025-11-11T02:35:16.081Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.082Z] Headers:
[2025-11-11T02:35:16.082Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.082Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.082Z]   Content-Type: application/json
[2025-11-11T02:35:16.082Z]   Content-Length: 172
[2025-11-11T02:35:16.082Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.082Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.082Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.082Z]   Connection: close
[2025-11-11T02:35:16.083Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.083Z] Status Code: 200
[2025-11-11T02:35:16.083Z] Message: OK
[2025-11-11T02:35:16.083Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.083Z] Headers:
[2025-11-11T02:35:16.083Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.084Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.084Z]   Content-Type: application/json
[2025-11-11T02:35:16.084Z]   Content-Length: 172
[2025-11-11T02:35:16.084Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.084Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.084Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.084Z]   Connection: close
[2025-11-11T02:35:16.086Z] Response Content: {"client_ip": "192.168.10.7","code": 200,"msg": "GET Request Success","request_method": "GET","request_params": {},"server_time": "2025-11-11 10:35:17"
}[2025-11-11T02:35:16.517Z] === OkHttp Request Info ===
[2025-11-11T02:35:16.517Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.517Z] Method: POST
[2025-11-11T02:35:16.517Z] Headers:
[2025-11-11T02:35:16.517Z]   Content-Type: application/json
[2025-11-11T02:35:16.519Z] Request Body: {"body":"frida body","title":"frida title"}
[2025-11-11T02:35:16.520Z] === OkHttp Request Info ===
[2025-11-11T02:35:16.520Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.520Z] Method: POST
[2025-11-11T02:35:16.520Z] Headers:
[2025-11-11T02:35:16.520Z]   Content-Type: application/json; charset=utf-8
[2025-11-11T02:35:16.520Z]   Content-Length: 43
[2025-11-11T02:35:16.520Z]   Host: 192.168.10.6:3000
[2025-11-11T02:35:16.521Z]   Connection: Keep-Alive
[2025-11-11T02:35:16.521Z]   Accept-Encoding: gzip
[2025-11-11T02:35:16.521Z]   User-Agent: okhttp/5.3.0
[2025-11-11T02:35:16.521Z] Request Body: {"body":"frida body","title":"frida title"}
[2025-11-11T02:35:16.567Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.567Z] Status Code: 200
[2025-11-11T02:35:16.567Z] Message: OK
[2025-11-11T02:35:16.568Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.568Z] Headers:
[2025-11-11T02:35:16.568Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.568Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.569Z]   Content-Type: application/json
[2025-11-11T02:35:16.569Z]   Content-Length: 231
[2025-11-11T02:35:16.569Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.570Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.570Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.570Z]   Connection: close
[2025-11-11T02:35:16.570Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.570Z] Status Code: 200
[2025-11-11T02:35:16.570Z] Message: OK
[2025-11-11T02:35:16.571Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.571Z] Headers:
[2025-11-11T02:35:16.572Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.572Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.572Z]   Content-Type: application/json
[2025-11-11T02:35:16.572Z]   Content-Length: 231
[2025-11-11T02:35:16.573Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.573Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.573Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.574Z]   Connection: close
[2025-11-11T02:35:16.575Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.575Z] Status Code: 200
[2025-11-11T02:35:16.575Z] Message: OK
[2025-11-11T02:35:16.575Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.575Z] Headers:
[2025-11-11T02:35:16.575Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.575Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.575Z]   Content-Type: application/json
[2025-11-11T02:35:16.576Z]   Content-Length: 231
[2025-11-11T02:35:16.576Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.576Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.576Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.576Z]   Connection: close
[2025-11-11T02:35:16.577Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.577Z] Status Code: 200
[2025-11-11T02:35:16.577Z] Message: OK
[2025-11-11T02:35:16.577Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.578Z] Headers:
[2025-11-11T02:35:16.578Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.579Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.579Z]   Content-Type: application/json
[2025-11-11T02:35:16.579Z]   Content-Length: 231
[2025-11-11T02:35:16.579Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.580Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.580Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.580Z]   Connection: close
[2025-11-11T02:35:16.580Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.581Z] Status Code: 200
[2025-11-11T02:35:16.581Z] Message: OK
[2025-11-11T02:35:16.581Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.581Z] Headers:
[2025-11-11T02:35:16.581Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.582Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.582Z]   Content-Type: application/json
[2025-11-11T02:35:16.582Z]   Content-Length: 231
[2025-11-11T02:35:16.582Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.582Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.582Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.583Z]   Connection: close
[2025-11-11T02:35:16.583Z] === OkHttp Response Info ===
[2025-11-11T02:35:16.583Z] Status Code: 200
[2025-11-11T02:35:16.583Z] Message: OK
[2025-11-11T02:35:16.584Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T02:35:16.584Z] Headers:
[2025-11-11T02:35:16.585Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T02:35:16.585Z]   Date: Tue, 11 Nov 2025 02:35:17 GMT
[2025-11-11T02:35:16.585Z]   Content-Type: application/json
[2025-11-11T02:35:16.585Z]   Content-Length: 231
[2025-11-11T02:35:16.586Z]   Access-Control-Allow-Origin: *
[2025-11-11T02:35:16.586Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T02:35:16.586Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T02:35:16.586Z]   Connection: close
[2025-11-11T02:35:16.588Z] Response Content: {"client_ip": "192.168.10.7","code": 200,"msg": "POST Request Success","received_params": {"body": "frida body","title": "frida title"},"request_method": "POST","server_time": "2025-11-11 10:35:17"
}

包含 HTTP 请求的 URL、请求方法、请求头、请求体(body)、响应体,可以看到日志文件中的记录有很多重复内容。

这并非脚本设计问题,而是由 OkHttp 框架的核心机制——Interceptor(拦截器) 导致的。它是 OkHttp 中用于拦截、处理 HTTP 请求和响应的组件。你可以把它理解成请求/响应在发送和接收过程中经过的“检查站”——每个拦截器都可以对请求进行修改(比如添加头信息、加密参数),或对响应进行处理(比如解密数据、记录日志),然后将处理后的内容传递给下一个拦截器。

OkHttp 的拦截器分为两类:

  • 应用拦截器(Application Interceptors):面向开发者,通常用于添加全局头信息、打印日志等业务逻辑。
  • 网络拦截器(Network Interceptors):更底层,会处理实际网络请求(如 DNS 解析、连接复用等),甚至包括重试、重定向等场景。

拦截器的“链式调用”导致重复日志

OkHttp 的拦截器采用链式结构(Interceptor Chain)工作:一个请求从发起到收到响应,会依次经过所有拦截器(包括 OkHttp 内置的拦截器和开发者自定义的拦截器)。

具体流程如下:

  1. 当调用 Request.Builder().build() 构建请求时,请求会先进入第一个拦截器;
  2. 拦截器处理后,将请求传递给下一个拦截器;
  3. 经过所有拦截器后,请求才会真正发送到服务器;
  4. 服务器返回响应后,响应会按相反顺序经过所有拦截器,最终回到应用层。

正因为这种“链式传递”,我们 Hook 的 Request.Builder.build()Response.Builder.build() 方法会被每个拦截器调用一次。例如:

  • 如果 OkHttp 内部有 3 个内置拦截器,加上 1 个自定义应用拦截器,那么一个请求会触发 4 次 build() 调用;
  • 对应的响应也会经过同样的拦截器链,导致响应相关的 Hook 方法被多次触发。

这就是日志中出现重复内容的核心原因——并非请求被发送了多次,而是同一请求/响应在拦截器链中被多次处理,每次处理都会触发我们的 Hook 逻辑。

了解这一机制后,我们就能理解如何优化脚本:通过全局变量记录最近一次的请求/响应信息,只在最终获取响应体时统一输出,从而避免拦截器链式调用导致的重复日志。

最终脚本 优化后如下:请求和响应内容进行了去重,你也可以根据自己的需求继续改造,以获得适合自己业务需要的数据,该脚本也可以作为 OkHttp 库的通用 Hook 脚本。

import Java from "frida-java-bridge";// 统一日志函数
function log(message) {const timestamp = new Date().toISOString();const logStr = `[${timestamp}] ${message}`;console.log(logStr);send({ type: "http_log", message: logStr });
}// 添加全局变量来存储响应信息
var lastResponseInfo = {headers: [],body: "",statusCode: 0,url: ""
};// 添加全局变量来存储请求信息
var lastRequestInfo = {url: "",method: "",headers: [],body: ""
};Java.perform(function () {// Hook Request.Builder.build() 方法来捕获完整的请求信息var RequestBuilder = Java.use('okhttp3.Request$Builder');RequestBuilder.build.implementation = function () {var request = this.build();// 更新最后一次请求信息lastRequestInfo.url = request.url().toString();lastRequestInfo.method = request.method();lastRequestInfo.headers = [];// 读取请求头var headers = request.headers();for (var i = 0; i < headers.size(); i++) {lastRequestInfo.headers.push({name: headers.name(i),value: headers.value(i)});}// 读取请求体var requestBody = request.body();lastRequestInfo.body = "";if (requestBody) {try {var buffer = Java.use('okio.Buffer').$new();requestBody.writeTo(buffer);lastRequestInfo.body = buffer.readUtf8();} catch (e) {// 忽略错误}}return request;};// Hook Response.Builder.build() 方法,在响应构建完成时记录完整信息var ResponseBuilder = Java.use('okhttp3.Response$Builder');ResponseBuilder.build.implementation = function () {var response = this.build();// 更新最后一次响应头信息lastResponseInfo.statusCode = response.code();lastResponseInfo.url = response.request().url().toString();lastResponseInfo.headers = [];var headers = response.headers();for (var i = 0; i < headers.size(); i++) {lastResponseInfo.headers.push({name: headers.name(i),value: headers.value(i)});}return response;};// 记录响应体,并输出完整响应信息var ResponseBody = Java.use('okhttp3.ResponseBody');ResponseBody.string.implementation = function () {var result = this.string();// 更新响应体并记录完整响应信息lastResponseInfo.body = result;// 输出完整的请求信息log('=== Complete OkHttp Request ===');log('URL: ' + lastRequestInfo.url);log('Method: ' + lastRequestInfo.method);log('Headers:');for (var i = 0; i < lastRequestInfo.headers.length; i++) {log('  ' + lastRequestInfo.headers[i].name + ': ' + lastRequestInfo.headers[i].value);}if (lastRequestInfo.body) {log('Request Body: ' + lastRequestInfo.body);}// 输出完整的响应信息log('=== Complete OkHttp Response ===');log('URL: ' + lastResponseInfo.url);log('Status Code: ' + lastResponseInfo.statusCode);log('Headers:');for (var i = 0; i < lastResponseInfo.headers.length; i++) {log('  ' + lastResponseInfo.headers[i].name + ': ' + lastResponseInfo.headers[i].value);}log('Response Body: ' + lastResponseInfo.body);log('================================');return result;};
});

优化后日志文件frida_http_monitor.log记录如下:可以看到内容更加简洁清晰。

[2025-11-11T04:40:10.270Z] === Complete OkHttp Request ===
[2025-11-11T04:40:10.270Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T04:40:10.271Z] Method: GET
[2025-11-11T04:40:10.271Z] Headers:
[2025-11-11T04:40:10.271Z]   Content-Type: application/json
[2025-11-11T04:40:10.271Z]   Cache-Control: max-age=3600
[2025-11-11T04:40:10.271Z]   Host: 192.168.10.6:3000
[2025-11-11T04:40:10.271Z]   Connection: Keep-Alive
[2025-11-11T04:40:10.271Z]   Accept-Encoding: gzip
[2025-11-11T04:40:10.271Z]   User-Agent: okhttp/5.3.0
[2025-11-11T04:40:10.271Z] === Complete OkHttp Response ===
[2025-11-11T04:40:10.271Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T04:40:10.271Z] Status Code: 200
[2025-11-11T04:40:10.271Z] Headers:
[2025-11-11T04:40:10.271Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T04:40:10.271Z]   Date: Tue, 11 Nov 2025 04:40:11 GMT
[2025-11-11T04:40:10.271Z]   Content-Type: application/json
[2025-11-11T04:40:10.271Z]   Content-Length: 172
[2025-11-11T04:40:10.272Z]   Access-Control-Allow-Origin: *
[2025-11-11T04:40:10.272Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T04:40:10.272Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T04:40:10.272Z]   Connection: close
[2025-11-11T04:40:10.272Z] Response Body: {"client_ip": "192.168.10.7","code": 200,"msg": "GET Request Success","request_method": "GET","request_params": {},"server_time": "2025-11-11 12:40:11"
}[2025-11-11T04:40:10.272Z] ================================
[2025-11-11T04:40:10.886Z] === Complete OkHttp Request ===
[2025-11-11T04:40:10.886Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T04:40:10.886Z] Method: POST
[2025-11-11T04:40:10.886Z] Headers:
[2025-11-11T04:40:10.886Z]   Content-Type: application/json; charset=utf-8
[2025-11-11T04:40:10.886Z]   Content-Length: 43
[2025-11-11T04:40:10.886Z]   Host: 192.168.10.6:3000
[2025-11-11T04:40:10.886Z]   Connection: Keep-Alive
[2025-11-11T04:40:10.886Z]   Accept-Encoding: gzip
[2025-11-11T04:40:10.886Z]   User-Agent: okhttp/5.3.0
[2025-11-11T04:40:10.886Z] Request Body: {"body":"frida body","title":"frida title"}
[2025-11-11T04:40:10.886Z] === Complete OkHttp Response ===
[2025-11-11T04:40:10.886Z] URL: https://192.168.10.6:3000/api/frida/test
[2025-11-11T04:40:10.886Z] Status Code: 200
[2025-11-11T04:40:10.886Z] Headers:
[2025-11-11T04:40:10.886Z]   Server: Werkzeug/3.1.3 Python/3.13.2
[2025-11-11T04:40:10.886Z]   Date: Tue, 11 Nov 2025 04:40:12 GMT
[2025-11-11T04:40:10.886Z]   Content-Type: application/json
[2025-11-11T04:40:10.886Z]   Content-Length: 231
[2025-11-11T04:40:10.886Z]   Access-Control-Allow-Origin: *
[2025-11-11T04:40:10.886Z]   Access-Control-Allow-Methods: GET, POST, OPTIONS
[2025-11-11T04:40:10.886Z]   Access-Control-Allow-Headers: Content-Type
[2025-11-11T04:40:10.886Z]   Connection: close
[2025-11-11T04:40:10.886Z] Response Body: {"client_ip": "192.168.10.7","code": 200,"msg": "POST Request Success","received_params": {"body": "frida body","title": "frida title"},"request_method": "POST","server_time": "2025-11-11 12:40:12"
}[2025-11-11T04:40:10.886Z] ================================

3.3 脚本详解

  1. Hook 请求构建:Request.Builder.build()

    var RequestBuilder = Java.use('okhttp3.Request$Builder');
    RequestBuilder.build.implementation = function () {var request = this.build(); // 调用原方法获取请求对象// 存储请求信息到全局变量lastRequestInfo.url = request.url().toString();lastRequestInfo.method = request.method();lastRequestInfo.headers = [];// 遍历请求头并存储var headers = request.headers();for (var i = 0; i < headers.size(); i++) {lastRequestInfo.headers.push({name: headers.name(i),value: headers.value(i)});}// 读取并存储请求体(通过 okio.Buffer 转换)var requestBody = request.body();lastRequestInfo.body = "";if (requestBody) {try {var buffer = Java.use('okio.Buffer').$new(); // 创建缓冲区requestBody.writeTo(buffer); // 将请求体写入缓冲区lastRequestInfo.body = buffer.readUtf8(); // 从缓冲区读取字符串} catch (e) {}}return request; // 返回原请求对象,不影响正常流程
    };
    
    • 作用:捕获请求的 URL、方法、头和体,并存储到 lastRequestInfo 全局变量。
    • 关键:通过 Java.use('类名') 获取内部类 Request$Builder,用 implementation 替换 build() 方法,既保留原功能(this.build()),又新增信息收集逻辑。
  2. Hook 响应构建:Response.Builder.build()

    var ResponseBuilder = Java.use('okhttp3.Response$Builder');
    ResponseBuilder.build.implementation = function () {var response = this.build(); // 调用原方法获取响应对象// 存储响应元信息到全局变量lastResponseInfo.statusCode = response.code();lastResponseInfo.url = response.request().url().toString();lastResponseInfo.headers = [];// 遍历响应头并存储var headers = response.headers();for (var i = 0; i < headers.size(); i++) {lastResponseInfo.headers.push({name: headers.name(i),value: headers.value(i)});}return response; // 返回原响应对象,不影响正常流程
    };
    
    • 作用:捕获响应的状态码、URL、响应头,并存储到 lastResponseInfo 全局变量。
    • 关键:响应与请求通过 response.request().url() 关联,确保后续输出时请求与响应对应。
  3. Hook 响应体:ResponseBody.string()

    var ResponseBody = Java.use('okhttp3.ResponseBody');
    ResponseBody.string.implementation = function () {var result = this.string(); // 调用原方法获取响应体字符串lastResponseInfo.body = result; // 存储响应体到全局变量// 输出完整的请求和响应信息log('=== Complete OkHttp Request ===');// ... 输出请求的 URL、方法、头、体log('=== Complete OkHttp Response ===');// ... 输出响应的 URL、状态码、头、体return result; // 返回原响应体,不影响应用解析
    };
    
    • 作用:在响应体被应用读取时,将之前存储的请求和响应信息一次性输出,避免重复日志。
    • 关键:string() 是响应体解析的最后一步,此时 lastRequestInfolastResponseInfo 已包含完整数据,适合作为输出触发点。

4. 技术总结

  1. OkHttp 核心逻辑与 hook 点选择
    OkHttp 通过 Request.BuilderResponse.Builder 分别构建请求和响应,其 build() 方法是参数组装的终点,适合捕获完整的元信息;ResponseBody.string() 是响应体解析的关键方法,适合捕获响应内容。
  2. 断点调试的价值
    通过调试可明确数据在哪个方法中最终确定(如 build() 方法),避免 hook 中间过程导致的信息不完整或重复。
  3. Frida 脚本设计思路
    • 用全局变量暂存请求和响应信息,解决 OkHttp 拦截器机制导致的重复日志问题。
    • 在响应体解析时统一输出,确保一次请求-响应对应一条完整日志。
http://www.dtcms.com/a/598192.html

相关文章:

  • 文心 5.0 来了,百度大模型的破局之战
  • 做多个网站 买vpsword和wordpress
  • 网站文章伪原创怎么做icp备案查询网站
  • 酒仙桥网站建设中国建筑官网一测二测成绩多少算及格
  • 如何防止 IPA 被反编译,工程化防护与多工具组合实战(静态 + 成品 + 运行时 + 治理)
  • leetcode 474
  • 有哪些C++20特性可以在Dev-C++中使用?
  • 网站如何不需要备案电白网站开发公司
  • 【数据结构】单链表核心知识点梳理
  • 中山做网站排名国外中文网站域名注册商
  • 在 LangFlow 中,**节点(Node)是构成工作流的核心基本单元**
  • 普中51单片机学习笔记-数码管
  • Python 开发环境安装与配置全指南(2025版)
  • 上海建设官方网站设计学类包括哪些专业
  • 网站 网页制作南京广告公司黄页
  • 如何用网站做推广网络营销策划书封面
  • 宁波seo建站价格wordpress长文章分页代码
  • AI 赋能教育新生态 | 教学创新、范式转型与实践路径探析
  • 网站开发按钮素材搜索视频 网站开发
  • 二手车网站开发多少钱网站里的课程配图怎么做
  • 网站上传模板后太原制作网站的公司
  • 【复习408】计算机网络应用层协议详解
  • 在那些网站做宣传更好wordpress怎么安装上服务器
  • 2023年php凉透了大连seo顾问
  • Redis的知识整理《1》
  • 怎样免费建一个网站网站开发培训费用
  • 数据产品之数据埋点
  • 7.MySQL这的内置函数
  • 网站建设设计师招募重庆网络seo公司
  • -1网站建设购物中心网站建设