百趣云 百趣云的博客

抖音 App 抓包终极方案:从 SSL Pinning 绕过到 hook libttboringssl

为什么常规套路对抖音全部失效

想抓抖音 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_idiidaccess_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,字节混淆后很多类名对不上,属正常现象)。

总结

抖音抓包的完整链路:

  1. Java 层绕过 TrustManagerImpl / X509TrustManagerExtensions 的 Pinning
  2. 认清现实:抖音不用 okhttp,入口是 SsHttpCall
  3. Native 层 hook libttboringssl.soSSL_read / SSL_write 拿明文
  4. 流量落盘成 jsonl,离线分析

这套方案不只适用抖音——TikTok、头条、西瓜等字节系 App 全是同一套网络栈,一次打通全部通用。

交流微信:teawhites

By 百趣云 阅读量:4 On