惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
博客园_首页
H
Help Net Security
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
B
Blog
I
InfoQ
SecWiki News
SecWiki News
T
Tailwind CSS Blog
Spread Privacy
Spread Privacy
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
N
Netflix TechBlog - Medium
P
Palo Alto Networks Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Vercel News
Vercel News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
K
Kaspersky official blog
M
MIT News - Artificial intelligence
S
Schneier on Security
T
Threat Research - Cisco Blogs
F
Fortinet All Blogs
Cyberwarzone
Cyberwarzone
Scott Helme
Scott Helme
aimingoo的专栏
aimingoo的专栏
Martin Fowler
Martin Fowler
MyScale Blog
MyScale Blog
The Cloudflare Blog
Recent Announcements
Recent Announcements
Security Latest
Security Latest
G
GRAHAM CLULEY
IT之家
IT之家
Y
Y Combinator Blog
The Last Watchdog
The Last Watchdog
腾讯CDC
Google DeepMind News
Google DeepMind News
V
V2EX
S
Securelist
TaoSecurity Blog
TaoSecurity Blog
B
Blog RSS Feed
S
SegmentFault 最新的问题
博客园 - 叶小钗
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
Project Zero
Project Zero
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
F
Full Disclosure

iyear - 记录本是反抗 - iyear

LFX Mentorship: 与 KubeVela 社区在开源相遇 - iyear OSPP2022 + DevStream社区 复盘 - iyear 《图解TCP/IP》读后感 - iyear - 记录本是反抗 Redis · 为什么哈希槽数为16384? - iyear 分布式 · 一致性哈希 - iyear 3200元32G1TSSD开发机DIY选型经历 - iyear - 记录本是反抗 2021 - iyear - 记录本是反抗 在N1小钢炮上搭建私有音乐库koel - iyear - 记录本是反抗 尝试理解前后端分离 - iyear - 记录本是反抗
企鹅电竞WebSocket弹幕协议浅析 - iyear - 记录本是反抗
博主: iyear · 2022-01-18 · via iyear - 记录本是反抗 - iyear

背景

目前还没研究具体的协议实现,只实现了二进制还原JSON的过程。

因为 pure-live 项目,开始对企鹅电竞进行分析。直播流的获取还是比较简单的,参考 real-url 项目,发几个HTTP请求就拿到了。难的是 websocket 协议的破解。

看了 real-url 的实现是模拟了 js 的过程,但是模拟的过程非常抽象,无法理解其宏观实现。本来也打算跟着写下来,但是 pythongolang 的太多地方不同了,python 随便返回,对象里嵌套对象。但是 golang 却无法这么轻松的做到,模仿实现实在是困难。

既然无法模仿 python 代码,但是其又来自于网页的 js 代码。不如直接找 js 代码,直接用 gojagolang 里跑这段 js 不就行了?

这段未完成的模拟实现代码放到了 gist ,等待大佬完成它,效率肯定更高。

https://gist.github.com/iyear/b580a87c1a76c564c77783e02b98a364

分析

real-url 项目也没有说 js 的位置,所以我们自己抓抓看。

由于并不会 js 逆向,只能用一些土办法找到相关代码。

随便点开一个直播间,F12,Search。

根据 python 代码,就用 event_id 作为关键词搜索整个网页。结果就三个。依次查看上下逻辑,找到最像 python 实现的代码。

发现 https://static.egame.qq.com/pgg_pcweb/v2/7873f0f1dec453d99a98.js

复制到 webstorm 中,查看上下导出模块,发现应该逻辑都在 725 这个 id 中,把 725 的复制出来。

发现

这里和 python 代码非常像了,大概率就是了。去除与t赋值相关的模块导出代码,把 decodePacket 写成单独的函数,然后把整个实现逻辑单独拎出来。

先去掉那些 encode 的代码,把代码内含 encode set 等字样的代码都去掉。

写个 hex2ArrayBuffer 函数,把抓到的 websocket 包以十六进制字符串扔进去看看能解析出啥东西。 为了还原运行环境,所以先放到浏览器运行。

完美解析,但是浏览器提供的库比本地多得多,那么再在 webstorm 中运行这段代码,果然报错了。

ReferenceError: e is not defined

开始分析

b = "undefined" != typeof window ? window : void 0 !== e ? e : "undefined" != typeof self ? self : {}

发现这段代码似乎可以直接去除,直接给 b 空对象即可。去除,再次运行,报了另一个错。

Error: TextDecoder is required

跳转到代码处,查看文档,发现 TextDecoder 并非 js 原生提供的库,而是浏览器提供的。但是把整个 TextDecoder 的代码再嵌入进去就太重了。如何解决 utf8 解码?

想了很久,想到一个做法,学 golang 的时候,都知道 golang 中的 string 都是 utf8 编码的。那我把 buffer 传给 golang 转成 string 再传回来不就可以了吗?

这点用 goja 可以方便地实现(感谢这么好,实现这么全的 js 引擎库)。把 ArrayBuffer 传进来,string(buf) 即可。

vm.Set("utf8Decode", vm.ToValue(func(call goja.FunctionCall) goja.Value {
    // 替代 js 中的 TextDecoder,在 golang 中,string 默认为 utf8 格式。直接 string() 强制转换即可实现 bytes -> utf8 string
    buf := call.Argument(0).Export().(goja.ArrayBuffer)
    return vm.ToValue(string(buf.Bytes()))
}))

于是,js 中直接调用 utf8Decode 就可以解码了。

把原来代码中 I() 的实现替换成 utf8Decode

因为有外部函数,直接放到 goja 里跑

完美解析。

再稍微封装和完善一下整个 Decode 过程。

js 代码放在外部文件 decode.js 中,用 embed 引进 golang

由于 goja.Runtime 是非并发安全的,但是每次解析都 New 一个太浪费时间和内存了,就用 sync.Pool 实现 Runtime 的复用。

完整代码(把 jsdecodePacket 改成了 decode):

其实最后代码很少,但是实现的过程其实走了很多弯路,单单模拟实现就耗费了许多时间,结果最后还没用上。后来的 js 解决错误又花了很多时间(不太熟悉 js),最后才想到用 golang 解决 utf8 问题。不过最后解析成功还是很有成就感的,

Benchmark

func BenchmarkDecode(b *testing.B) {
    data, _ := hex.DecodeString("000001010012000100030000000000000000000000eb0625313634323530343138343937375f3133323231343738345f392e3231392e31312e32335f31100129000106ae023072d314161c5f722d4c43376978484142686333464f3264554641507a624141426826096979656172313030393609363636e59388e59388430000017e6ce0e1a65c6c780009060a6c6f74746572795f6964160006026c7616013006056c766e6577160130060973686f775f74797065160130060762775f7765617216027b7d060863616e5f636f70791601310604636f7079160130060963616e5f7265706c7916013106026d641600811000380c4261e69ff85261e6a002690c7c")
    for i := 0; i < b.N; i++ {
        if _, err := Decode(data); err != nil {
            b.Error(err)
            return
        }
    }
}
goos: windows
goarch: amd64
cpu: AMD Ryzen 7 5800H with Radeon Graphics         
BenchmarkDecode
BenchmarkDecode-16            1275        826234 ns/op
PASS

速度还是不错的,相对于网络传输已经可以忽略不计了,基本上解析都可以在 1ms 内完成。

完整代码等我提交到 https://github.com/iyear/pure-live-core 即可查看。

越发地对 goja 感兴趣了,有空观摩一下源码,说不定自己也能搞个小脚本语言地引擎~。