Lazy loaded image
Frida学习笔记(九):r0capture 源码精读 · 不靠代理的抓包是怎么实现的
Words 7417Read Time 19 min
2026-7-9
2026-7-9
type
Post
status
Published
date
Jul 9, 2026
slug
summary
本篇是一次源码级精读。我们把 r0ysue/r0capture 这个不到 30 KB 的项目从头读到尾——它是中文 Android 安全圈最常被使用的 Frida 抓包工具,几乎能干掉所有"代理抓不到"的场景。读懂它,你既能立刻拿来用,也能改它扩展它,并且把 Java 层 Hook、Native 层 Hook、socket fd 还原、pcap 文件结构这些技术一次性串起来。
tags
frida
Android
逆向工程
category
公众号
icon
password
网址
上次编辑时间
Jul 9, 2026 09:57 AM
comment
AI 总结
本篇是一次源码级精读。我们把 r0ysue/r0capture 这个不到 30 KB 的项目从头读到尾——它是中文 Android 安全圈最常被使用的 Frida 抓包工具,几乎能干掉所有"代理抓不到"的场景。读懂它,你既能立刻拿来用,也能改它扩展它,并且把 Java 层 Hook、Native 层 Hook、socket fd 还原、pcap 文件结构这些技术一次性串起来。

一、r0capture 总览

1.1 它从哪里来

r0capture 不是 r0ysue 凭空写的,有清晰的血缘:
notion image
r0capture 的三代演化
r0capture.py 开头的 Apache 2.0 头声明 Copyright 2017 Google Inc.,就能理解 pcap 那段写得为什么如此规整——那是 Google 工程师七年前的代码,移植到今天依然几乎没改。

1.2 28 KB 代码做了什么

仓库结构非常紧凑:
三层架构:
| 层 | 文件 | 职责 | | :-- | :-- | :-- | | CLI / pcap 写入 | r0capture.py | 参数解析、设备连接、spawn/attach、加载 JS、on_message 回调里把 SSL\_read/write 事件伪造成 pcap 包 | | Frida JS · Native Hook | script.js:1-216 | Hook SSL_read / SSL_write,还原五元组和会话 ID,send() 给 Python | | Frida JS · Java Hook | script.js:218-336 | 抓 Java 调用栈、抓明文 HTTP、抓客户端证书、定位 Pinning 代码 |
notion image
r0capture 整体架构

1.3 一条命令的生命周期

python3 r0capture.py -U -f com.example.app -v -p out.pcap 为例,从按下回车到第一个数据包落盘,发生了什么:
notion image
一条命令的生命周期
下面我们逐层拆开。

二、Native SSL Hook · 数据从哪里来

这一章是 r0capture 的核心。所有"明文数据"都是从这两个函数捕获的:
  • SSL_write(ssl, buf, num):App 把明文交给 SSL 库准备加密时调用
  • SSL_read(ssl, buf, num):SSL 库解密完毕、把明文交给 App 时调用
在这两个函数被调用的瞬间,数据是明文——这是整个 r0capture 设计的根基。

2.1 为什么是 SSL\_read / SSL\_write

Android 系统的 HTTPS 调用链大致是:
notion image
r0capture 在 BoringSSL.SSL\_write 切入:明文加密分水岭
SSL_write 之上是明文,之下是加密。Hook 它就拿到了最干净的数据——不用做协议解析、不用关心 SSL Pinning、不用解压缩、不用处理 chunked encoding。
iOS 是同理,只是把 BoringSSL 换成 libboringssl.dylib。这就是 r0capture 同一脚本能跨 Android/iOS 工作的原因。

2.2 ApiResolver · 多平台 / 多版本兼容

script.js:58-111initializeGlobals 函数是地址解析的入口:
两组关键导出,分别来自 TLS 库和 C 库。注意几个细节:
**通配符 *libssl***:而不是 libssl.so。这是因为不同 App 内嵌的 BoringSSL 可能名字不同——系统的 /system/lib64/libssl.so、Conscrypt 的 libconscrypt_jni.so、甚至 App 自带的 libxxxssl.so 都可能命中。
**ApiResolver("module") + enumerateMatches**:相比 Module.findExportByNameApiResolver 能枚举所有模块里的同名导出。这对应了真实场景:很多 App 同时加载了多个 SSL 实现(系统 Conscrypt + 自带 BoringSSL),需要明确选择 Hook 哪一个。
多匹配时的优先级script.js:79-94):
优先选 com.android.conscrypt 命名空间下的——因为这是 Android 系统标准 TLS 路径,绝大多数 App 走这条;只有少数自带 BoringSSL 的 App 才会有其他 match。
**SSL_get_fd 的兜底**(script.js:73-77):
SSL_get_fd 在某些精简版 BoringSSL 里可能被剥离。其他函数找不到就 throw(脚本无法工作),但 SSL_get_fd 找不到只是降级——五元组解析会失败,但抓数据这条主路径还能跑。这种"区分核心依赖 vs 可降级依赖"的设计值得学。

2.3 SSL\* → fd → 五元组的还原链路

抓到 buffer 只是第一步。要让 Wireshark 把它当作合法的 TCP 流来展示,必须知道这个包的源/目的 IP 和端口。这个还原链路是 r0capture 最巧的设计。
notion image
从 SSL 指针还原五元组
#### 第一跳:SSL 对象 → fd
script.js:103SSL_get_fd 包成 NativeFunction:
调用 SSL_get_fd(ssl) 返回 socket 文件描述符(fd)。这是 BoringSSL/OpenSSL 提供的官方 API——SSL 对象内部记录了底层 socket。
#### 第二跳:fd → (ip, port)
script.js:140-160getPortsAndAddresses
这里有两个细节要拆开看。
**Socket.localAddress / Socket.peerAddress**:Frida 内置的 API,本质是包装了 getsockname / getpeername 系统调用。给一个 fd,它返回 { ip: "192.168.x.x", port: 12345 }
异或选址逻辑
读懂这一行要先想清楚抓包视角下"源/目的"的语义:
| 函数 | App 视角 | 抓包视角的 src | 抓包视角的 dst | | :-- | :-- | :-- | :-- | | SSL_write | App 发出数据 | App 本地(localAddress) | 远端服务器(peerAddress) | | SSL_read | App 收到数据 | 远端服务器(peerAddress) | App 本地(localAddress) |
异或 ^ 用作"双开关":
  • src && !isRead(SSL\_write)→ true ^ false = true → 取 local
  • dst && !isRead(SSL\_write)→ false ^ false = false → 取 peer
  • src && isRead(SSL\_read)→ true ^ true = false → 取 peer
  • dst && isRead(SSL\_read)→ false ^ true = true → 取 local
四种组合一行解决。少写两个 if-else,但要慢读一下才能看懂。
网络字节序转换
sockAddr.ip 是字符串如 "192.168.1.100"(IPv6 是 "::ffff:192.168.1.100",所以 split(":").pop() 取最后一段)。ipToNumber 把它转成 32 位整数。然后用 ntohl 做字节序转换——后面 Python 端写 pcap 时要把它再转回网络序,这里先转一道是为了对齐 Python 端 struct.pack(">I", ...) 的展开。
#### 第三跳:SSL\* → session\_id
script.js:168-184getSslSessionId
session\_id 是 TLS 协议层面的会话标识,通常是 32 字节随机数。它的用途不是给 Wireshark 看——pcap 里没这个字段——而是给 Python 端的 ssl_sessions[session_id] 字典做 key,用来追踪同一个会话内的累计字节数(伪造 seq/ack 时要用,见 §4)。
\\为什么不直接用 fd 当 key?\\fd 会被重用——同一个 fd 关闭后可能被新的连接拿去。session\_id 才是连接唯一标识。

2.4 Interceptor 主体

万事俱备,主体 Hook 简洁得不像核心代码(script.js:186-216):
几个关键差异:
SSL\_read 必须在 onLeave 读 buf:因为进入时缓冲区还是空的,要等 SSL 库填好数据再读。所以代码把 args[1] 暂存到 this.buf,retval 告知实际读了多少字节,再用 readByteArray(retval) 取出。
SSL\_write 在 onEnter 直接读:因为进入时 App 已经把明文放进 args[1],长度是 args[2]。这时候读最准——如果在 onLeave 读,可能 SSL 库已经把缓冲区改了。
**retval |= 0:这是两步配合**,别误读成"一行就过滤了负数":
  1. **|= 0 只做类型强转**:把 Frida 的 NativeReturnValue 通过位运算副作用强转成 JS 的 32 位有符号整数(JavaScript 本身没有 int32 类型,但位运算规范要求先 ToInt32)。这一步并不丢弃任何值。
  1. **下一行 if (retval <= 0) return; 才是真正的过滤**:拒掉 SSL_read 失败时的负数错误码(如 -1SSL_ERROR_*)和零字节读。
为什么必须先做第 1 步?因为 NativeReturnValue 直接和 JS 数字做 <= 比较,在某些 Frida 版本上行为不可靠——强转成原生 Number 后比较才稳。
**message["stack"] 从哪里来**:注意这两个全局变量 SSLstackread / SSLstackwrite——它们不是 Native 层填的,而是从 Java 层"传递"过来的。这就是下一章要拆的"Java 层只抓栈不抓数据"的设计。

三、Java 层 Hook · 调用栈从哪里来

读到这里很容易误以为 r0capture 也用 Java 层来抓数据——毕竟脚本里有大量 Java Hook 代码。但仔细看,这些 Hook 的角色完全不同

3.1 SSLSocket Hook:抓栈不抓数据

script.js:308-332
请仔细看这段:
  • Hook 了 writeread
  • 但没有读 bytearry 的内容
  • 仅仅取了当前 Java 调用栈写到全局 SSLstackwrite / SSLstackread
  • 返回值原样透传
这层的唯一职责是:记录"是谁调起了这次 SSL 操作"。下一刻 Native 层 SSL\_write 触发时,从全局变量里把刚刚记下的调用栈带上一起 send 给 Python。
\\为什么不在 Native 层直接取 Java 栈?\\Frida 的 Native Interceptor 在 Thread.backtrace 里能拿到 Native 栈,但 Java 栈需要走 ART 的 JNI 接口才能取,复杂且性能差。让 Java 层先把栈"备好",Native 层只负责"取",是高效的分工。
为什么栈是"前一次"的栈?Java 层 SSLOutputStream.write 调用早于 Native 层 SSL_write(前者会触发后者)。所以 Native 层抓到的 stack 全局变量,时间上确实对应这次 SSL\_write 的调用者。这个时序假设在单线程下成立;多线程并发时会有竞争——但抓包工具对栈的精确度容忍度较高,不是严重问题。
notion image
Java Hook → 全局变量 → Native Hook 的时序原理

3.2 SocketOutputStream / SocketInputStream:明文 HTTP 的兜底

script.js:273-306
这段 Hook 抓的是没走 SSL 的明文流量——FTP、SMTP、明文 HTTP、自定义 TCP 协议。Native 层 SSL\_write 抓不到(因为根本没经过 SSL),所以这里走 Java 兜底。
注意几个差异:
**function = "HTTP_send" / "HTTP_recv"**:在 Python 端 log_pcap 里这两个值的处理和 SSL\_read/write 一样——都是塞进 pcap。但是 ssl_session_id 是空字符串,所以每条 HTTP 流量会被当作一个独立会话。
字节数组转 NativePointer
send(message, data) 的 data 必须是 ArrayBuffer 类型(来自 NativePointer),不能直接传 Java byte\[\]。所以这里手动分配一段 Native 内存,把 Java 字节逐个写进去——开销大,但是 send 二进制附件的唯一方式。
**this.socket.value**:在 Frida Java Bridge 里,访问 Java 对象字段用 .valueSocketOutputStream 有个 socket 字段指向所属 Socket,这里读出来取本地/远程地址。

3.3 Android 版本差异

script.js:308 那个 parseFloat(Java.androidVersion) > 8 是 r0capture 兼容性策略的缩影:
| Android 版本 | Conscrypt 类名 | | :-- | :-- | | ≤ Android 8 | com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream | | ≥ Android 9 | com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream |
不分情况都试一遍是不行的——一个版本上不存在的类 Java.use 直接抛异常。这里用版本号分支保证只 Hook 当前版本存在的类。
\\如果在 Android 14+ 上跑遇到 ClassNotFoundException?\\先看 README 是否声明支持。截至 2026.03 commit,作者声明已测过 Android 7-16。如果未来某个版本类名又变了,这里就是要改的第一个地方。

四、pcap 组装 · 怎么让 Wireshark 信以为真

这一章是 r0capture 另一个核心。Native + Java 层把零散的字节流送到 Python,但 Wireshark 只认 pcap 文件格式——必须把它伪造成"看起来像真 TCP 包"的样子。

4.1 pcap 文件结构

pcap 是个简单的二进制格式:
notion image
pcap 文件二进制布局
每个字段是真的还是假的,下一张图按颜色分了类:
notion image
pcap 文件结构与字段真伪
Global Header(r0capture.py:280-288):
24 字节固定结构。注意最后一个字段 228 = LINKTYPE_IPV4——这是 pcap 设计的关键选择,下面 4.2 详谈。
每个 Packet(r0capture.py:172-198):
40 字节 = IPv4(20) + TCP(20),加上 data 部分构成完整的"伪 TCP 包"。

4.2 LINKTYPE\_IPV4=228 · 跳过以太网层

真实抓包(如 tcpdump)的 pcap 链路层类型通常是 LINKTYPE\_ETHERNET(值=1),每个包前面会带 14 字节以太网头(源 MAC、目的 MAC、类型)。
r0capture 用 LINKTYPE\_IPV4(值=228)——意思是"这个 pcap 里的每个包从 IPv4 头开始,没有底层链路层"。这是个非常机智的选择:
| 链路层类型 | 含义 | r0capture 为什么不用 | | :-- | :-- | :-- | | LINKTYPE\_ETHERNET (1) | 以太网帧 | 我们不知道 App 用了什么 MAC 地址,也不需要伪造 | | LINKTYPE\_RAW (101) | 直接 IPv4/IPv6(区分依靠包内自识别) | 历史遗留,行为有歧义 | | LINKTYPE\_IPV4 (228) | 明确告知是 IPv4 包 | 最干净,Wireshark 直接按 IPv4 解析 |
读者拿 r0capture 的 pcap 用 Wireshark 打开,会发现:
  • 能看到 IP/TCP/HTTP 层
  • 看不到 Ethernet 层
  • 包都是 PSH+ACK 标志,没有三次握手、没有四次挥手
  • TCP 校验和全是 0,Wireshark 会标红警告
这些都是"伪 pcap"的特征。但因为应用层数据是真的,HTTP 头/JSON body 在 Wireshark 里能正常解析展开——这正是 r0capture 的目的。

4.3 伪造 seq/ack · ssl\_sessions 当虚拟 TCP 状态机

真实 TCP 的 seq/ack 是双方动态协商的。r0capture 既然在伪造,就需要自己模拟一份。r0capture.py:128 全局字典:
每会话维护两个累加器:client 已发字节数 / server 已发字节数。每次有新数据进来:
每个会话初次出现时分配一对随机初始序列号(mimic 真实 TCP 三次握手时的 ISN)。之后:
  • SSL\_write(客户端发):seq = client\_sent,ack = server\_sent,发完后 client\_sent += len(data)
  • SSL\_read(服务端发):seq = server\_sent,ack = client\_sent,发完后 server\_sent += len(data)
r0capture.py:201-205
这样在 Wireshark 看 TCP 流的时候,seq/ack 是连贯的,可以"Follow TCP Stream"完整拼出整个会话——尽管它从未真正在线路上传输过这样的 TCP 包。

4.4 TCP flags 0x5018 = PSH + ACK

这是个高 8 位 + 低 8 位组合字段:
notion image
0x5018 的 16 bit 拆解
PSH+ACK 是 TCP 数据传输的常规组合——表示"这是一段数据,请立刻交给应用层"。SYN/FIN 都没置位,所以 Wireshark 不会期待握手挥手。Header Length=5 表示 TCP 头是 20 字节(无 options)。

4.5 时间戳

t = time.time() 取本地当前时间。注意微秒部分的取法——直接 (t * 1000000) % 1000000 是取小数部分的微秒,等价于 int((t - int(t)) * 1000000) 但更紧凑。

五、彩蛋功能 · 大部分教程没提的两个

r0capture 不止抓包,还顺手做了两件事——很多介绍它的文章都漏掉了。

5.1 客户端证书自动导出

服务器对客户端做双向 TLS 认证时(金融、政务、企业 App 常见),客户端要持有一份 p12 证书。这个证书内嵌在 APK 里,密码混淆在代码中。r0capture 自动把它 dump 出来。
script.js:235-256
storeP12 函数(script.js:220-233):
逻辑:在 App 调用 getPrivateKey 取出私钥的瞬间,把"私钥 + 证书"组装成一个新的 PKCS12 容器,密码硬编码 r0ysue,落到 /sdcard/Download/<package><uuid>.p12
运行前必须给 App 加上存储卡读写权限——README 特别强调。否则 FileOutputStream 失败,证书 dump 不下来。
\\为什么也 Hook getCertificateChain?\\有些 App 不调 getPrivateKey,而是调 getCertificateChain。两个都 Hook 提高命中率。
拿到 p12 后,用 keystore-explorer 打开,密码 r0ysue,就能看到完整证书。
这个功能和第08篇双向 TLS 那一节形成强联动——以前要靠 frida-tools 抓 KeyStore 各种方法,现在 r0capture 一行命令搞定。

5.2 SSL Pinning 代码定位器

抓到了流量只是第一步。如果你想自己改 App 代码绕过 Pinning(而不是依赖运行时 Hook),需要知道"App 里哪一行做了 Pinning"。r0capture 提供一个轻量级定位器(script.js:259-270):
逻辑非常聪明:
  • Hook File 构造函数(任何 File 创建都会触发)
  • 但只在两个条件同时满足时上报:① 路径里含 cacert(很可能是证书文件);② 调用栈里出现 X509TrustManagerExtensions.checkServerTrusted(这是 Android 标准证书验证 API)
  • 两条匹配上 = 这个 File 创建是 Pinning 流程的一部分
上报的 message 包含 File 路径 + 完整调用栈——定位 Pinning 代码位置一步到位。
局限:这只覆盖了"标准方式做 Pinning"的 App。如果 App 用了 OkHttp 的 CertificatePinner 或者自写的证书校验,这个定位器抓不到。但它对最常见的"通过 Conscrypt + X509TrustManagerExtensions 做 Pinning"的 App 非常好用。

六、动手实战 · 从源码到运行

6.1 安装与第一次运行

**关于 nohup + & 的写法**:旧版教程常写成 su -c '... &',但部分 su 实现(特别是 Magisk 的某些版本)在 adb shell 退出时会一并杀掉 & 起的进程。nohup 把它从 adb shell 的进程组解耦,配合 stdout/stderr 重定向到 /dev/null 后就能稳定 detach。如果你用 KernelSU 或较新 Magisk,可以直接 adb shell su -c "/data/local/tmp/frida-server &" 也能工作——但 nohup 写法是兼容性最广的。

6.2 在 Wireshark 里看什么

打开 out.pcap
  • 能看到:每个 TCP 包的源/目的 IP/端口、应用层数据(HTTP 头、JSON body、Protobuf 二进制)、用"Follow TCP Stream"看完整会话
  • 看到红色警告也别慌:TCP 校验和都是 0,Wireshark 会标红——因为是伪造的 pcap,这是正常的
  • 没有三次握手 / 没有 TLS 握手:每个会话直接从 PSH+ACK 数据开始,没有 SYN / ServerHello 等握手包
  • 没有以太网层:因为用了 LINKTYPE\_IPV4
实战推荐过滤器:

6.3 改一处代码 · 给 r0capture 加 JSON 美化输出

读懂源码后最直接的练手就是改它。来加一个功能:当 SSL_read 返回的数据看起来是 JSON 时,控制台输出格式化版本。
定位修改点:r0capture.py:226-242on_message 回调里的 verbose 分支。在 hexdump 之前插入 JSON 检测:
跑一次目标 App:JSON 响应直接以 indent=2 的方式打印,二进制数据回退到 hexdump。
这个改动只用了 ~15 行代码——这正是 r0capture 体积小的红利:每一处都看得懂,改起来心里有数。

七、r0capture 不覆盖时怎么办

README 自陈了它的局限:
部分开发实力过强的大厂或框架,采用的是自身的 SSL 框架,比如 WebView、小程序或 Flutter,这部分目前暂未支持。暂不支持 HTTP/2、或 HTTP/3,该部分 API 在安卓系统上暂未普及或部署,为 App 自带,无法进行通用 hook。各种模拟器架构、实现、环境较为复杂,建议珍爱生命、使用真机。暂未添加多进程支持。
针对这些缺口,下面是按场景补强的方案。

7.1 WebSocket 业务消息层

r0capture 能抓到 WebSocket 握手时的 HTTP Upgrade 请求,但 WebSocket 帧解析它不做。要解析业务消息,Hook OkHttp 的 WebSocket 抽象层:
**注意 WebSocketListener 是抽象类**:实际 App 用的是其子类(往往是匿名内部类)。直接 Hook 抽象方法在不同 OkHttp 版本上命中率不一。如果抓不到,改用 Java.enumerateMethods("*WebSocketListener*!onMessage") 枚举所有具体子类再 Hook。
加载方式:

7.2 自定义 TCP / 私有协议

游戏类 App 常用纯 TCP 自定义协议。这部分 r0capture 通过 SocketOutputStream.socketWrite0 Hook 已经能抓到——它会出现在 pcap 的 HTTP\_send / HTTP\_recv 事件里。但二进制协议结构需要自己解析。
如果想加上协议格式化输出,类似 §6.3 那样在 on_message 里加个 type 分支:

7.3 Flutter / RN / Unity

这是 r0capture 明确不支持的场景。原因:
  • Flutter:用自己内置的 BoringSSL(编译进 libflutter.so),不调系统 libssl
  • React Native:JS 层走 fetch,最终落到 OkHttp/NSURLSession——通常 r0capture 能抓到,除非 用了 expo-modules-core 等自带网络栈
  • Unity:调用 UnityWebRequest,底层走 libil2cpp 调 mono 网络栈
针对这些场景,方案是在它们各自的 SSL 库上重新做 SSL\_read/write Hook:
完整方案见第二十九篇《跨平台框架逆向 · Flutter、RN、Unity》。

7.4 HTTP/2 / HTTP/3 / QUIC

HTTP/2 二进制帧、HTTP/3 QUIC(UDP)r0capture 都抓不到——前者因为 r0capture 不识别 HTTP/2 帧结构,后者因为整个走 UDP 协议绕过了 TCP-based 的 SSL\_read/write。
应对:
  • HTTP/2:r0capture 的 pcap 里会看到加密的 HTTP/2 流量(被 OkHttp 走 HTTP/2 时其实仍经过 SSL\_write),但 Wireshark 解码需要 HTTP/2 解码插件,且当前 r0capture 把同一会话所有数据并到一个 TCP 流里,多路复用的流不容易区分。临时方案:让 OkHttp 降级到 HTTP/1.1(Hook OkHttpClient.Builder.protocols(List<Protocol>),传入只含 HTTP_1_1 的列表)
  • QUIC:极少 App 在 Android 上用 QUIC。如果遇到,要专门 Hook Cronet 的 quiche 实现,属于另一个话题

7.5 反 Frida App · 先绕过检测再 attach

有反 Frida 检测的 App 不会让 r0capture 顺利 attach——进程刚启动就退出,或者 attach 后立刻杀掉自己。这种情况下:
  1. 用第17篇《Frida 检测原理与对抗》里的脚本先注入反检测脚本
  1. attach r0capture 时改用非标准 frida-server 端口(README 提到的 -H 参数):
绕开了 App 对默认 27042 端口的检测。

7.6 多进程子进程

很多 App 用 :push :web 等子进程做独立任务,r0capture 默认只 attach 主进程。要追子进程:
  • 方案 A:用 frida 的 Child-gating 机制,需要修改 r0capture.py 增加 device.on('child-added', ...) 回调
  • 方案 B(粗暴):先用 frida-ps -U | grep <pkg> 找出所有相关 PID,分别开多个 r0capture 实例 attach
支持多进程后还要解决 pcap 写入锁——多个进程同时写一个 pcap 文件会乱。用 frida-tools 的 Reactor 单线程串行化是 README 给出的官方建议。

总结

r0capture 是一个 28 KB 的开源项目,但它把 Native Hook、Java Hook、Socket 五元组还原、pcap 文件伪造这四件事协同得很优雅:
  • **Native 层 Hook SSL_read/SSL_write**:拿到加密前后的明文,这是数据的唯一来源
  • Java 层 Hook SSLSocket:只抓调用栈,不抓数据,由 Native 层取栈使用——清晰的分工
  • Java 层 Hook SocketOutputStream:兜底明文协议(FTP/SMTP/明文 HTTP)
  • 从 SSL 对象一路还原到 (ip, port):SSL → fd → Socket.localAddress/peerAddress
  • pcap 伪造:用 LINKTYPE_IPV4 跳过以太网层,用 ssl_sessions 维护虚拟 TCP 状态机伪造 seq/ack
它还做了两件被很多教程忽略的事:客户端证书自动 dump、SSL Pinning 代码定位器。
它的局限也写在 README 里:不支持 Flutter / WebView / HTTP2 / HTTP3 / 多进程。这些缺口在 §7 里都给了应对方案。
读懂这个项目的最大收获不是"学会一个工具"——而是看到一个真实的开源项目怎么把一个棘手问题拆成几个能独立工作的小部件,再用 send() 跨语言通讯把它们粘起来。后面几篇你会看到类似的设计模式——这种"协同设计"的鉴赏力,比记住几条 API 重要得多。
上一篇
libmsaoaidsec.so 检测监测——绕过 Hook 脚本
下一篇
Frida学习笔记(八):SSL Pinning 绕过全攻略

Comments
Loading...