抖音直播间的 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,进逆向群聊,一起探讨技术
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值