为什么常规套路对抖音全部失效
想抓抖音 App 的包,网上教程的套路是:装 Charles/Fiddler 证书 → 上 JustTrustMe → 完事。真上手才发现对抖音一个都不好使:
- JustTrustMe 无效:抖音不信任用户证书,且 SSL Pinning 不止一处
- hook okhttp3 报错:
java.lang.ClassNotFoundException: okhttp3.RealCall——抖音根本不用 okhttp,网络库是字节自己封的com.bytedance.retrofit2.SsHttpCall - Java 层全 hook 了还是看不到明文:因为 TLS 加解密发生在 Native 层,Java 层拿到的已经是密文
这篇文章记录我最终打通的方案:Java 层绕过 Pinning + Native 层 hook boringssl,并附上真实抓到的流量分析。
Java 层:先绕过证书固定
抖音的 Pinning 分布在好几个类里,需要全部处理。我的 hook 框架初始化时加载的清单:
['com.android.org.conscrypt.TrustManagerImpl',
'TM:android.net.http.X509TrustManagerExtensions',
...]TrustManagerImpl:conscrypt 的默认证书校验X509TrustManagerExtensions:Android 7+ 的扩展校验点
同时 hook 住两个 HTTP 入口,观察流量走向:
com.bytedance.retrofit2.SsHttpCall hooked
HttpURLConnection hooked注意 SsHttpCall——这是字节 retrofit2 封装的调用入口,hook 住它才能看到 Java 层的请求。但很快会发现:即使全部 hook 成功,很多请求依然只能看到加密后的数据流。因为抖音把 TLS 栈整个搬进了 Native 层。
Native 层:抖音的 TLS 在 libttboringssl.so 里
抖音用的是 Google BoringSSL 的字节定制版,编译在 libttboringssl.so 里(同时还加载了系统 libssl.so)。这是字节系的通用做法:把 TLS 栈下沉到 SO,Java 层的证书 hook 全部够不着。
破解思路:不管证书了,直接在 SSL 读写函数上动手。hook libttboringssl.so 导出的 SSL_read / SSL_write,进出的都是解密后的明文:
// 伪代码示意:找到导出符号后 Interceptor.attach
const lib = Process.findModuleByName('libttboringssl.so');
const sslWrite = lib.findExportByName('SSL_write');
const sslRead = lib.findExportByName('SSL_read');
Interceptor.attach(sslWrite, {
onEnter(args) {
// args[1] = buf, args[2] = len,直接读明文
const buf = args[1];
const len = args[2].toInt32();
console.log('[SSL_write]', buf.readUtf8String(len));
}
});SSL_read 同理,在 onLeave 里读返回值长度对应的缓冲区即可。
实战:抓到的真实流量
方案打通后,明文流量哗哗地来。挑几条有代表性的(来自真实抓包记录):
设备设置下发(lynx 容器配置):
GET /service/settings/v3/?caller_name=lynx&os_type=android&aid=1128
&sdk_version=4.0.28&app_version=40.0.0&device_id=68584846924564...设备注册/长连接握手(这是最有价值的一条):
GET /ws/v2?aid=1128&device_id=68584846924564...
&access_key=468519e126a08a217c86202bcf29a7bd&fpid=9&sdk_version=3
&iid=7677481637751653...aid=1128 是抖音的 AppID,device_id、iid、access_key 是设备身份三件套——后续研究设备指纹协议(X-Argus、X-Ladon 那套)就是从这里开始的。
bytelink 长连接(字节自家 IM/推送通道):
POST /bytelink/fetch/v1/?is_guest_mode=0&app_type=normal
&channel=xiaomi_1128_64&device_type=M2007J1SC...
GET /bytelink/wss/v1/?is_guest_mode=0&app_type=normal...PCDN 调度配置:
GET /service/settings/v2/?caller_name=pcdn2_client_odl&app=1&bid=10020...工程化记录
抓包框架把所有 SSL 流量按 jsonl 格式落盘,一条一行,带方向、库名、长度、预览:
{"type":"ssl","dir":"write","lib":"libttboringssl.so","len":3242,"preview":"GET /ws/v2?aid=1128..."}
{"type":"ssl","dir":"read","lib":"libttboringssl.so","len":2606,"preview":"HTTP/1.1 200 OK..."}后续要分析哪个接口,直接对 jsonl 做过滤检索,不用重新抓包。本次实测记录:初始化 6 条、SSL 明文 9 组、Java 层异常 1037 条(主要是各类 ClassNotFound,字节混淆后很多类名对不上,属正常现象)。
总结
抖音抓包的完整链路:
- Java 层绕过
TrustManagerImpl/X509TrustManagerExtensions的 Pinning - 认清现实:抖音不用 okhttp,入口是
SsHttpCall - Native 层 hook
libttboringssl.so的SSL_read/SSL_write拿明文 - 流量落盘成 jsonl,离线分析
这套方案不只适用抖音——TikTok、头条、西瓜等字节系 App 全是同一套网络栈,一次打通全部通用。
交流微信:teawhites