乐于分享
好东西不私藏

安卓逆向系列 07:SSL Pinning 绕过与 HTTPS 抓包全攻略

安卓逆向系列 07:SSL Pinning 绕过与 HTTPS 抓包全攻略

本系列文章仅供安全研究与学习使用,请在合法授权环境下操作,切勿用于非法用途。


一、什么是 SSL Pinning,为什么需要绕过

1.1 问题的起点

你架好了 Burp Suite,手机上配好了代理,满心期待地打开 App——结果发现:

Network error: SSL handshake failed
或者
javax.net.ssl.SSLHandshakeException: Certificate pinning failure

这就是遇上了 SSL Pinning(证书锁定/证书绑定)。

1.2 SSL Pinning 的作用

普通 HTTPS 只验证证书链是否由受信任 CA 签发。而 SSL Pinning 更进一步——App 内置了服务端证书的指纹(Hash)公钥,只有与内置值完全匹配才允许连接:

普通 HTTPS:系统信任的任意 CA 签发的证书 → 通过
SSL Pinning:必须是我指定的那张证书/公钥 → 其他全拒绝

这样即使攻击者(或安全研究员)插入了中间人证书,也无法通过 Pinning 验证,抓包失败。

1.3 为什么逆向分析需要绕过 Pinning

目的 说明
接口分析 了解 App 与服务端的通信格式和字段含义
参数还原 定位签名参数的生成位置
协议复现 用 Python/脚本复现 API 调用
安全测试 检测接口是否存在越权、注入等漏洞

二、SSL Pinning 原理图解

正常流程(无 Pinning):

App → HTTPS 请求 → [Burp 中间人] → 用 Burp CA 签名 → 服务端
App ← HTTPS 响应 ← [Burp 解密/重加密] ← 服务端

但中间人的证书是 Burp 自签的,若 App 开启了 Pinning,会在握手阶段检测到证书不匹配并拒绝连接。

绕过后:

App → Frida/objection Hook Pinning 检查函数 → 始终返回"通过"
App → HTTPS 请求 → [Burp 中间人] 正常工作 → 服务端

三、三种 Pinning 实现方式

3.1 OkHttp CertificatePinner(最常见)

// 典型的 OkHttp Pinning 代码
OkHttpClientclient=newOkHttpClient.Builder()
    .certificatePinner(newCertificatePinner.Builder()
        .add("api.example.com""sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
        .add("api.example.com""sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
        .build())
    .build();

检测特征:jadx 中搜索 CertificatePinner 或 sha256/ 字符串即可定位。

3.2 自定义 TrustManager(中等难度)

// 手动实现证书验证
classCustomTrustManagerimplementsX509TrustManager {
@Override
publicvoidcheckServerTrusted(X509Certificate[] chainStringauthType
throwsCertificateException {
// 比对证书指纹
Stringfingerprint=getFingerprint(chain[0]);
if (!EXPECTED_FINGERPRINT.equals(fingerprint)) {
thrownewCertificateException("Certificate mismatch!");
        }
    }
}

3.3 Native 层 Pinning(最难绕过)

部分 App 在 .so 文件中实现证书校验,通过 OpenSSL/BoringSSL 的 SSL_CTX_set_verify 等函数完成。这种方式无法用 Java 层 Hook 绕过,需要 Hook Native 函数。

实现方式 所在层 Objection 能否绕过 Frida 难度
OkHttp CertificatePinner Java ✅ 可以 简单
自定义 TrustManager Java ✅ 大多可以 简单-中等
Conscrypt/BouncyCastle Java ✅ 可以 中等
OpenSSL Native Native ❌ 不能 较难
BoringSSL Native Native ❌ 不能 较难

四、环境准备:Burp + 手机代理配置

4.1 Burp Suite 配置

1. Burp → Proxy → Options → Proxy Listeners
2. 添加监听器:0.0.0.0:8888(监听所有网卡)
3. Burp → Proxy → Options → Export CA Certificate
   → 格式选 DER → 保存为 burp_ca.der

4.2 手机安装 Burp CA 证书

# 将证书推送到手机
adb push burp_ca.der /sdcard/

# 手机操作路径:
# 设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书
# 选择 /sdcard/burp_ca.der

4.3 手机配置代理

设置 → WLAN → 长按当前 WiFi → 修改网络
→ 代理:手动
→ 代理主机名:电脑 IP(如 192.168.1.100)
→ 代理端口:8888
→ 保存

4.4 Android 7+ 用户证书信任问题

Android 7.0 起,App 默认不信任用户安装的 CA 证书(只信任系统 CA),需要额外处理(见第九节)。


五、方案一:objection 一键绕过

objection 是基于 Frida 的移动端安全测试框架,内置了主流 Pinning 的绕过脚本,使用最简单。

5.1 安装 objection

pip install objection

# 验证安装
objection version

5.2 连接 App

# 方式一:附加到运行中的进程
objection -g com.target.app explore

# 方式二:spawn 模式(App 启动前注入)
objection -g com.target.app explore --startup-command'android sslpinning disable'

5.3 执行绕过

# 进入 objection shell 后
[com.target.app]# android sslpinning disable

# 成功输出:
# (agent) Custom TrustManager registered, overriding...
# (agent) OkHTTP 3.x TrustManager overridden
# (agent) OkHTTP 4.x CertificatePinner overridden
# (agent) Bypassing TrustManagerImpl (Android)

5.4 objection 绕过覆盖范围

检测类 是否覆盖
OkHttpClient.CertificatePinner
javax.net.ssl.TrustManager
SSLContext.init
HttpsURLConnection
WebViewClient.onReceivedSslError
Native OpenSSL

六、方案二:Frida 脚本绕过(完整代码)

当 objection 无效时,需要手写 Frida 脚本精准 Hook。

6.1 通用绕过脚本(覆盖主流 Java 实现)

// ssl_bypass.js - 通用 SSL Pinning 绕过脚本

Java.perform(function() {

// ========== 1. OkHttp CertificatePinner ==========
try {
varCertificatePinner=Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String''java.util.List')
            .implementation=function(hostnamecerts) {
console.log('[*] OkHttp CertificatePinner.check() bypassed for: '+hostname);
return// 直接返回,不抛出异常
            };

CertificatePinner.check.overload('java.lang.String''java.security.cert.Certificate')
            .implementation=function(hostnamecert) {
console.log('[*] OkHttp CertificatePinner.check(cert) bypassed');
return;
            };
    } catch(e) {
console.log('[-] OkHttp CertificatePinner not found: '+e);
    }

// ========== 2. 自定义 TrustManager ==========
try {
varTrustManagerImpl=Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation=function(
untrustedChaintrustAnchorChainhostclientAuthocspDatatlsSctData) {
console.log('[*] TrustManagerImpl.verifyChain() bypassed for: '+host);
returnuntrustedChain;
        };
    } catch(e) {
console.log('[-] TrustManagerImpl not found: '+e);
    }

// ========== 3. HttpsURLConnection ==========
try {
varHttpsURLConnection=Java.use('javax.net.ssl.HttpsURLConnection');
HttpsURLConnection.setDefaultHostnameVerifier.implementation=function(verifier) {
console.log('[*] HttpsURLConnection.setDefaultHostnameVerifier bypassed');
// 不调用原始方法,相当于使用宽松的 verifier
        };
    } catch(e) {}

// ========== 4. 动态创建绕过 TrustManager ==========
try {
varSSLContext=Java.use('javax.net.ssl.SSLContext');
varTrustManager=Java.use('javax.net.ssl.TrustManager');
varX509TrustManager=Java.use('javax.net.ssl.X509TrustManager');

// 创建一个信任一切的 TrustManager
varTrustEverything=Java.registerClass({
name'com.bypass.TrustEverything',
implements: [X509TrustManager],
methods: {
checkClientTrustedfunction(chainauthType) {},
checkServerTrustedfunction(chainauthType) {},
getAcceptedIssuersfunction() { return []; }
            }
        });

SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;',
'java.security.SecureRandom'
        ).implementation=function(keyManagerstrustManagerssecureRandom) {
console.log('[*] SSLContext.init() intercepted, injecting TrustAll');
vartrustAllManagers= [TrustEverything.$new()];
this.init(keyManagerstrustAllManagerssecureRandom);
        };
    } catch(e) {
console.log('[-] SSLContext override failed: '+e);
    }

// ========== 5. WebView SSL 错误绕过 ==========
try {
varWebViewClient=Java.use('android.webkit.WebViewClient');
WebViewClient.onReceivedSslError.implementation=function(viewhandlererror) {
console.log('[*] WebViewClient.onReceivedSslError bypassed');
handler.proceed(); // 忽略 SSL 错误
        };
    } catch(e) {}

console.log('[+] SSL Pinning bypass loaded successfully!');
});

6.2 运行脚本

# 附加到进程
frida -U-f com.target.app -l ssl_bypass.js --no-pause

# 或者附加到已运行的进程
frida -U com.target.app -l ssl_bypass.js

6.3 OkHttp 4.x 专用绕过

OkHttp 4.x 使用 Kotlin 重写,类名略有不同:

// OkHttp 4.x (kotlin)
try {
varRealX509TrustManager=Java.use('okhttp3.internal.tls.RealTrustRootIndex');
// ...
catch(e) {}

// 更通用的方式:Hook CertificatePinner$check 方法
varCertificatePinner=Java.use('okhttp3.CertificatePinner');
// 获取所有重载版本
CertificatePinner['check$okhttp'].implementation=function() {
console.log('[*] OkHttp4 check$ bypassed');
};

七、方案三:r0capture 直接抓明文

r0capture 在 SSL 握手完成后、应用数据加密前的位置 Hook,可以直接抓到明文 HTTP 数据,无需证书配置。

7.1 原理

r0capture Hook 的是 javax.net.ssl.SSLOutputStream.write() 和 javax.net.ssl.SSLInputStream.read(),在数据传输层面拦截,完全绕过证书验证机制。

7.2 安装与使用

# 下载 r0capture
git clone https://github.com/r0ysue/r0capture

# 运行
python3 r0capture.py -U-f com.target.app

# 同时保存到 pcap 文件(可用 Wireshark 打开)
python3 r0capture.py -U-f com.target.app -p /tmp/output.pcap

7.3 r0capture 输出示例

[r0capture] Attached to com.target.app
[r0capture] SSL Write: POST /api/v2/user/login HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...

{"username":"test@example.com","password":"xxx","sign":"a1b2c3d4"}

[r0capture] SSL Read: HTTP/1.1 200 OK
Content-Type: application/json

{"code":0,"data":{"token":"xxxxxx","userId":12345}}

八、方案四:VPN 层抓包(mitmproxy)

不修改系统证书,通过 VPN 层面拦截流量,适合处理复杂的证书信任问题。

8.1 mitmproxy 配置

# 安装 mitmproxy
pip install mitmproxy

# 启动(透明代理模式)
mitmproxy --mode transparent --ssl-insecure
# 或
mitmdump --mode transparent -w /tmp/capture.mitm

8.2 配合 Android VPN App

推荐工具:
- VPNHotspot + 路由规则
- tun2socks(将流量路由到 mitmproxy)
- ProxyDroid(Root 模式,强制代理)

8.3 使用 mitmproxy 脚本过滤

# intercept_filter.py - 只记录目标域名的请求
frommitmproxyimporthttp

TARGET_DOMAINS = ['api.example.com''auth.example.com']

defrequest(flowhttp.HTTPFlow):
ifany(domaininflow.request.hostfordomaininTARGET_DOMAINS):
print(f"→ {flow.request.method} {flow.request.url}")
print(f"  Body: {flow.request.get_text()}")

defresponse(flowhttp.HTTPFlow):
ifany(domaininflow.request.hostfordomaininTARGET_DOMAINS):
print(f"← {flow.response.status_code}")
print(f"  Body: {flow.response.get_text()[:500]}")
# 运行过滤脚本
mitmproxy -s intercept_filter.py

九、Android 7+ 证书信任问题解决

9.1 问题原因

Android 7.0+ 引入了 Network Security Config,默认规则:

  • 系统 CA:App 完全信任

  • 用户安装的 CA:Debug 版 App 信任,Release 版不信任

9.2 方案一:修改 AndroidManifest.xml(推荐)

# 1. 反编译 APK
apktool d target.apk -o target_decoded/

# 2. 在 AndroidManifest.xml 的 <application> 标签中添加
# android:networkSecurityConfig="@xml/network_security_config"

# 3. 创建 res/xml/network_security_config.xml
<!-- res/xml/network_security_config.xml -->
<?xmlversion="1.0" encoding="utf-8"?>
<network-security-config>
<base-configcleartextTrafficPermitted="true">
<trust-anchors>
<!-- 信任系统 CA -->
<certificatessrc="system"/>
<!-- 信任用户 CA(Burp 证书) -->
<certificatessrc="user"/>
</trust-anchors>
</base-config>
</network-security-config>
# 4. 重新打包 + 签名
apktool b target_decoded/ -o target_patched.apk
# 使用 uber-apk-signer 签名
java -jar uber-apk-signer.jar --apks target_patched.apk

9.3 方案二:将 Burp CA 安装到系统证书目录(需 Root)

# 转换证书格式
openssl x509 -inform DER -in burp_ca.der -out burp_ca.pem

# 计算证书 hash(Android 需要特定命名格式)
HASH=$(openssl x509 -inform PEM -subject_hash_old -in burp_ca.pem | head -1)
cp burp_ca.pem ${HASH}.0

# 推送到系统证书目录
adb root
adb remount
adb push ${HASH}.0 /system/etc/security/cacerts/
adb shell chmod644 /system/etc/security/cacerts/${HASH}.0
adb reboot

9.4 方案三:Magisk 模块(最优雅)

Magisk Manager → 搜索并安装 "Move Certificates" 模块
→ 重启后,用户安装的 CA 自动移动到系统证书目录
→ 所有 App 均信任 Burp CA

十、双向认证(mTLS)绕过

部分金融类 App 不仅验证服务端证书,还要求客户端出示证书(双向认证)。

10.1 提取客户端证书

// Frida 脚本:拦截 KeyManager,提取客户端证书和私钥
Java.perform(function() {
varKeyManagerFactory=Java.use('javax.net.ssl.KeyManagerFactory');

KeyManagerFactory.init.overload(
'java.security.KeyStore''[C'
    ).implementation=function(kspassword) {
console.log('[*] KeyManagerFactory.init() called');

// 枚举 KeyStore 中的证书
varaliases=ks.aliases();
while(aliases.hasMoreElements()) {
varalias=aliases.nextElement();
console.log('  Alias: '+alias);
varcert=ks.getCertificate(alias);
console.log('  Cert: '+cert.toString());
        }

this.init(kspassword);
    };
});

10.2 导入到 Burp

# 将提取的 PKCS12 格式证书导入 Burp
# Burp → Settings → Network → TLS → Client TLS Certificates
# → Add → 选择证书文件和密码

十一、常见抓包工具对比

工具 平台 支持协议 免 Root SSL Pinning 绕过 优势 劣势
Burp Suite PC HTTP/HTTPS/WebSocket 需配代理 需额外处理 功能强大,插件丰富 配置复杂
Charles PC/Mac HTTP/HTTPS 需配代理 需额外处理 界面友好 付费
mitmproxy PC HTTP/HTTPS 透明代理 需额外处理 可编程,脚本强大 命令行
r0capture PC+Frida HTTP/HTTPS 需 Root ✅ 内置绕过 直接抓明文 依赖 Frida
objection PC+Frida 所有协议 需 Root ✅ 内置绕过 操作简便 复杂场景需手写
HttpCanary 手机 HTTP/HTTPS ❌ 不需要 部分支持 手机端直接使用 功能有限
Stream 手机 HTTP/HTTPS ❌ 不需要 不支持 操作简单 SSL Pinning 无解
Packet Capture 手机 HTTP/HTTPS ❌ 不需要 不支持 免费无 Root 功能基础

小结

SSL Pinning 绕过按难度从低到高:

  1. objection 一键绕过:覆盖 90% 的 Java 层 Pinning

  2. Frida 自定义脚本:覆盖特殊实现,精准 Hook

  3. r0capture:绕过所有 Java 层证书验证,直接抓明文

  4. 修改 AndroidManifest:解决 Android 7+ 系统级信任问题

  5. Native Hook:处理 .so 中实现的 Pinning(高难度)

拿到了明文流量之后,下一步就是分析接口中的加密字段和签名,这正是下一篇的主题。


文章持续更新,欢迎关注。转载请注明出处。