首页
社区
课程
招聘
[原创]python纯协议实现亿级短视频app的直播间弹幕采集
发表于: 2026-10-7 18:07 4307

[原创]python纯协议实现亿级短视频app的直播间弹幕采集

2026-10-7 18:07
4307

版本:20260923,40.6.0

测试需求:纯协议获取直播间弹幕等信息

随机抓包找的10个直播间id,随机注册10台设备信息监控上并且持续接收弹幕等信息
图片描述

最开始,我想做的事情很简单:输入一个直播间 ID,在电脑上建立连接,把收到的弹幕解析出来。抓到 WebSocket 握手后,首先遇到的是 X-Cylons。它的算法还原出来了,但把 URL 里的 iid 随手换成另一个数,连接依然不能用。

问题于是从“这个请求头怎么算”变成了“这组身份从哪里来”。顺着 iid 往前查,会到设备注册接口;注册请求又带着六个安全头,其中 X-Medusa 包含请求摘要、设备信息和 SDK 的运行状态。整个项目就是沿着这条依赖关系逐步展开的。

这篇文章按分析时遇到问题的顺序写:先定位并还原直播握手的 X-Cylons 算法,再说明为什么需要注册,接着拆开注册 body 和六个安全头,最后回到 WebSocket 与弹幕解析。本文保留定位思路、数据关系和验证范围;关键计算公式、常量、完整实现及原始测试向量暂不公开。

最终脚本的执行方向,与分析时回溯依赖的方向相反:

图片描述

本版首次注册观察到的 6 神分别是:

注册样本没有 X-Perseus;当前直播握手使用的是 X-Cylons。因此不能把不同请求上观察到的 X 头汇总后,机械地给所有接口都加一遍。

目前,这条流程所需的七个 X 字段已经接通:注册时的六个安全头,加上直播握手使用的 X-Cylons。其中,Medusa 的 mode 5 已完成内部 16 种摘要核心、摘要尾部状态和 4 种外层封装分支的还原。Python 脚本可以自己生成一套新的设备信息,算出注册所需的六个安全头,再用注册成功后服务器返回的设备 ID 和安装 ID 连接直播间。

这里的“七个”指这条流程用到的字段,不代表所有版本、所有签名模式都已还原。脚本中的设备与进程状态仍是模拟数据,真实 Android 后台采集和更新这些信息的流程尚未完整复现;mode 7、Perseus 的剩余部分也不计入上述完成范围。X-Cylons 已验证的取时范围在后文单独说明。

抓包定位:

图片描述

抓包中直播连接的路径是:

从 Java 层找同名字符串不一定能直接找到计算代码。这个头是在底层网络回调中加入握手的,因此我先沿着“最终请求头在哪里被添加”往回追。

在这份 libsscronet.so 中,建立连接时会通过全局函数指针调用 MetaSec 的回调,传入 URL 和已有请求头。回调返回后,Cronet 再将新头加入握手。

这把“网络层添加请求头的位置”和“安全库计算结果的位置”连了起来。返回缓冲区也有相应的释放约定,主动调用做对照时需要遵守。精确偏移与调用参数细节暂不展开。

这一步得到的是一个原生对照入口:给定 URL,能够取得当前进程计算的结果。它可以帮助定位算法,却还不是独立实现,因为 SDK 状态仍然来自手机进程。

接下来需要确定回调究竟用了什么。我让 AI 辅助整理重放与对照实验,逐项检查哪些输入变化会影响计算结果。直接连续请求时,URL 和时间一起变化,很容易把时间变化误认成随机数。为此我固定原生 CLOCK_REALTIME,采用 A/B/A 对照:先计算输入 A,再只改一项得到 B,最后还原 A。

已保存的同进程对照结果如下:

另外,已测试的 ws://、https:// 输入没有产生 X-Cylons。也就是说,scheme 参与选择处理分支;进入这个 WSS 分支以后,已确认参与计算的是原始 query 和时间。

这里的“原始 query”很关键:它是问号之后、fragment 之前的字节串,不先 URL decode,不排序,也不经过字典重建。a=hello+world 与 a=hello%20world 在业务含义上接近,参与摘要的字节却不同。

这个版本的 X-Cylons 固定为 24 个标准 Base64 字符,解码后是 18 字节。不过,Base64 解码只去掉了最外面一层编码,得到的字节还经过内部变换,不能直接当成原始数据来看。

结合前面的固定时间和单变量对照,可以把内部数据大致分成三部分:

这些数据组合后,还会经过校验和字节混合,最后才编码成 X-Cylons。本文先介绍这层结构,具体字节位置、固定常量、摘要截取规则和混合公式暂不展开,也不放出完整生成代码。

分析时有一个容易误判的现象:只改 query 中的一个字符,最终解码出来的很多字节都会变化。这并不说明每个字节都直接保存了 query 的信息;后续的混合操作也会让变化扩散到其他位置。因此,要结合固定输入、逐项修改和原生执行结果来判断,不能只比较最终结果的外观。

还原后的实现已与保存的 154 组受控原生 URL/时间测试数据核对一致。这些测试把时间固定在整秒,也就是纳秒部分设为零。真实运行时还观察到不足一秒内结果发生变化的情况,这部分取时细节尚未完全还原;当前脚本采用的是已验证的整秒计算方案。

拿到本地算法后,一个自然的想法是:既然签名可以重新生成,直接生成任意 device_id、iid,再重签一次是否就够了?保存的实验表明,至少对当时的握手样本,这样做不成立。

这个实验必须在每次改 URL 后重新计算 X-Cylons。如果只改 iid,却沿用原来的签名,那么失败同时混入了“身份变化”和“摘要不匹配”两个因素,不能据此判断 iid 的用途。

这里的 415、417 是响应头里的业务握手状态,并不是这几次响应的 HTTP 状态码。

这组结果支持的结论是:iid 不必等于计算签名的那台手机当时的 iid,但需要服务器认可的注册身份;完整 query 还有其他约束。实验没有暴露服务端判定代码,因此不能把某一个失败码解释成某个字段的唯一错误原因,也不能从中推出所有直播参数的最小必填集合。

于是问题转向注册接口:

注册响应中的 device_id 进入直播 URL 的同名字段,install_id 进入直播 URL 的 iid。这就是直播连接对注册流程的直接依赖。

JADX 中可追到这条调用链:

LJI() 是触发入口,并不直接同步返回最终 HTTP 请求。清除数据、启动应用、同意隐私协议能够稳定触发首次注册;已有注册状态时主动调用同一个入口,也可能进入另一种签名模式。后面会看到,决定 mode 5/7 的是 SDK 状态位,不能只看 URL。

注册明文的结构是:

上面只展示结构,省略了其他字段,不能作为完整注册请求直接发送。当前代码使用 UTF-8、紧凑 JSON,并固定构造顺序。

这些字段大致来自五类输入:

当前代码默认构造 header 49 项、注册 query 39 项;最初抓包截图里的真机样本是 header 51 项、query 40 项,包含额外采集结果和来源尚未定位的 md=0。两组数量对应不同的实际报文,不能把截图字段数当成代码每次输出的固定值。

同一条数据在不同位置还可能使用不同表示。例如 body 中的分辨率为高乘宽形式 H x W,query 中转换为 W*H。注册 URL 的空格使用 + 编码,当前直播 URL 则使用 %20。签名前必须先得到最终要发送的字节。

篇幅原因,这里不展示所有设备指纹信息。

抓包中的注册 body 是二进制。解开后可以恢复前面的 JSON,这一层与 X-Medusa 的 protobuf 是两套数据流。

顺着明文进入网络层的过程,可以确认它先经过压缩,再结合随机材料进行密钥派生和加密,最后加上协议需要的封装信息。这里先说明处理关系,不展开派生公式、固定前缀、密钥截取位置和最终字节布局。

对照时还有一个容易忽略的变量:gzip 本身也有头部信息。如果压缩库自动加入当前时间,那么即使 JSON 不变,中间字节也可能不同。因此需要分开检查序列化、压缩、加密和封装各阶段,不能把所有差异都归到加密算法上。

验证也不能停留在“解出了可读文本”。项目中还对照了各阶段的输入输出,并检查解密、解压后的内容是否与原始 JSON 一致。

X-SS-STUB 与最终 HTTP body 的摘要有关。这里参与计算的是已经完成加密封装、实际要发出的字节,而不是最开始的 JSON 明文。

后续安全头也会引用 body 的摘要。如果几个位置使用的不是同一份 body,即使各自算法算对了,同一条请求内部仍然对不上。

因此实际构造顺序是:先确定 JSON、完成压缩和加密,拿到最终 body,再生成与之绑定的安全头。具体摘要表示方式在本文中省略。

先把各个头依赖哪些信息分清楚,比一开始就猜算法名更有帮助:

这些头虽然常被统称为“签名”,内部操作却不同。编码、校验、摘要和加密需要分别辨认,同名头在不同版本的格式也不能直接互套。下面只展开分析中容易判断错的地方,不给出完整生成步骤。

这两个头需要放在同一次签名调用中对照。原生取时会受到 SDK 保存的校时状态影响,还存在回退路径,因此不能把抓包里的时间值简单理解为系统时钟的直接输出。

当前版本的 Argus 是短格式。看到这个字段名就套用其他版本的大型加密 protobuf 实现,会从一开始走错方向。这里保留它与时间的关联,具体编码方式不展开。

URL 的 ts、设备采样时间和签名时间来自不同读取位置,不要求数值机械相等。当前代码尚未复现完整的服务端校时更新过程。

这一段有初始化状态表、更新索引、取出字节流等操作,外观上很像常见的流密码。但逐条对照写表指令后,可以看到关键更新行为与标准实现不同,直接换成现成库会得到另一套结果。

后续字节变换还有顺序依赖:某些位置读取的是前面已经写回的值。因此,将循环改成同时处理全部位置,也可能改变结果。

这里的收获是先确认每次读写的真实语义,再给算法命名。输入排列、状态表更新细节、位变换顺序和输出前缀暂不公开。部分输入还与运行时对象有关,不能长期沿用一条抓包里的固定值。

Helios 的输入涉及应用身份、时间和随机材料。继续往下追,会遇到摘要、密钥准备和分组运算。

其中一个容易忽略的问题,是二进制摘要与摘要的文本表示并不是同一份字节。只确认“这里用到了某种摘要算法”还不够,还要核对交给下一步的是哪一种表示。

分组结构也需要逐层确认,包括密钥扩展、轮运算和分组之间是否存在依赖。本文保留这些分析要点,省略明文拼接方式、派生规则、轮数、旋转参数和最终封装公式。

Ladon 不只作为独立请求头出现,还会影响 Medusa 内的 fl、lp。如果分别生成这些位置,就可能让一条请求携带互相矛盾的状态。

继续追上游,可以看到请求内容、SDK 启动状态、时间和请求上下文都参与了计算。部分状态保存在 TLS 中;这里指的是 thread-local storage,线程局部存储,与 WebSocket 外层的 TLS 加密传输不是一回事。

代码为每次请求建立状态快照,让 Ladon 与 Medusa 从同一份快照取值,避免并发时互相串用。本文不展开校验多项式、种子生成、地址混合以及各字段之间的换算公式。

还原这一部分时,整数位宽也是验证重点。在错误的位置提前截断,会让表面相同的公式产生不同结果。除此之外,两处看起来都叫“启动时间”的数据,也可能来自两次独立读取。

最初容易混淆的地方,是认为“请求路径是 device_register,所以一定使用 mode 5”。实际观察中,同一注册入口主动触发以后,也出现过 mode 7。

顺着签名对象的构造往前追,可以看到模式由 SDK 的配置状态决定,上游还涉及 Java 配置缓存和持久化配置。主动调用注册方法只保证请求发生,不保证签名对象重新以某个模式创建。

对对象工厂做了 24 组原生对照后,才把两种模式与配置变化对应起来。具体状态位、构造入口和抓包识别公式在这里省略。分析时应先确认当前样本实际走了哪种模式,再讨论它的算法。

追 Medusa 输入时,曾遇到一组解密和 protobuf 处理代码。它看起来也像在还原“Medusa 明文”,实际处理的却是应用 license。

这条链得到的是应用身份、应用配置和签名所需材料。它发生在取得新设备 ID 之前,因此不能把 Medusa 的应用密钥误认为由注册响应中的 DID/IID 推导而来。

本文只保留这一来源关系,省略 license 原文、内部字段布局、多层解包顺序和密钥派生规则。项目中还原的是已定位的解包流程,不等于原生 license 的全部有效性检查;部分字段的业务含义仍未确认。

Medusa 解开后是 protobuf。为了阅读方便,可以把它展开成 JSON,但 JSON 只是展示格式,不是原生算法直接接收的明文字符串。

分析中需要同时区分三份数据:注册接口的 JSON、Medusa 内部的设备与环境消息,以及计算请求摘要时使用的输入。它们互相有关联,却不是同一份内容。

恢复出可解析的 protobuf 只是第一步。还需要使用同一份输入和状态重新封装,与原始请求头逐字节核对,才能证明解包过程没有漏掉某层处理。完整样本和中间字节串不在本文展开。

Medusa 中有一个字段保存请求摘要。最初仅从长度猜测某个常见哈希算法,但与原生输出比对后无法解释样本,说明长度只能提供线索,不能作为算法结论。

最后通过返回值与目标字段的完整匹配,定位到真正的计算入口。再把入口数据与同一次 Charles 请求对齐,确认输入同时涉及原始 query、最终密文 body 和本次签名时间。

这里省略具体摘要算法组合、字节长度、拼接顺序和调用参数布局。需要保留的方法是:必须对齐同一次请求,不能把不同采样时刻的 URL、body 和时间凑在一起。

最初通过固定输入、枚举分支,可以找到与抓包一致的结果。但这只回答了“这一支能否算对”,还没有回答“调用方为什么选它”。

继续追上游,才能确认选择值由输入经过运算得到。摘要核心的选择与外层封装的选择是两件事,不能拿一个阶段的条件去代替另一个阶段。

目前 mode 5 的选择规则已经纳入独立实现。本文不列出种子、移位方式、取值规则和可复算的输入输出样例。

不同核心存在各自的常量、表和运算组合,不能用某个标准摘要库统一替换。分析经历了三个层次:

第三步最容易误判:一条 trace 可以展开成代码,不代表换输入、换时间后仍然正确。如果把采样现场里的某个中间值误当成常量,其他测试就会失败。因此还要主动改变条件,回到原 ARM64 结果核对。

保存的核心验证包括 48 组手机原生向量和 1024 组原 ARM64 向量,十六个分支都有覆盖。正式生成路径已经不需要调用手机或执行 SO;完整核心、常量表和提取代码暂不随文公开。

只还原摘要核心,仍然不能解释完整字段。尾部还与检测结果、JNI 相关状态及 SDK 保存的状态有关。

改变这些状态,有时不仅改变一个标志,还会影响参与校验的数据范围。因此不能把尾部当成一个固定后缀,也不能认为 URL、body 和时间相同就一定得到相同结果。

这部分来源已分别追到 Java/JNI 调用、加载器环境和 SDK 状态读写。状态缺省值与后续事件写入的值,也需要区分,不能把另一模式才发生的更新当成首次签名必经流程。

完整摘要有 92 组受控原生向量对照。具体检测条件、环境标记、缓存值、校验公式和标志位位置暂不展开。

摘要计算完以后,还要把应用、设备和 SDK 状态组织成 protobuf,再执行外层封装。下面只展示各层的关系:


[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

收藏
点赞 ・118
打赏
分享
最新回复 (71)
雪    币: 1416
活跃值: (1965)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2 楼
1
2026-10-7 21:56
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3 楼
感谢分享
2026-10-7 22:28
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4 楼
学习
2026-10-8 09:20
0
雪    币: 1901
活跃值: (6053)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5 楼
111
2026-10-8 09:55
0
雪    币: 86
活跃值: (1642)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6 楼
感谢分享
2026-10-8 10:00
0
雪    币: 5536
活跃值: (5874)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7 楼
66
2026-10-8 10:07
0
雪    币: 27
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8 楼
6666
2026-10-8 10:21
0
雪    币: 1956
活跃值: (4409)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9 楼
666
2026-10-8 10:22
0
雪    币: 83
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10 楼
6566
2026-10-8 10:23
0
雪    币: 1904
活跃值: (9484)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
11 楼
感谢分享!
2026-10-8 10:39
0
雪    币: 178
活跃值: (3986)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
12 楼
666
2026-10-8 10:44
0
雪    币: 1062
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13 楼
66
2026-10-8 11:07
0
雪    币: 114
活跃值: (910)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14 楼
66
2026-10-8 11:52
0
雪    币: 90
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15 楼
感谢分享
2026-10-8 14:18
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16 楼
谢谢分享
2026-10-8 14:21
0
雪    币: 28
活跃值: (1922)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17 楼
感谢分享
2026-10-8 14:50
0
雪    币: 240
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18 楼
666
2026-10-8 15:36
0
雪    币: 1474
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19 楼
666
2026-10-8 15:44
0
雪    币: 110
活跃值: (9656)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20 楼
666
2026-10-8 16:10
0
雪    币: 230
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21 楼
没有遇到Google的Android KeyStore(TEE) 硬件证明链问题吗,直接提取真机的序列号用的?
2026-10-8 16:40
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22 楼
666
2026-10-9 02:19
0
雪    币: 223
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23 楼
多谢大佬分享
2026-10-9 08:55
0
雪    币: 548
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24 楼
66
2026-10-9 09:12
0
雪    币: 153
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25 楼
666
2026-10-9 11:24
0
游客
登录 | 注册 方可回帖
返回