1. 项目概述与工具链准备
我说句实在话,现在做客户端接口分析,十个里有七八个都在跟WebSocket较劲。美团App的很多实时数据——比如订单状态推送、骑手位置更新、消息中心的即时通知——走的都是WSS(WebSocket over TLS)通道,而不是传统的HTTP轮询。这背后的原因也很直接:WebSocket能维持一条长连接,服务端可以主动往客户端推数据,实时性比轮询高出一个量级,而且省掉了大量无意义的HTTP握手开销。
把“美团WSS WebSocket逆向分析”作为研究对象,并不是说要去碰什么不该碰的东西,而是这个案例特别典型。美团App的WSS实现基本代表了国内大厂的主流做法:TLS加密传输、自定义协议头、JSON或二进制负载、心跳保活机制、Token鉴权。把这套链路拆明白,你再去分析其他App的WebSocket通信,思路和方法论可以直接平移,很多东西都大同小异。
这篇文章我会从零开始,把完整链路走一遍:先说清楚怎么定位WSS端点和确认抓包方案,再分析握手阶段的参数构造逻辑,然后深入解析消息帧的数据格式和加解密规则,最后演示如何用Python脚本完整复现一次WSS连接,以及常见的反爬坑和排查思路。
动手之前,先把工具准备好。我列一下这次实际用到的全家桶:
- 抓包工具:Charles 4.x 或 Fiddler Everywhere,用于明文抓取HTTPS请求,定位WSS握手入口。Android 7.0以上系统默认不信任用户证书,所以还需要配合Frida或Magisk模块处理SSL Pinning。
- 反编译与代码分析:jadx 1.4+,用于反编译APK,搜索关键字定位WebSocket相关代码路径。配合JEB或GDA做交叉引用,效率会更高。
- 动态调试:Frida 15.x+,用来Hook关键方法,动态查看参数生成过程,或者绕过证书校验。
- Python环境:需要websocket-client库(新版叫websockets库),或者直接基于asyncio写原生WebSocket客户端。
- 辅助工具:010 Editor或十六进制工具,用于分析二进制消息帧;Wireshark用于TCP层的原始流量对照;Burp Suite也可以备用,处理特殊场景。
注意:本案的抓包分为两个层面。如果只是看WSS握手请求,Charles足够;但如果要看TLS内部的WebSocket帧,就必须解决证书信任问题。这里最干净的做法是给手机装Magisk模块,把用户证书移入系统证书目录,同时Hook掉App的SSL Pinning检测。两个步骤缺一不可,否则要么抓不到明文,要么App直接断网。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 定位WSS端点与确认分析目标
2.1 从流量入口反查WebSocket端点
第一步永远是抓包看流量。美团App打开后,会有大量HTTP请求,你需要先去过滤WebSocket握手请求——特征是HTTP Upgrade头,值为websocket。用Charles抓包时,直接在Filter栏输入websocket,所有WSS连接会立刻被过滤出来。
我这次抓到的主要WSS端点长这样:
code复制wss://activity-api.meituan.com:443/ws?app=meituan&channel=and&...
注意几个细节。端口用的是443,说明TLS终止在标准HTTPS端口上,这类设计是为了躲过防火墙对非常规端口的拦截。协议版本通常有v参数标识,不同版本的消息格式可能有差异。另外,美团倾向于把多个业务域的WebSocket合并成一个网关入口,通过URL上的app和scene字段做路由分发,所以单独一个WSS端点背后可能承载着多个不同业务的消息。
确认端点的下一步,是把握手请求的完整头信息复制下来。这里能挖出很多重要线索:Sec-WebSocket-Key、Sec-WebSocket-Version、Origin、Cookie,以及一堆自定义Header。其中自定义Header往往藏着鉴权参数,比如我这次就看到了x-token、x-uid、x-sign这几个关键字段。
2.2 静态代码搜索定位WebSocket入口
光靠抓包看不到内部的逻辑,接下来用jadx反编译APK,在代码层面找到WebSocket的初始化入口。首选搜索关键字是WebSocket、websocket、WebSocketClient、OkHttpClient(美团客户端网络层基于OkHttp,这是公开信息)。
jadx打开反编译结果后,用全局搜索wss://或ws://,通常能直接命中WebSocket地址的拼接逻辑。我这次命中的代码大概长这样:
java复制public void connectWebSocket(String token, String uid) {
HttpUrl url = HttpUrl.parse("wss://activity-api.meituan.com/ws")
.newBuilder()
.addQueryParameter("app", "meituan")
.addQueryParameter("scene", "message_center")
.addQueryParameter("v", "2.0")
.build();
Request request = new Request.Builder()
.url(url)
.addHeader("x-token", token)
.addHeader("x-uid", uid)
.build();
mWebSocket = mOkHttpClient.newWebSocket(request, mListene
