某视频网站wasm简要分析
本帖最后由 我是不会改名的 于 2023-9-22 14:10 编辑## 某视频网站wasm简要分析
## 前言
很久之前写的了,应该没怎么改。
本文仅供学习交流使用,请勿随意传播,更不要和某些人一样四处倒卖。
如有侵犯你的权益及时联系我删除
托管平台:aHR0cHM6Ly93d3cucG9seXYubmV0Lw==
图片挂了的话就下https://lanzouy.com/iST3y19eym2d,所有图片代码基本都有
## 一、1104
### 扣wasm,补代码
和之前相比js层几乎没怎么变,因为用到的是
worker,直接对js下断点不会停,所以需要修改加上的断点,或者对wasm里面的_malloc下断点,js层核心代码就下面,简单看下
```
function(f, v, g, m) {
var y = this;
if (this.isWorker) {
var t = function() {
debugger//修改代码,方便调试
var t = y.Module
, e = 2 * y.config.minSeekHole//实际就是seed_const,前面先除了2,这里乘上了
, i = Object(E.a)(y.config.playsafe.split("-").pop())//解码token,获取用户id之类的参数
, n = new Uint8Array(function(t) {
var e = []
, i = 0;
for (t = encodeURI(t); i < t.length; ) {
var n = t.charCodeAt(i++);
37 === n ? (e.push(parseInt(t.substr(i, 2), 16)),
i += 2) : e.push(n)
}
return e
}(i))
, r = Object(b.a)(n, t)//_malloc,开辟内存空间,存放解码后的token
, a = n.byteLength
, o = new Uint8Array(v)
, s = Object(b.a)(o, t)//存放加密的key
, l = o.byteLength
, c = new Uint8Array(f)
, u = c.byteLength
, d = Object(b.a)(c, t)//存放加密的ts
, h = Object(b.a)(new Uint8Array(g), t);//存放iv
y.Module["" + i] = a;
var p = t.ccall("get", "number", ["number", "number", "number", "number", "number", "number", "number", "number"], )//核心代码
, A = y.Module.HEAPU8.subarray(p, p + u);//获取解密后的ts内容
Object(b.b)(d, y.Module),
Object(b.b)(h, y.Module),
Object(b.b)(s, t),
Object(b.b)(r, t),//释放内存空间
m(A)
};
e.a = function(t) {
var e, i, n, r;
for (t = t.substr(1),
e = "",
i = 0; i < t.length; i++)
n = t.charAt(i),
e += -1 == (r = "lpmkenjibhuvgycftxdrzsoawq0126783459".indexOf(n)) ? n : "abcdofghijklnmepqrstuvwxyz0123456789".charAt(r);
return e
}
```
在这里,就将大部分参数存放进去了,然后就进入了wasm内部,对于1104来说扣代码的话内部操作并不重要,直接扣代码就行
先找到加载wasm,一般如果有wasm文件的url的话直接搜链接就行了,没有的话,搜索data:application/octet-stream;base64,AG
因为wasm加载有两种方式,各有各的特点,为了避免跨域或者减去设置响应头的这一步,一般会采取不同方式
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/2.png)
然后吧对应的部分全部扣下来,很明显这直接就是emcc 编译的结果,几乎没有任何修改,emcc 编译出来的js本身就是支持nodejs的,所以几乎不用补任何代码,去头去尾,然后运行一下试试
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/3.png)
需要注意一下,加载wasm的过程是一个异步函数,所以需要等待加载完成后再运行相关代码
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/5.png)
然后根据网站前面的代码,写一个调用wasm函数的js,我这里直接把ts内容设置为空测试
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/6.png)
一般来说,所有的结果都在内存中,所以直接在内存中获取就行了,所以首先要知道key是多少
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/7.png)
好了,知道key是多少了,先把内存下载下来
```
blob = new Blob(, {type: 'application/octet-stream'});
objectUrl = URL.createObjectURL(blob)
```
随便找个记事本搜索一下,很快啊,一下就注意到了
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/8.png)
然后摸出计算器,发现返回的地址比key的地址大304,直接修改代码,从内存中获取key,`A = Module.HEAPU8.subarray(p - 304, p - 304 + 16)`,然后运行一下,很好,不出意外的报错了
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/9.png)
尝试补了一下,但还是报错,然后更离谱的事情就来了,使用pacharm调试,把异常点打开调试
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/10.png)
可以看到,这个环境检测是从wasm内部,调用了js里面的eval函数,‘ ? 1 : 0’ 这字符串可以在wasm中找到
因为wasm里面的内容都是存放在内存中的,所以要转换成js的字符串一般都在js里面运行,所以直接修改js就可以了,修改以后再运行
```
function _emscripten_run_script_int(ptr) {
const str = UTF8ToString(ptr)
if (str.indexOf('location') !== -1) {
return 1
}
return 0 | eval(str)
}
```
很好,不出意外的又报错了,同样的也是eval出的事,返回值都没有,只要能运行就行
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/11.png)
```
function _emscripten_run_script(ptr) {
eval('1')
}
```
再运行,很好成功跑起来了,和网页对比也一样,扣代码基本就结束了,很简单
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/12.png)
### wasm算法还原
先把,wasm文件下载下来,用[逍遥一仙的工具](https://www.52pojie.cn/thread-1438499-1-1.html)转换成o,供ida分析,jeb可以直接分析wasm文件,建议两个一起用
拖进ida,浏览器调用导出函数f,
```js
_get = Module._get = function() {
return (_get = Module._get = Module.asm.f).apply(null, arguments)
}
```
传入参数上面已经分析了,就不讲了,然后调用了$func7,注意到传入传出的都是相同的,那么把数据就是移了个位置,从第二个地址开始取32位,然后移动到第一个地址,这里移动的是解码后的token
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/14.png)
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/13.png)
然后就进入了$func49,传入的参数分别是32位key的地址,key长度,seed_const,上面token移动后的地址,token长度
这里面一开始调用了一个上面提到的eval,然后又调用$func7,换个坑,然后从内存中加载,注意到这里的地址很小,很可能就是常值,那么就可以直接尝试搜索一下
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/16.png)
很明显了就是常值,除此之外还发现了,前面扣代码报错的一些字符串,这就是emcc 编译的特点,利用ast解析了字符串和数组,然后分配一定空间存放,初始化的时候在写入内存。对于字符串可以直接搜索,对于数组就需要转换成c代码来分析
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/17.png)
比如下面这一段,就是AES的s盒,这些常值有利于我们去分析相关加密解密算法,因为很多网站都会魔改算法
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/18.png)
然后继续分析,获取了常值过后,又进入到了$func38,同样的就是移了个坑
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/19.png)
然后就是取值存值,就是移动了一下,把某些参数组合在一起
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/20.png)
然后就进入了$func13,传入的是前面提到的字符串加上了seed_const,结果是一个32位的字符串,根据摸鱼三大定理,之前的版本也对
seed_const进行了md5,这里很可能也是md5,验证一下,很好就是一个md5函数
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/24.png)
然后进入了$func24,传入的是上面md5加密的结果,和seed_const,这很明显就是一个仿射密码,和去年的Windows 中级题很像,
当seed_const为0的时候,abcd0123==>abcdJKLM,seed_const为1的时候abcd0123==>bcdeKLMN
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/25.png)
然后又是$func13,对上面解码后的token取md5,再调用了$func11,就是一个计算字符串长度的函数
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/26.png)
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/27.png)
然后进入了函数,传入了md5后结果的地址,md5长度,存放结果的地址,这里面的f9就是一个移动的函数,就是把md5后的结果重新排列对照写就行了,很简单
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/28.png)
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/29.png)
然后又进行了一次md5,对上面获取到的两次md5结果,再加上字符串进行md5
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/30.png)
md5过后截取了中间16位,然后调用了$func21 ,同样根据摸鱼三大定理这里就是设置key,那么下一个函数$func20就是解密了,
ida里面可以很清楚的看见,这就是aes,iv呢是固定的,直接看传入参数就行了
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/31.png)
几个参数分别是key长度,iv地址,key地址,以及存放解密后的内容的地址
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/32.png)
然后又调用了$func22,截取了前16位,并重新排列,和前面还原后的js对照,很好一样的,
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/33.png)
解密key到此基本结束了,wasm里面还调用了一次aes解密ts,然后返回了解密后的内容的地址
根据上面思路简单还原一下就行了
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/34.png)
## 二、v13key和文件头解密部分
### 扣wasm
v13把wasm部分的js,单独放在了一个文件lib_player.js,直接复制下来就行,都不用改
相对于1104呢,v13多了两个回调函数用于存储音频和帧信息,必须加载上去才能正常运行
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/35.png)
除此之外呢,ts内容不能置空,因为它内部封装了ffmpeg的很多函数,虽然它先解密了key但是如果后续处理错误,会自动释放对应key的内存,所以需要传入ts或者,修改wat代码,在处理ts前就返回key的地址,但是啊,接近30mb的wat代码,修改起来了太复杂了,而且也不一定能成功转换回去
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/36.png)
然后就是调用部分,多了一个参数,ts分片的id和版本号,我这个就是18,其余基本都一样
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/37.png)
然后运行,缺啥补啥,基本没有什么需要补的,成功运行了,就需要找个key存放的地址就可以直接拿key了
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/38.png)
好了,拿到key了,同样的找到了key位置,结果这次发现每都是一样的,直接固定就行了。
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/39.png)
然后解密一个分片,很好只有声音
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/40.png)
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/41.png)
然后js里面还调用了其他函数提取视频帧在利用WEBGL(?或者类似的) 渲染YUV成图片,这些就太专业了,看不懂,就没仔细研究了。
### 算法还原
同样的拖进ida,分析,然后三千多个函数
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/42.png)
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/43.png)
没办法,只有慢慢分析了,建议先把1104过一遍,再来看V13,同时把ffmpeg的源码下载下来对照,因为wasm里面封装了很多ffmpeg的函数https://ffmpeg.org/download.html(我这里没用到,但分析后面的ts需要用)
然后呢建议下个Chrome canary 版本,我试了好多浏览器,只有这一个勉强能加载全部的wat代码,其他的要么还没加载出来就崩了,要么后面一大串都是空的。
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/44.png)
废话说完了,正式看看wasm,先进去_sendData,一开头基本没啥用,就是初始化一些东西,然后就进入到了函数f2523
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/45.png)
然后进去,和1104对照特征很明显,先调用了2524解密key,在调用2672(aes解密部分)解密ts,但V13多了一步目前不知道有什么用
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/46.png)
然后进去,同样和1104对照,发现基本一样,只是salt以及最后一次md5组合顺序,截取的16位key区间不一样而已
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/47.png)
没什么难度的直接改下就行了
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/48.png)
然后就看下多了的哪个函数,a7就是比1104多了的ts序号,然后传入了函数2525,几个参数分别是ts长度,ts地址,seed_const,版本号
```
if ( a7 > -1 )
w2c_f2525(v14, a3, a7, a8);
```
进去后,先从内存中加载常量字符串,然后调用了函数f2526
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/50.png)
就是md5上面已经提到了,然后截取了16位,多么熟悉的操作,紧接着就是2670设置key,然后通过运算得到需要解密文件头大小
```
((id % 5 + 1) * 1024) | 16
```
然后再解密key就是上面提到的,iv是固定的bytes()
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/51.png)
然后调用memcpy拷贝内存,这里要注意一下,解密长度v47 | 0x10,和16或为了填充到16倍数,但拷贝的时候是不需要填充的这一部分的
```
w2c_f2672(v40 + 200, v47 | 0x10, v26, __SPAIR64__(v36, a1));
w2c__memcpy(a1, v36, v47);
```
然后再还原v47-v47+16的数据,就是一个简单异或,然后再把v47 | 0x10后面的数据还原,就是把后面的数据往前移16位
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/52.png)
然后返回了解密后的ts文件地址,再把内存拷贝过去,这个就和v12js层里面的`m = r.Module.HEAPU8.subarray(g, g + p - p % 188);`一样
!( https://s3.ananas.chaoxing.com/sv-w8/doc/7f/3a/df/3afab18d742d7086fb76689bf1e538e9/thumb/53.png)
后面的涉及视频的处理了,不是很懂没搞定
## 后记
总的来说还是比较麻烦,尤其对于js逆向来说,因为涉及到了伪c代码,但不是很难,如果有相关基础那就很简单,直接上ida,用插件找到加密函数关键字,秒定位。
目前js逆向越来越难了,jsvmp和wasm越来越普遍,wasm也越来越难,从最简单的md5,到某些网站的aes白盒应有尽有。
!(https://attach.52pojie.cn//forum/202309/22/140944k55s2z3bh4tsxrxz.png?l) 本帖最后由 涛之雨 于 2023-9-22 14:29 编辑
windows逆向:①(除了没混淆的.net)肝二进制
② 发现是python打包-解包-发现是pyd-肝二进制
③ 发现是electron-解包-发现打包成bytecode了-肝二进制。。。甚至都没有多少教程
安卓逆向 ① as开发:java层分析-发现是so-肝二进制
② unity开发:分析.net(dll)
③ web打包:js
苹果(没设备)
网页:分析js-发现是wasm-肝二进制
总结:万物皆肝二进制 最好还是把图上传论坛,防止图床哪天挂了,最后一个图引用的地址不对,那个应该是一个临时的地址,还是通过discuz那种插入方式插入到markdown格式里。 发现个事,就发技术贴没几人免费评分,但凡发个成品,好家伙 现在这些视频网站都开始搞wasm加密了,真的有难度 的确不错的wasm文章分析,可惜还没看得懂 学习了,写的很详细感谢楼主分享web逆向教程。 你是真的猛啊,把wasm脱出来一个一个看啊 不得不说太强了,咱们还得多多努力 wasm以后的主流了