阅读提示:本文面向有一定 Android 开发基础或安全研究背景的读者,采用"原理 + 实操 + 攻防对抗"的三层结构撰写。建议按章节顺序阅读;如已熟悉基础知识,可直接跳至第六章开始阅读攻防部分。
本文系统梳理了 Android 应用抓包的完整知识体系,覆盖从入门到内核级对抗的全链路。
你能从本文学到什么:
"抓包"对于移动端开发者和安全研究者而言,是一项基础但极其强大的技术手段。理解 App 与服务端之间的通信,几乎是所有逆向分析工作的起点。具体来说,抓包能解决这些问题:
抓包从来不是"一劳永逸"的事,随着整个行业安全意识的提升,抓包这件事变得越来越难。
第一阶段(HTTP 明文时代):早期 App 普遍使用 HTTP 协议传输数据,数据包明文可见,任何网络设备都能直接读取。这个时代,抓包几乎毫无障碍——只需把手机接入同一局域网,Wireshark 一开就什么都看到了。
第二阶段(HTTPS 普及):HTTPS 在移动端全面铺开后,中间人(MITM)代理模式应运而生。代理工具通过重签证书实现"合法"的流量拦截,前提是客户端信任代理的 CA 根证书。这个时代,抓包变成了"安装一个证书就能解决"的问题。
第三阶段(Android 7.0 系统收紧):2016 年 Android 7.0 的发布是一道明显的分水岭。从这个版本开始,Google 修改了系统的网络安全配置(network_security_config),应用默认不再信任用户安装的 CA 证书,只有系统证书目录下的证书才有效。从此,单纯在手机里安装代理证书已无法奏效,必须想办法将证书提权到系统级别。
第四阶段(App 自我保护觉醒):面对日益严峻的逆向分析威胁,各大厂商开始在应用内构建多层防御体系:
这四重防御叠加在一起,让传统的代理抓包方式几乎完全失效。
本文提供一套完整的排查与对抗路径,从最简单的代理配置,到 Frida Hook,再到内核级 eBPF 方案,覆盖从 Android 4 到 Android 16 的所有版本,应对市面上所有主流的反抓包防御手段。
本节是理解后续所有对抗手段"为什么有效"的理论基础。如果你已经熟悉网络协议基础,可以快速浏览后直接进入第三章。
数据包的本质 是网络通信中的信息单元,由两部分组成:Header(头部) 和 Payload(数据载荷)。头部包含路由、控制信息(如 IP 地址、端口号、协议类型),载荷则是真正要传输的业务数据。抓包工具本质上就是在特定节点捕获这些数据单元,并对其进行解码还原。
OSI 七层模型与 TCP/IP 四层模型的对应关系与抓包工具的关联:
代理工具(Charles/Reqable/Burp)工作在 L7 应用层,它们"理解"HTTP 协议的语义,能够解析、展示、修改请求/响应内容,但它们无法处理不经过代理的流量(如 App 直接通过 socket 绕过代理)。Wireshark/tcpdump 工作在更底层,可以捕获所有流量,但无法自动解密 TLS 内容。
常见协议速览:

端口号在 HTTPS 下默认为 443,HTTP 下默认为 80,省略时使用默认值。
POST /api/v2/login HTTP/1.1 ← 请求行(方法 + URL + 协议版本)
Host: api.example.com ← 请求头开始
Content-Type: application/json
Authorization: Bearer eyJhbGci...
User-Agent: MyApp/2.0 (Android 12)
← 空行:头部与正文的分隔符
{"username":"user","password":"xxx"} ← 请求体(Body)
Host | ||
Content-Type | ||
Authorization | ||
User-Agent | ||
Cookie | ||
X-SignX-Token |
HTTP vs HTTPS 的本质差异:
HTTP 是明文协议,数据在传输过程中完全可读;HTTPS 则在 HTTP 之上叠加了三重保障:
HTTPS = HTTP + 通信加密 + 服务端身份认证 + 数据完整性保护
TLS(Transport Layer Security,传输层安全协议)是 HTTPS 的核心,负责以上三重保障的实现。
TLS 握手简化流程:

握手完成后,双方使用协商好的对称密钥(如 AES-256)加密所有后续通信。中间人代理正是在握手阶段介入,用自己的证书替换服务端证书,从而让客户端与代理之间建立一个 TLS 连接,代理再与真实服务端建立另一个 TLS 连接,实现对双向流量的解密。
数字证书(X.509 标准)是公钥基础设施(PKI)的核心组件,本质上是一份由权威机构(CA)签名的"身份证明文件",内容包括:
证书格式速查表:
.pem | ||
.crt.cer | ||
.p12.pfx | ||
.bks | ||
.der | ||
.csr |
系统证书目录 vs 用户证书目录的区别:

代理工具实现 HTTPS 解密的完整流程如下图所示:

关键机制:
这就是为什么只要 App 信任代理的 CA 证书,HTTPS 流量就可以被解密——证书体系的信任传递机制被利用了。
不同的对抗场景需要不同层次的工具。工欲善其事,必先利其器——在投入大量时间排查之前,选对工具能节省 80% 的时间。

这是日常使用频率最高的一类工具,工作在 HTTP/HTTPS 协议层,能够解析并展示请求/响应的完整内容。
Reqable 是近年来国内影响力最大的抓包工具,由国内团队维护,具备完整的中文界面,桌面端和移动端均有良好支持,集代理抓包与 API 调试功能于一体。

核心优势:

老牌抓包工具,经典且稳定,功能全面:

渗透测试领域的标准工具,功能远超普通抓包:

完全开源免费的跨平台方案(Flutter 开发),支持 iOS/Android/macOS/Windows/Linux 全平台。对 Flutter App 的抓包支持友好(基于 VPN 模式),适合开源环境或对 Charles 授权有顾虑的场景。
HTTP Toolkit 是一款开源的现代化 HTTP/HTTPS 调试工具,其最大亮点是一键式 Android 接入:通过 ADB 自动完成证书安装和代理配置,无需手动操作手机,极大降低上手门槛。

核心优势:
典型使用场景:
# 1. PC 端启动 HTTP Toolkit,选择 "Android device via ADB"
# 2. 工具自动执行以下操作(无需手动):
# - adb push httptoolkit-ca.pem /data/local/tmp/
# - 安装伴侣 APK(配置系统代理 + 证书信任)
# 3. 手机端流量自动路由到 HTTP Toolkit,开始抓包
局限性:与其他代理工具一样,若 App 部署了 SSL Pinning 或代理检测,仍需配合 Frida 等方案绕过;对 Flutter、QUIC 等非标准 TLS 栈的支持也受限。
当 App 通过代码检测到系统代理并拒绝请求时,VPN 模式抓包就派上了用场。
核心原理:代理工具工作在 OSI L7 层(应用层),通过系统代理配置将流量转发;而 VPN 工作在 L2/L3 层(数据链路/网络层),通过虚拟网卡(tun0)在操作系统内核层面拦截所有流量。App 在 Java 层检测到的代理设置(http.proxyHost)为空,因此代理检测失效。
但需注意:部分 App 同时进行 VPN 检测(检测 tun0 网卡是否存在),这一点在第七章会详细介绍绕过方法。
当应用层代理无法捕获流量时(例如 App 使用了私有协议、QUIC、WebSocket 等),需要下沉到网络层进行抓包。

业界最权威的图形化网络分析工具,能够捕获和分析所有协议的数据包:
# 在手机上 tcpdump 抓包,通过 adb 拉取到 PC 后用 Wireshark 分析
adb shell tcpdump -i any -w /data/local/tmp/capture.pcap
adb pull /data/local/tmp/capture.pcap ./capture.pcap
# 然后双击 capture.pcap 用 Wireshark 打开
命令行抓包工具,直接运行在 Android 设备上:
# ptcpdump 按进程名过滤
ptcpdump -i any --process-name com.example.app -w output.pcap
当以上所有方法都遭遇阻碍时,这两款工具提供了绕过一切证书校验机制的终极能力。
由 r0ysue 大佬开发,基于 Frida 框架,直接 Hook OpenSSL/BoringSSL 的 SSL_write 和 SSL_read 函数,在 TLS 加密之前(发送侧)和解密之后(接收侧)截获明文数据流,以 PCAP 格式输出:
# 使用方式
python r0capture.py -U <package_name> -v -p output.pcap
# 然后用 Wireshark 打开 output.pcap 分析
能力边界:
最前沿的内核级 TLS 明文捕获工具,基于 Linux eBPF(Extended Berkeley Packet Filter)技术,通过 uprobe 探针在内核态直接钩住加密库的读写函数:
# 推送到设备并运行
adb push ecapture /data/local/tmp/ && adb shell chmod 777 /data/local/tmp/ecapture
adb shell /data/local/tmp/ecapture tls -p <pid> -m text
核心能力:eCapture 提供按功能组织的八个不同捕获模块:
tls | SSL_readSSL_write, SSL_do_handshake | ||
gotls | crypto/tls.(*Conn).Writecrypto/tls.(*Conn).Read | ||
gnutls | gnutls_record_recvgnutls_record_send | ||
nss | PR_WritePR_Read, PR_Send, PR_Recv | ||
bash | readline | ||
zsh | zle_line_finish | ||
mysqld | dispatch_command | ||
postgres | exec_simple_query |
每个模块独立运行,可以通过 cli/cmd/root.go 中定义的 CLI 命令结构启用。
核心优势:完全不修改目标 App,没有 Frida 运行时特征,极难被检测到。
设备要求:Android 13+ 或内核版本 5.10 以上。
这是实战的第一关,也是很多人卡住的地方。搞清楚不同版本的证书信任策略,能解决"为什么我安装了证书还是抓不到包"这个最常见的问题。

在 Android 7.0 之前,系统默认信任用户安装的所有 CA 证书,抓包只需三步:
实战 SOP(Android 6 及以下):
http://charlesproxy.com/getssl),下载并安装 CA 证书;从 Android 7.0 开始,Google 引入了网络安全配置(Network Security Config)机制。应用层的默认配置变更为:不信任用户安装的 CA 证书。
具体表现在 App 的 AndroidManifest.xml 中引用的 res/xml/network_security_config.xml 文件,默认配置等效于:
<!-- Android 7+ 默认行为,应用不信任用户证书 -->
<network-security-config>
<base-configcleartextTrafficPermitted="false">
<trust-anchors>
<!-- 只信任系统证书目录,不信任用户目录 -->
<certificatessrc="system"/>
</trust-anchors>
</base-config>
</network-security-config>
这意味着即使你在手机系统设置里安装了代理的 CA 证书(安装到用户证书目录),App 发出的 HTTPS 请求依然会因"证书不受信任"而失败。
针对这个问题,有三种解决方案:
原理:解包 APK,修改网络安全配置文件,使 App 信任用户证书,重新签名打包。
操作步骤:
# 1. 使用 apktool 解包
apktool d target.apk -o target_decompiled
# 2. 查看 AndroidManifest.xml 中是否已有 networkSecurityConfig 属性
# <application android:networkSecurityConfig="@xml/network_security_config" ...>
# 如无,在 application 标签中添加该属性
# 3. 修改或创建 res/xml/network_security_config.xml
<!-- res/xml/network_security_config.xml:添加用户证书信任 -->
<network-security-config>
<base-configcleartextTrafficPermitted="true">
<trust-anchors>
<certificatessrc="system"/>
<!-- 添加这一行,信任用户安装的证书 -->
<certificatessrc="user"/>
</trust-anchors>
</base-config>
</network-security-config>
# 4. 重新打包并签名(需要 Java 环境)
apktool b target_decompiled -o target_patched.apk
# 生成调试签名
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey \
-keyalg RSA -keysize 2048 -validity 10000
# 签名 APK
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \
-keystore debug.keystore target_patched.apk androiddebugkey
注意事项:
原理:获取 Root 权限后,将代理 CA 证书以系统证书格式推送到系统证书目录,让所有 App 均信任。
操作步骤:
# 1. 将代理的 CA 证书(PEM 格式)保存到本地,假设文件名为 ca.crt
# 2. 计算 OpenSSL Subject Hash(旧式,Android 使用此格式)
openssl x509 -inform PEM -subject_hash_old -in ca.crt | head -1
# 输出示例:a1b2c3d4
# 3. 将证书重命名为 <hash>.0 格式
cp ca.crt a1b2c3d4.0
# 4. 推送到系统证书目录(需要 Root)
adb root
adb remount # 重新挂载为可写
adb push a1b2c3d4.0 /system/etc/security/cacerts/
adb shell chmod 644 /system/etc/security/cacerts/a1b2c3d4.0
adb reboot # 重启生效
注意事项:
adb remount 可能无法执行;MoveCertificate 模块是目前最优雅的解决方案:它会在系统启动时,自动将用户证书目录下的证书"挂载"到系统证书目录,无需修改实际的系统分区文件,完全安全可回滚。
安装步骤:
MoveCertificate 模块;Android 14 引入了一个更为复杂的机制:系统证书目录由 APEX(Android Pony EXpress)模块动态挂载管理,而不再是一个静态的文件目录。这使得传统的 adb push 和简单 Magisk 模块推送方式都失效了。
根本原因:APEX 在系统启动阶段会将证书目录挂载到特定位置,覆盖掉之前对 /system/etc/security/cacerts/ 目录的任何修改。
解决方案:需要在 APEX 挂载之后、Zygote 启动之前这个窗口期,强制将证书注入进去,以下两种方案可以尝试:
方案A(推荐):MoveCertificate 模块
MoveCertificate 是一个专为此问题设计的 Magisk / KernelSU / APatch 模块,支持 Android 7–16,原理是在 post-fs-data 阶段将用户证书目录 mount --bind 覆盖到 APEX 的系统证书路径,一次安装永久生效。
安装步骤:与上文一致。
方案B:手动内置 / remount(自编译 ROM)
如果使用的是自行编译的 AOSP 镜像,可以直接将证书内置到源码或在启动后 remount system 手动复制:
# remount system 为可写后直接推送(需要 userdebug/eng 构建)
adb root && adb remount
adb push charles-proxy-ssl-proxying-certificate.pem \
/system/etc/security/cacerts/<hash>.0
adb shell chmod 644 /system/etc/security/cacerts/<hash>.0
adb reboot
Tips:如果以上方案都嫌麻烦,可以直接跳过证书信任问题,使用第八章介绍的 r0capture 在 SSL 底层捕获明文,彻底绕开系统证书校验链路。
本章用 Reqable 作为演示工具,完整演示一次标准的 HTTPS 抓包流程。Reqable 对国内用户最友好,但流程对 Charles、Burp 同样适用。
第一步:安装 PC 端 Reqable
从 reqable.com 下载适合你操作系统的版本(macOS/Windows/Linux),安装后首次启动会引导你生成 CA 根证书。证书会被保存在 ~/.reqable/ 目录下。
第二步:导出并安装证书到手机
不同的桌面端平台(这里主要是Windows/MacOS/Linux),证书安装方式有所不同,为了简化安装过程,Reqable提供了一键安装证书的功能。证书的安装入口位于顶部操作栏,点击盾牌图标打开弹窗。

安装安卓证书:

Android证书分为两种:用户证书和系统证书。用户目录不需要权限即可添加和删除CA证书,系统目录需要Root权限才可以添加和删除CA证书。
Reqable要求用户事先已经在电脑上安装了ADB工具,Reqable会使用ADB工具检查连接到电脑的Android设备的证书安装状态,包括系统证书安装状态和用户证书安装状态。

只有Root的设备Reqable才可以一键安装证书(支持Android 5.0 - 15系统)。
如果已经安装了系统证书,可以跳过用户证书的安装。如果系统证书安装失败,则需用户手动安装用户证书,在手机端打开设置进行如下步骤的操作:
手机设置 → 安全 → 更多安全设置 → 加密与凭据 → 安装证书 → CA 证书
注意:不同 Android 版本和手机品牌的菜单路径略有差异,关键词是"安装证书"或"信任凭据"。另外Reqable无法检测到非Root设备的用户证书安装状态,会一直显示
未知证书安装状态。
第三步:配合 Magisk 模块提升为系统证书(Android 7+ 必须)
如第四章所述,安装 MoveCertificate 模块并重启,使用户证书在系统级别生效。
获取 PC 的局域网 IP:
# macOS/Linux
ifconfig | grep "inet " | grep -v 127.0.0.1
# Windows
ipconfig | findstr "IPv4"
手机 WiFi 代理设置:
手机设置 → WiFi → 长按已连接的 WiFi → 修改网络 → 代理 → 手动
主机名:<PC 的局域网 IP,如 192.168.1.100>
端口:9000(Reqable 默认端口)
验证连通性:在手机浏览器访问 http://reqable.com/install,如果可以正常加载且 HTTPS 请求能被拦截,说明配置成功。
在 Reqable 中点击任意一条已捕获的请求,可以看到完整的请求/响应详情:
请求结构解析:

Body 格式识别:
Content-Type | ||
|---|---|---|
application/json | ||
application/x-www-form-urlencoded | ||
multipart/form-data | ||
application/protobuf | ||
application/octet-stream |
当遇到 Protobuf 格式的数据时,Reqable 和 Burp 都支持内置或插件方式的 Protobuf 解码,但需要先找到对应的 .proto 文件(通常在 APK 中可以找到)。
设置断点后,当匹配的请求/响应触发时,工具会暂停并允许你手动修改数据后再继续:
Reqable → 右键请求 → 断点 → 选择"在响应返回时暂停"
# 再次触发该请求,Reqable 会弹出编辑界面
# 修改响应 JSON 中的 vip 字段为 true,点击继续
这是不修改 APK 实现"破解"某些功能的常见手段。
自动化版本的断点,基于规则匹配对请求/响应进行固定改写:
Reqable → 重写规则 → 添加规则
匹配:URL 包含 /api/user/vip
操作:响应 Body 替换(将 "vip":false 替换为 "vip":true)
模拟不同网络环境,验证 App 的网络容错能力:
当你发现某个 App 即使配置了代理和证书也无法抓包时,说明 App 部署了主动防御机制。本章深度剖析四大防御体系的实现原理,为第七章的对抗手段打下基础。
理解防御侧的实现,才能找到对应的突破口。这里展示的都是真实存在于生产 App 中的防御代码。

Java 层的代理检测非常简单直接——读取系统属性,如果发现代理配置就拒绝请求:
// 检测方式一:读取系统代理属性
String proxyHost = System.getProperty("http.proxyHost");
String proxyPort = System.getProperty("http.proxyPort");
if (proxyHost != null && !proxyHost.isEmpty()) {
// 检测到代理,记录日志并拒绝本次网络请求
Log.w("Security", "Proxy detected: " + proxyHost + ":" + proxyPort);
thrownew SecurityException("Proxy not allowed");
}
// 检测方式二:构造请求时强制不走代理
OkHttpClient client = new OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY) // 强制绕过系统代理
.build();
第二种方式更为彻底:即使手机设置了代理,OkHttpClient 也会直接连接目标服务器,完全绕过代理工具。这种情况下,代理工具根本收不到任何请求。
// Kotlin 写法
val proxyHost = System.getProperty("http.proxyHost")
val httpsProxyHost = System.getProperty("https.proxyHost")
if (!proxyHost.isNullOrEmpty() || !httpsProxyHost.isNullOrEmpty()) {
// 发送安全告警,中断请求
reportSecurityEvent("proxy_detected")
}
VPN 检测通过枚举网络接口来识别 VPN 的存在:
// 方式一:检查网卡名称
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
String name = ni.getName().toLowerCase();
// tun0/ppp0/p2p0 是常见的 VPN 虚拟网卡名
if (name.startsWith("tun") || name.startsWith("ppp") || name.startsWith("p2p")) {
Log.w("Security", "VPN interface detected: " + name);
returntrue; // VPN 已激活
}
}
// 方式二:通过 NetworkCapabilities 检测(Android 5+)
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
Network activeNetwork = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(activeNetwork);
if (caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
// 传输类型包含 VPN(能力值 4)
returntrue;
}
SSL Pinning 是目前最常见、最有效的反抓包手段之一。其核心思想是:将服务端证书的公钥哈希(或完整证书)硬编码进 App,在 TLS 握手完成后额外验证一次,确保与己方预期的服务端完全一致。
即使攻击者的代理 CA 证书被系统信任,SSL Pinning 也会因"公钥不匹配"而拒绝连接。

这是主流 Android App(使用 OkHttp 框架)最常见的实现方式:
// 预先将服务端证书的公钥 Base64 哈希硬编码进代码
// 格式:sha256/<Base64 编码的公钥 SHA-256 哈希>
CertificatePinner pinner = new CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // 备用
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(pinner)
.build();
获取服务端证书 Hash 的命令:
# 用 openssl 提取服务端证书的公钥 Base64 SHA256 哈希
openssl s_client -connect api.example.com:443 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64
更底层的实现,直接在 X509TrustManager 的 checkServerTrusted 方法中手动比对:
publicclassPinnedTrustManagerimplementsX509TrustManager{
// 从 res/raw 中预置的证书提取的公钥
privatestaticfinal String EXPECTED_PUBLIC_KEY =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...";
@Override
publicvoidcheckServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// 提取服务端证书的公钥,转换为 Base64
String serverPublicKey = Base64.encodeToString(
chain[0].getPublicKey().getEncoded(), Base64.DEFAULT);
// 与预置值比较,不匹配则抛出异常中断连接
if (!EXPECTED_PUBLIC_KEY.equals(serverPublicKey.trim())) {
thrownew CertificateException("Certificate pinning failure: public key mismatch");
}
}
// ...
}
通过限制可信域名来防止 MITM:
client.setHostnameVerifier(new HostnameVerifier() {
@Override
publicbooleanverify(String hostname, SSLSession session){
// 只允许指定的域名通过
return"api.example.com".equals(hostname) ||
"cdn.example.com".equals(hostname);
}
});
普通 TLS 只验证服务端的证书;mTLS(双向 TLS)要求双方互相验证证书——服务端也要验证客户端证书的合法性。
抓包工具因为没有合法的客户端证书,连接在 TLS 握手阶段就被服务端拒绝,根本无法看到任何内容。

// 加载内置的客户端证书(存放在 assets/ 或 res/raw/ 中)
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream p12Stream = getAssets().open("client_cert.p12");
// 密码通常硬编码在代码中,jadx 搜索即可找到
keyStore.load(p12Stream, "certificate_password".toCharArray());
// 创建 KeyManagerFactory
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "certificate_password".toCharArray());
// 构建支持 mTLS 的 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0])
.build();
.p12.pfx | ||
.cer.crt | ||
.bks |
本章是全文的核心。针对第六章介绍的每一种防御机制,提供完整的绕过方案,并附带可直接使用的 Frida Hook 脚本。
前提准备:Frida 相关方案需要设备安装并运行 frida-server(需 Root)。
使用 Reqable 移动端或 HttpCanary 的 VPN 模式,完全绕过代理检测:
HttpCanary → 启动抓包 → App 检测不到 http.proxyHost
Reqable 移动端 → 连接 → 选择 VPN 模式
原理:VPN 在内核层面拦截流量,Java 层的 System.getProperty("http.proxyHost") 返回 null,代理检测逻辑失效。
在路由器或 Linux 主机上通过 iptables 强制转发流量,无需在手机端配置任何代理:
# 在运行 Clash 等代理工具的主机/路由器上执行
# 将所有来自手机 IP 的 HTTP/HTTPS 流量强制转发到代理端口
iptables -t nat -A PREROUTING -s 192.168.1.100 -p tcp --dport 80 \
-j REDIRECT --to-ports 7890
iptables -t nat -A PREROUTING -s 192.168.1.100 -p tcp --dport 443 \
-j REDIRECT --to-ports 7890
# Clash 透明代理(tproxy 模式)配置示例
# tproxy-port: 7893
# mode: tproxy
这种方案下,手机端的代理设置为空,App 完全感知不到代理的存在。
当 App 的代理检测代码在 Java 层,可以直接 Hook System.getProperty() 方法,让代理相关的属性查询返回 null:
// frida hook: 隐藏代理配置
Java.perform(function() {
var System = Java.use("java.lang.System");
var getPropertyOverload = System.getProperty.overload("java.lang.String");
getPropertyOverload.implementation = function(key) {
// 拦截代理相关的属性查询
if (key.indexOf("http.proxyHost") >= 0 ||
key.indexOf("http.proxyPort") >= 0 ||
key.indexOf("https.proxyHost") >= 0 ||
key.indexOf("https.proxyPort") >= 0) {
console.log("[+] Hiding proxy property: " + key);
returnnull; // 返回 null,欺骗代理检测逻辑
}
// 其他属性正常返回
returnthis.getProperty(key);
};
console.log("[+] Proxy detection bypass activated");
});
# 运行方式
frida -U -n <process_name> -l bypass_proxy.js
# 或在 spawn 模式下注入(App 启动前注入,避免初始化时的检测)
frida -U -f <package_name> -l bypass_proxy.js --no-pause
// frida hook: 隐藏 VPN 网卡特征
Java.perform(function() {
var NetworkInterface = Java.use("java.net.NetworkInterface");
NetworkInterface.getName.implementation = function() {
var name = this.getName();
// 将 VPN 虚拟网卡名替换为普通数据网卡名
if (name === "tun0" || name === "tun1" ||
name === "ppp0" || name === "p2p0") {
console.log("[+] Hiding VPN interface: " + name + " -> rmnet_data0");
return"rmnet_data0"; // 伪装成普通数据网卡
}
return name;
};
console.log("[+] VPN interface detection bypass activated");
});
// frida hook: 屏蔽 TRANSPORT_VPN 能力标志
Java.perform(function() {
var NetworkCapabilities = Java.use("android.net.NetworkCapabilities");
NetworkCapabilities.hasTransport.implementation = function(transportType) {
// TRANSPORT_VPN = 4,拦截对 VPN 传输类型的检测
if (transportType === 4) {
console.log("[+] Hiding TRANSPORT_VPN capability");
returnfalse; // 告诉 App:没有 VPN 传输层
}
returnthis.hasTransport(transportType);
};
});
如 7.1 中的透明代理方案,完全不在手机端创建 VPN 虚拟网卡,因此 VPN 检测也失效。这是最彻底的方案,但需要额外的网络基础设施(路由器或中间主机)。
JustTrustMe 是一个 Xposed 框架模块,安装后自动 Hook 所有主流 HTTPS 框架的证书验证逻辑,无需针对每个 App 单独分析:
HttpsURLConnection、OkHttp 1/3/4、Retrofit、Volley、WebViewClient、Apache HttpClient 等;类似模块还有 SSLUnpinning,原理相同。
// frida hook: 绕过 OkHttp CertificatePinner 的指纹校验
Java.perform(function() {
try {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
// check 方法有多个重载,需要分别处理
CertificatePinner.check.overload(
"java.lang.String", "java.util.List"
).implementation = function(hostname, peerCertificates) {
// 直接返回,跳过所有指纹验证
console.log("[+] Bypassing SSL Pinning (OkHttp) for: " + hostname);
return; // void 方法,直接返回即绕过
};
// 兼容旧版 OkHttp 的另一个重载
CertificatePinner.check.overload(
"java.lang.String",
"[Ljava.security.cert.Certificate;"
).implementation = function(hostname, certs) {
console.log("[+] Bypassing SSL Pinning (OkHttp legacy) for: " + hostname);
return;
};
} catch(e) {
console.log("[-] OkHttp CertificatePinner not found: " + e.message);
}
});
对于使用自定义 X509TrustManager 实现证书固定的 App,需要通过注入一个"空的"TrustManager 来绕过:
// frida hook: 注入空 TrustManager,绕过 SSLContext.init 中的证书校验
Java.perform(function() {
// 动态注册一个什么都不做的 TrustManager
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var TrustManager = Java.registerClass({
name: "com.bypass.TrustManager",
implements: [X509TrustManager],
methods: {
// checkClientTrusted: 不做任何检查,接受所有客户端证书
checkClientTrusted: function(chain, authType) {
console.log("[+] checkClientTrusted bypassed");
},
// checkServerTrusted: 不做任何检查,接受所有服务端证书
checkServerTrusted: function(chain, authType) {
console.log("[+] checkServerTrusted bypassed for: " +
chain[0].getSubjectDN().getName());
},
// 返回空的接受列表,表示接受所有 CA
getAcceptedIssuers: function() {
return Java.array("java.security.cert.X509Certificate", []);
}
}
});
// Hook SSLContext.init,将我们的空 TrustManager 注入进去
var SSLContext = Java.use("javax.net.ssl.SSLContext");
SSLContext.init.overload(
"[Ljavax.net.ssl.KeyManager;",
"[Ljavax.net.ssl.TrustManager;",
"java.security.SecureRandom"
).implementation = function(keyManagers, trustManagers, secureRandom) {
console.log("[+] SSLContext.init hooked, injecting empty TrustManager");
// 替换原有的 TrustManager 数组,注入我们的空实现
this.init(keyManagers,
Java.array("javax.net.ssl.TrustManager", [TrustManager.$new()]),
secureRandom);
};
console.log("[+] TrustManager bypass activated");
});
如第四章方案 A 所述,在 APK 中删除 <pin-set> 节点或添加用户证书信任,重签打包:
<!-- 修改前(有 SSL Pinning)-->
<domain-config>
<domainincludeSubdomains="true">api.example.com</domain>
<pin-set>
<pindigest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
</pin-set>
</domain-config>
<!-- 修改后(删除 pin-set 节点即可解除 SSL Pinning)-->
<domain-config>
<domainincludeSubdomains="true">api.example.com</domain>
<!-- pin-set 节点已删除 -->
</domain-config>
mTLS 的对抗核心是提取 App 内置的客户端证书,然后将其导入抓包工具,让抓包工具能以合法客户端的身份与服务端握手。
# 解压 APK(APK 本质是 ZIP 格式)
unzip -d apk_extracted target.apk
# 在解压目录中搜索证书格式文件
find apk_extracted -name "*.p12" -o -name "*.bks" -o -name "*.cer" -o -name "*.pfx"
# 注意:证书可能伪装为其他格式(.png/.dat/.bin 等),
# 可以用 file 命令检查真实格式
find apk_extracted/assets -type f | xargs file | grep -i pkcs
# 常见存放位置
# assets/ssl/client.p12
# res/raw/keystore.bks
# assets/cert/client_certificate.dat(伪装格式)
如果静态分析找不到,或证书在运行时才解密,可以在运行时捕获:
// frida hook: 在 KeyStore.load 时 dump 证书输入流到本地文件
Java.perform(function() {
var KeyStore = Java.use("java.security.KeyStore");
KeyStore.load.overload(
"java.io.InputStream",
"[C"// char 数组密码
).implementation = function(stream, password) {
// 打印密码(通常是硬编码字符串)
if (password !== null) {
var pwd = Java.use("java.lang.String").$new(password);
console.log("[+] KeyStore password: " + pwd);
}
if (stream !== null) {
// 将 InputStream 数据读出并写入本地文件
var ByteArrayOutputStream = Java.use("java.io.ByteArrayOutputStream");
var baos = ByteArrayOutputStream.$new();
var buffer = Java.array("byte", newArray(4096).fill(0));
var bytesRead;
while ((bytesRead = stream.read(buffer)) !== -1) {
baos.write(buffer, 0, bytesRead);
}
var data = baos.toByteArray();
// 写入到应用私有目录
var FileOutputStream = Java.use("java.io.FileOutputStream");
var fos = FileOutputStream.$new("/data/data/<package>/files/client_cert.p12");
fos.write(data);
fos.close();
console.log("[+] Certificate dumped to /data/data/<package>/files/client_cert.p12");
// 重置 stream 供原方法使用(需要重新创建 InputStream)
var ByteArrayInputStream = Java.use("java.io.ByteArrayInputStream");
stream = ByteArrayInputStream.$new(data);
}
// 调用原始 load 方法
this.load(stream, password);
};
});
# 反编译 APK
jadx -d jadx_output target.apk
# 在反编译代码中搜索 KeyStore 相关代码
grep -r "KeyStore" jadx_output/sources/ | grep -E "load|password"
# 或搜索 .p12/.bks 文件名关键字
grep -r "client" jadx_output/sources/ --include="*.java" | grep -E "p12|bks|cert"
密码通常以字符串字面量或常量的形式出现在紧邻 keyStore.load() 的代码处,例如:
// jadx 反编译结果(示例)
keyStore.load(getAssets().open("client.p12"), "P@ssw0rd123".toCharArray());
提取到 .p12 文件和密码后:
Charles:
Proxy → SSL Proxying Settings → Client Certificates
→ 添加域名:api.example.com
→ 选择证书文件:client.p12
→ 输入密码
Burp Suite:
User options → SSL → Client TLS Certificates
→ Add → Destination Host: api.example.com
→ Certificate type: PKCS#12 file
→ File: client.p12,Password: 输入
当 App 代码经过高强度混淆(ProGuard/R8/DexGuard),或使用了非标准网络框架时,以上基于类名的 Hook 方法可能会因为找不到目标类而失败。此时需要下沉到更底层的 Hook 层次。

OkHttp 内部采用责任链模式(Interceptor Chain),所有请求/响应都会依次经过已注册的拦截器。如果能在运行时动态注入一个日志拦截器,就能在不知道混淆类名的前提下捕获所有 HTTP 流量。
OkHttpLogger-Frida 方案:
# 1. 下载工具
# git clone https://github.com/siyujie/OkHttpLogger-Frida
# 2. 推送辅助 DEX 到设备
adb push okhttpfind.dex /data/local/tmp/
adb shell chmod 777 /data/local/tmp/okhttpfind.dex
# 3. 启动 Frida 并加载脚本
frida -U -f <package_name> -l okhttp_poker.js --no-pause
// okhttp_poker.js(核心逻辑)
// 通过反射动态定位 OkHttpClient 类(即使被混淆也能找到)
functionfindOkHttpClient() {
// 遍历所有已加载的类,寻找包含 newCall 方法的类
var clazzes = Java.enumerateLoadedClassesSync();
for (var i = 0; i < clazzes.length; i++) {
try {
var clazz = Java.use(clazzes[i]);
if (clazz.newCall !== undefined) {
return clazzes[i]; // 找到了 OkHttpClient
}
} catch(e) {}
}
returnnull;
}
// 找到后,通过 Builder 动态添加日志拦截器
// 具体实现参见 OkHttpLogger-Frida 项目 README
当 App 绕过系统代理直接使用 Socket 时,可以 Hook Java 层的 Socket 原生方法:
// frida hook: 截获 SocketOutputStream.socketWrite0
Java.perform(function() {
// Hook 原生方法(native method)
var SocketOutputStream = Java.use("java.net.SocketOutputStream");
SocketOutputStream.socketWrite0.implementation = function(fd, buf, off, len) {
// 将写入的字节流转换为字符串打印
var bytes = Java.array("byte", buf);
var data = "";
for (var i = off; i < off + len && i < bytes.length; i++) {
var c = bytes[i] & 0xFF;
if (c >= 32 && c < 127) {
data += String.fromCharCode(c);
} else {
data += ".";
}
}
if (data.indexOf("HTTP") >= 0 || data.indexOf("GET ") >= 0 ||
data.indexOf("POST ") >= 0) {
console.log("[Socket Write] " + data.substring(0, 500));
}
returnthis.socketWrite0(fd, buf, off, len);
};
});
在 TLS 层解密完成之后、数据到达应用逻辑之前截获,这是 Java 层最接近底层的 Hook 点:
// frida hook: 截获 ConscryptFileDescriptorSocket 的 SSL 读写
Java.perform(function() {
try {
// Android 系统 TLS 实现(Conscrypt 库)
var SSLOutputStream = Java.use(
"com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream");
var SSLInputStream = Java.use(
"com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream");
// Hook HTTPS 请求发送(TLS 解密后的明文)
SSLOutputStream.write.overload("[B", "int", "int")
.implementation = function(buf, offset, len) {
var data = Java.use("java.lang.String")
.$new(Java.array("byte", buf), offset, len, "UTF-8");
console.log("[SSL Write] " + data.substring(0, 1000));
returnthis.write(buf, offset, len);
};
// Hook HTTPS 响应接收
SSLInputStream.read.overload("[B", "int", "int")
.implementation = function(buf, offset, len) {
var result = this.read(buf, offset, len);
if (result > 0) {
var data = Java.use("java.lang.String")
.$new(Java.array("byte", buf), offset, result, "UTF-8");
console.log("[SSL Read] " + data.substring(0, 1000));
}
return result;
};
} catch(e) {
console.log("[-] SSL Socket hook failed: " + e.message);
}
});
调用链:java.net.SocketOutputStream.socketWrite0 → libopenjdk.so NET_Send → libc.so sendto
在 libc.so 的 sendto 函数处 Hook,可以捕获所有通过 Socket 发送的原始字节:
// frida hook: Hook Native 层 libc.so sendto/recvfrom
Interceptor.attach(Module.findExportByName("libc.so", "sendto"), {
onEnter: function(args) {
var fd = args[0].toInt32();
var buf = args[1];
var len = args[2].toInt32();
// 读取缓冲区内容(可能是 HTTP 明文或加密后的 TLS 数据)
var data = buf.readByteArray(Math.min(len, 512));
var str = "";
for (var i = 0; i < data.length; i++) {
var c = data[i] & 0xFF;
str += (c >= 32 && c < 127) ? String.fromCharCode(c) : ".";
}
if (str.indexOf("HTTP") >= 0 || str.indexOf("GET ") >= 0) {
console.log("[sendto fd=" + fd + "] " + str);
}
}
});
这是整个 Hook 体系中层次最深、最有效的方案——直接在 libssl.so 的 SSL_write 和 SSL_read 函数处截获:
// r0capture 核心 Hook 逻辑(简化版)
// 完整版请参考 https://github.com/r0ysue/r0capture
functionhook_ssl_write() {
var sslWrite = Module.findExportByName("libssl.so", "SSL_write");
if (!sslWrite) {
sslWrite = Module.findExportByName("libssl.so", "ssl3_write");
}
Interceptor.attach(sslWrite, {
onEnter: function(args) {
// args[0]: SSL* (SSL 上下文指针)
// args[1]: buf(明文数据缓冲区)
// args[2]: num(数据长度)
var ssl = args[0];
var buf = args[1];
var len = args[2].toInt32();
// 获取文件描述符(用于关联到具体连接)
var getFd = Module.findExportByName("libssl.so", "SSL_get_rfd");
var fd = new NativeFunction(getFd, "int", ["pointer"])(ssl);
// 读取明文数据
var data = buf.readByteArray(Math.min(len, 4096));
console.log("[SSL_write fd=" + fd + "] " +
newUint8Array(data).reduce((s, b) =>
s + (b >= 32 && b < 127 ? String.fromCharCode(b) : "."), ""));
}
});
}
hook_ssl_write();
r0capture 的实际使用:
# 基本使用
python r0capture.py -U com.example.app -v
# 输出为 PCAP 文件(可用 Wireshark 分析)
python r0capture.py -U com.example.app -v -p capture.pcap
# spawn 模式(推荐,在 App 启动前注入)
python r0capture.py -U -f com.example.app -v -p capture.pcap
r0capture 能力矩阵:
eBPF(Extended Berkeley Packet Filter) 是运行在 Linux 内核中的虚拟机技术,允许用户在不修改内核代码、不加载内核模块的前提下,将自定义程序"注入"到内核的各种事件钩子点上运行。
eBPF 程序的执行由特定事件触发,支持的挂载点包括:
eBPF 的核心优势对安全研究场景极为重要:
eCapture 通过 uprobe 钩子挂载在 Android 系统中的加密共享库(OpenSSL/BoringSSL/NSS/GnuTLS)的关键读写函数上,在 TLS 解密完成后立即捕获明文数据。

工作流程:
libssl.so 的加载地址;SSL_write 和 SSL_read 的函数入口/出口处注册 uprobe 探针;SSL_write(发送数据)或 SSL_read(接收数据)时,eBPF 程序被执行;整个过程中,目标 App 毫无察觉——没有任何进程注入,没有 ptrace 调用,没有额外的系统调用痕迹。
# 检查 Android 版本
adb shell getprop ro.build.version.release
# 检查内核版本(需要 >= 5.10)
adb shell cat /proc/version
# 或
adb shell uname -r
# 示例输出:Linux version 5.15.104-android14-... 表示内核 5.15,满足要求
目前 eCapture 对 Android 的支持情况:
# 1. 下载 eCapture Android 版本(ARM64 架构)
# 从 https://github.com/gojue/ecapture/releases 下载对应版本
# 2. 推送到设备
adb push ecapture /data/local/tmp/
adb shell chmod 777 /data/local/tmp/ecapture
# 3. 获取目标 App 的 PID
adb shell pidof com.example.app
# 或通过 ps 过滤
adb shell ps | grep example
# 4. 启动 eCapture,捕获指定进程的 TLS 流量
adb shell /data/local/tmp/ecapture tls -p <PID> -m text
# 5. 输出为 PCAP 文件(可用 Wireshark 分析)
adb shell /data/local/tmp/ecapture tls -p <PID> -m pcap -o /data/local/tmp/output.pcap
adb pull /data/local/tmp/output.pcap ./
常用参数说明:
tls | |
-p <PID> | |
-m text | |
-m pcap | |
-o <path> | |
--libssl <path> |
选型建议:
本章是全文的综合演练环节,将前面所有技术集中应用于一个典型的"加密接口逆向"场景:目标接口使用 AES-CBC 加密数据,并附带时间戳 + HMAC 签名防重放。
目标接口:
POST https://api.example.com/get_coin
Content-Type: application/json
{
"user_data": "u2FsdGVkX1+xxxxx...",
"timestamp": 1710000000,
"sign": "a1b2c3d4e5f6..."
}
分析目标:
user_data 字段的加密方式(Key、IV);sign 的签名算法;如果 App 有代理检测或 SSL Pinning,优先使用 r0capture 在 SSL 底层直接获取明文:
# 启动 r0capture
python r0capture.py -U com.example.app -v -p raw_capture.pcap
# 在 App 内触发目标接口(如点击签到获取金币)
# 打开 Wireshark 分析 raw_capture.pcap
# 过滤条件:http.request.uri contains "get_coin"
在 Wireshark 中可以看到完整的明文 HTTP 请求,得到密文字段后,再通过 jadx 逆向找出 Key 和 IV。
# 使用 jadx-gui 打开 APK(GUI 模式搜索更方便)
jadx-gui target.apk
关键字搜索策略:在 jadx 搜索框中依次搜索 AES、CBC、encrypt、HMAC、sha256,定位加密函数位置。
典型的 AES 加密代码(jadx 反编译后):
// jadx 反编译后的加密函数(示例)
publicclassCryptoUtils{
// 硬编码的 Key 和 IV(可能在常量中,也可能动态下发)
privatestaticfinal String AES_KEY = "AbCdEfGhIjKlMnOp"; // 16 字节 AES-128
privatestaticfinal String AES_IV = "OpMnLkJiHgFeDcBa"; // 16 字节 IV
publicstatic String encrypt(String plaintext)throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes("UTF-8"), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(AES_IV.getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8"));
return Base64.encodeToString(encrypted, Base64.DEFAULT);
}
}
签名函数:
// HMAC-SHA256 签名生成
publicstatic String generateSign(String data, long timestamp){
String toSign = data + timestamp + "secret_key_here";
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec("secret_key_here".getBytes(), "HmacSHA256");
hmac.init(keySpec);
byte[] result = hmac.doFinal(toSign.getBytes("UTF-8"));
return bytesToHex(result);
}
静态分析提取的 Key 可能是动态计算的,通过 Frida 动态验证是最保险的方式:
// frida hook: 动态打印 AES 加密的 Key、IV 和明文数据
Java.perform(function() {
var Cipher = Java.use("javax.crypto.Cipher");
Cipher.init.overload(
"int",
"java.security.Key",
"java.security.spec.AlgorithmParameterSpec"
).implementation = function(mode, key, params) {
if (mode === 1) { // 只关注加密操作
var keyBytes = key.getEncoded();
var keyHex = Array.from(newUint8Array(keyBytes))
.map(b => b.toString(16).padStart(2, "0")).join("");
console.log("[AES Cipher] Key (hex): " + keyHex);
try {
var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec");
var ivSpec = Java.cast(params, IvParameterSpec);
var ivBytes = ivSpec.getIV();
var ivHex = Array.from(newUint8Array(ivBytes))
.map(b => b.toString(16).padStart(2, "0")).join("");
console.log("[AES Cipher] IV (hex): " + ivHex);
} catch(e) {}
}
returnthis.init(mode, key, params);
};
// Hook doFinal 打印明文
Cipher.doFinal.overload("[B").implementation = function(input) {
var inputStr = Java.use("java.lang.String").$new(input, "UTF-8");
console.log("[AES doFinal] Input plaintext: " + inputStr);
var result = this.doFinal(input);
return result;
};
});
运行后输出示例:
[AES Cipher] Key (hex): 4162436445664768496a4b6c4d6e4f70
[AES Cipher] IV (hex): 4f704d6e4c6b4a6948674665446342 61
[AES doFinal] Input plaintext: {"uid":"123456","action":"get_coin"}
确认加密算法、Key、IV 和签名方式后,用 Python 完整还原请求生成逻辑:
# 接口重放脚本:还原 AES-CBC 加密 + HMAC-SHA256 签名
# 依赖:pip install pycryptodome requests
import json
import time
import hmac
import hashlib
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
# ===== 加密参数(从逆向分析中提取)=====
AES_KEY = b"AbCdEfGhIjKlMnOp"# 16 字节 AES-128 密钥
AES_IV = b"OpMnLkJiHgFeDcBa"# 16 字节 IV
HMAC_KEY = "secret_key_here"# HMAC 签名密钥
defaes_encrypt(plaintext: str) -> str:
"""AES-CBC 加密,返回 Base64 编码结果"""
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
padded = pad(plaintext.encode("utf-8"), AES.block_size) # PKCS7 填充
encrypted = cipher.encrypt(padded)
return base64.b64encode(encrypted).decode("utf-8")
defaes_decrypt(ciphertext_b64: str) -> str:
"""AES-CBC 解密"""
ciphertext = base64.b64decode(ciphertext_b64)
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
decrypted = unpad(cipher.decrypt(ciphertext), AES.block_size)
return decrypted.decode("utf-8")
defgenerate_sign(data: str, timestamp: int) -> str:
"""HMAC-SHA256 签名生成"""
to_sign = data + str(timestamp) + HMAC_KEY
signature = hmac.new(
HMAC_KEY.encode("utf-8"),
to_sign.encode("utf-8"),
hashlib.sha256
).hexdigest()
return signature
defcall_get_coin(uid: str, token: str) -> dict:
"""构造并发送 get_coin 接口请求"""
payload = json.dumps({"uid": uid, "action": "get_coin"}, separators=(",", ":"))
encrypted_data = aes_encrypt(payload)
timestamp = int(time.time())
sign = generate_sign(encrypted_data, timestamp)
request_body = {
"user_data": encrypted_data,
"timestamp": timestamp,
"sign": sign
}
headers = {
"Content-Type": "application/json",
"User-Agent": "ExampleApp/3.2.1 (Android; en-US)",
"Authorization": f"Bearer {token}",
}
response = requests.post(
"https://api.example.com/get_coin",
json=request_body,
headers=headers,
timeout=10
)
resp_json = response.json()
if"data"in resp_json:
return json.loads(aes_decrypt(resp_json["data"]))
return resp_json
if __name__ == "__main__":
result = call_get_coin(uid="123456", token="your_token_here")
print("接口响应:", json.dumps(result, ensure_ascii=False, indent=2))
通过以上四步,完成了从"只能手动操作 App"到"Python 脚本全自动化"的完整逆向分析链路。
当抓包遇到问题,按下图流程逐步排查,定位根因后选择最低成本的解法:
原则一:优先成本最低方案
不要一开始就使用"核武器"——优先尝试最简单的方案,遇到阻碍再升级,这样能节省大量时间。
原则二:遇到加密协议先抓后逆
① 先用 r0capture 抓取原始包,确认请求/响应格式
② 再用 jadx 逆向找加密函数位置
③ Frida 动态验证 Key/IV 等参数
④ 最后 Python 脚本实现自动化重放
原则三:抓包工具与 Frida 配合
抓包工具负责观察数据结构(字段名、加密前后格式、响应逻辑);Frida 负责定位加密位置(在哪里加密、Key/IV 是什么、签名怎么生成)。两者配合能最快速地还原加密协议。
重要声明:本文涉及的所有技术均属于安全研究领域的通用技术手段,在合法授权的范围内使用具有重要的安全价值。
严格禁止的行为:
合法使用场景:自己或有授权的 App 安全评估、个人技术学习、负责任的漏洞披露、授权范围内的自动化测试。
文章版本:2026 年 3 月
适用系统版本:Android 4 - 16
风险提示:本文所有内容仅供安全学习与研究使用,请在授权范围内使用相关技术。