抖音直播间的 Websocket 和 Protobuf 解析

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在评论区联系作者立即删除!

前言

本文章讲解一下网页版直播间的数据逆向,关键逆向点在 signature 参数、Message.payloadack 应答,通过补环境的方式解决这几个问题。

一、加密参数

1.1 抓包分析

抓包数据,定位到数据请求,是一个 websocket 连接,wss://webcast5-ws-web-hl.douyin.com/webcast/im/push/v2/?...,后面有一大堆参数,只需要重点关注 room_idsignature 即可,这两个参数会动态变化,其他可先固定写死。

在这里插入图片描述

1.2 room_id

抓包数据中全局搜索 room_id 的值,定位到数据在 https://live.douyin.com/主播 ID 请求返回的 html 中。
在这里插入图片描述
请求没有特殊加密,直接发起请求,通过正则提取 html 中的 roomId 的值即可。

需要注意的是,请求返回了 set-cookie,要保存 ttwid 作为后面发起 websocketcookie
在这里插入图片描述
room_id 提取和 ttwid 保存代码如下。
在这里插入图片描述

1.3 signature

从请求调用栈定位到 websocket 发起,然后跟堆栈定位到 signature 的设置,再倒推到 signature 生成位置。
在这里插入图片描述 定位到_getSocketParams 是最终 signature 的设置入口,单步调试,继续跟栈。
在这里插入图片描述
最终确定 signature 生成方法是 r.frontierSign ,对传入的参数 {"X-MS-STUB": a}进行加密,得到最终的值。
在这里插入图片描述

1.3.1 a 参数

观察 a 的值 'b8c42842e8a17992f007bf4e0043e5e8',猜测是 md5加密,分析代码,找到加密函数 x()

注意:需要先判断一下 md5 函数有没有被魔改,如果有,则需要进一步调试,还原算法。
在这里插入图片描述
加密 "123456"字符串,判断是否和标准加密返回密文一致,如上图,经过测试,发现加密函数未作修改,直接通过标准的 md5处理即可。

分析代码发现加密的内容需要room_id,使用上面提取的动态 room_id 加密。
在这里插入图片描述

1.3.2 signature 加密

进入 webmssdk.es5.js 验证,确定 _0x5c2014 是我们要找的 signature 加密算法。
在这里插入图片描述
找到加密算法,剩下的就是让本地可以调用 _0x5c2014 实现动态生成 signature 了。

1.3.3 补环境

本地新建文件 signature.js,复制 webmssdk.es5.js 内容到文件中,导出目标函数 _0x5c2014,接下来就可以补环境啦~
在这里插入图片描述
验证本地生成 signature 流程。
在这里插入图片描述

二、直播间请求

2.1 PyExecJS

主要的请求流程通过 Python 实现,通过 PyExecJS 实现在 python 中调用上面实现的获取 signature 方法。

在这里插入图片描述

2.2 Websocket

构造 websocket 请求,主要实现 on_open、on_message、on_close、on_error 这几个基本的方法。在抖音直播间逆向过程中,关注 on_message 即可,每当服务器推送数据,就回进入到 on_message,在方法中可以针对服务器传输过来的数据进行解析,处理。
在这里插入图片描述
测试代码,结果如下。
在这里插入图片描述

三、消息解析

返回来的数据是 protobuf 序列化的内容,那么需要我们找到传输数据的 proto定义格式,去对内容进行反序列化,得到明文。

3.1 解析定位

服务端传输的数据进入到 on_message 处理中,跟进这个 e 的处理。
在这里插入图片描述
跟到 _receiveMessage 方法,找到 decode 关键字,继续跟进。
在这里插入图片描述
_decodeFrameOrResponse 跟进。
在这里插入图片描述
进入到 _decode,通过 this.getType(t) 判断消息类型,拿到对应的解析方法 n,通过 n.decode 对数据进行反序列化,得到明文数据。
在这里插入图片描述
可以看到最外层 PushFrameproto定义。
在这里插入图片描述
根据 PushFrame 结构,再解开嵌套字段的定义,至此最外层结构解析已完成。
在这里插入图片描述

3.2 protobuf

3.2.1 proto 定义

创建 danmu.proto,写入 PushFrame 定义。
在这里插入图片描述

3.2.2 protoc

  • 安装下载 protobuf
    在这里插入图片描述
    验证 protobuf 下载是否成功
    在这里插入图片描述
    验证 protoc 命令是否有效
    在这里插入图片描述

  • 生成 pb2 文件
    执行命令,会得到一个 danmu_pb2.py 文件。
    在这里插入图片描述

  • 解析消息
    在这里插入图片描述

3.3 Response

按照上面的步骤,解析完消息的第一层 PushFrame,发现 paylaod 依旧是序列化后的内容,那么继续找到 paylaodproto 定义。

3.3.1 解压缩

回到 _decodeFrameOrResponset 是解开的 PushFrame 数据,然后进行 n = yield this._extractResponse(t);处理,跟进发现,这里对 paylaod 做了一层解压。
在这里插入图片描述

3.3.2 proto 定义

解压后的数据通过 _decode 执行反序列化操作,根据数据类型 Response 解析,跟进 n.decode(e)
在这里插入图片描述
得到第二层 Response 及嵌套的 Messageproto 定义。
在这里插入图片描述

3.3.3 更新 pb2

执行 protoc --python_out=. danmu.proto 生成新的 pb2.py 文件,在 Python 中调用 Response 定义解析上一层的 payload 值,重新请求解析,拿到结果。
在这里插入图片描述

3.4 Message

回到 let[d,p] = yield this._decodeFrameOrResponse(e),发现解析后的内容 d 虽然解析出了 message 结构,但还是看不到消息明文,还需要继续跟进。
在这里插入图片描述

3.4.1 消息体解析

回到 _receiveMessage,我们需要解析的数据被 c 变量接收,分析代码,发现在判断消息体类型,如果是 msg ,就会对 c 进行处理,之后不再有明显对 c 进行的操作,那么可以猜测 this.emitter.emit("message", c) 对消息数据做了明文解析。
在这里插入图片描述
跟进 emit 方法,是调用 message 的监听事件进行处理。
在这里插入图片描述
跟步调试,发现最终都会走到 _decode 这个方法,通过不同的消息类型,拿到不同的解码方法,根据 n.decode 都能找到最终 Message.paylaodproto 结构。
在这里插入图片描述
至此我们可以找到所有类型消息体的 proto 定义了,将定义补充到 danmu.proto 中重新生成 pb 文件,重新发起请求查看解析情况。
在这里插入图片描述

四、应答

虽然我们完成了消息的解析,但是会发现连接会过一段时间就被关闭,问题出在服务器会对客户端做应答检测。
在这里插入图片描述

4.1 检测位置

回到 _receiveMessage,发现在拿到 Response 响应结果后,对 need_ack 这里做了判断,如果需要应答,会封装好数据,通过 n.send(e) 发送到服务器,这样服务器才会一直源源不断将新数据推送过来。
在这里插入图片描述

4.2 ack 数据结构

分析代码得知,先构建一个 ack_data 对象,结构为 {payload_type: "ack", payload: xxx, LogID: xxx},再通过 this.encode 编码数据,得到最后的应答数据。
在这里插入图片描述

4.2.1 ack_data

本地新建一个文件 ack.js,定义生成 ack_data 的方法。ack_data 的结构数据均来自于之前解析好的 PushFrame 字段,只有 payload 需要做一下处理。
在这里插入图片描述

4.2.2 encode

进入到 _encode 内部,会根据不同消息类型,获取不同编码方法,最终通过 n.encode(e).finish(); 实现数据的序列化。
在这里插入图片描述
进入到最终数据序列化的处理中,ack 消息类型主要做两个处理,看下图箭头指向。
在这里插入图片描述

4.2.3 webpack

跟进调试,发现上面做数据序列化的对象 t 的定义来自于 975.17c85355.js5065 模块,来自于 webpack 动态加载,因此我们采用 webpack 逆向的代码扣取方案解决,不清楚的可以查看这篇文章: 常见反爬策略整理——Webpack

在这里插入图片描述
将扣取的代码放到之前创建好的 ack.js 文件中,生成一个 a 对象给 encode 调用。
在这里插入图片描述
完整生成应答数据的流程。
在这里插入图片描述

4.3 保持 ws 连接

4.3.1 生成 ack_data

还是通过 PyExecJS 实现 js 调用,生成应答数据。
在这里插入图片描述

4.3.2 发送响应

在收到服务器数据时,提取 PushFrame 封装应答数据发送,这样就能解决服务器自动断开的问题啦~~~~
在这里插入图片描述

4.3.3 效果验证

大功告成🎉🎉🎉🎉🎉🎉🎉🎉
在这里插入图片描述

5、交流群

不会经常刷博客,有需要者可以加本人,搜索 LOVE_SELF_AD_LIFE,进逆向群聊,一起探讨技术
在这里插入图片描述

### 直播间 WebSocket (WSS) 弹幕 API 实现方法 #### 建立 WebSocket 连接 为了实现实时接收直播间弹幕消息,首先需要建立一个 WebSocket 客户端连接。根据现有资料,使用 WebSockets 来提供这种低延迟的消息传递服务[^1]。 ```javascript const ws = new WebSocket('wss://example.com/websocket'); // 替换为实际的WebSocket服务器地址 ws.onopen = () => { console.log("Connection established"); }; ``` #### 发送身份验证请求 一旦建立了初始连接,则通常还需要发送一些初始化命令来完成认证过程并订阅特定频道或房间的信息流。具体的认证机制可能涉及加密签名其他安全措施,这取决于平台的具体设计[^2]。 #### 接收与处理消息 当成功加入某个直播间之后,客户端就可以开始监听来自服务器推送过来的各种事件通知了。这些事件可能是纯文本形式也可能是二进制帧,在 JavaScript 中可以通过 `onmessage` 方法捕获它们: ```javascript ws.onmessage = function(event){ let data; try{ if(typeof event.data === 'string'){ data = JSON.parse(event.data); }else{ // 如果是Blob对象则转换成ArrayBuffer再解析 const reader = new FileReader(); reader.onloadend = function(){ const arrayBuffer = this.result; handleBinaryData(arrayBuffer); }; reader.readAsArrayBuffer(event.data); } processData(data); }catch(error){ console.error("Error processing message:", error); } }; function handleBinaryData(buffer){ var decoder = new TextDecoder(); let str = decoder.decode(new Uint8Array(buffer)); console.log(str); // 或者更复杂的protobuf解码逻辑... } ``` 对于非字符串类型的响应(如 Protobuf 编码),可以利用相应的库来进行序列化/反序列化的操作。上述代码片段展示了如何读取 Blob 数据作为 ArrayBuffer 并尝试将其解释为 UTF-8 字符串;而在实际情况中,应该按照官方文档中的说明正确地解析 protobuf 结构体。 #### 断开连接 最后不要忘记定义关闭回调函数以便优雅地结束会话: ```javascript ws.onclose = () => { console.log("Disconnected from server."); }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值