背景
目前还没研究具体的协议实现,只实现了二进制还原JSON的过程。
因为 pure-live
项目,开始对企鹅电竞进行分析。直播流的获取还是比较简单的,参考 real-url
项目,发几个HTTP请求就拿到了。难的是 websocket
协议的破解。
看了 real-url
的实现是模拟了 js
的过程,但是模拟的过程非常抽象,无法理解其宏观实现。本来也打算跟着写下来,但是 python
和 golang
的太多地方不同了,python
随便返回,对象里嵌套对象。但是 golang
却无法这么轻松的做到,模仿实现实在是困难。
既然无法模仿 python
代码,但是其又来自于网页的 js
代码。不如直接找 js
代码,直接用 goja
在 golang
里跑这段 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
的复用。
完整代码(把 js
的 decodePacket
改成了 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
感兴趣了,有空观摩一下源码,说不定自己也能搞个小脚本语言地引擎~。