Abema的m3u8 key格式为
#EXT-X-KEY:METHOD=AES-128,URI="abematv-license://2uqo9VkfUUGyqrRkeSrTTFsSucMVJ6kgecbu1Hr7ibLx",IV=0x1165956bdcbdd50f7ea3c555b3407d19
还没搞懂这个处理是怎么定义的,不过可以跟踪到。
Step.1 请求授权服务器
需要根据这个地址去授权服务器请求
https://license.abema.io/abematv-hls?t=SBX8JMLLbZ6EHhoBJuNSy6zEbz5vQ2zQ5yfPfcPnQgBthE8VGpNvbsmAAkrKEtMBwKzaGs3WbDZEtBpeSXww8akbjRxRKxDwwtyZBwaAth5mz2hVV6tJd4puHqB
这里后面的一长串是localStorage里的abm_mediaToken
payload为
{ kg: 315 kv: "wd" lt: "2uqo9VkfUUGyqrRkeSrTTFsSucMVJ6kgecbu1Hr7ibLx" }
lt为key url里abematv-license://
后面的部分。
kv的”wd”是固定值。
然而kg是什么呢?
来分析一下代码
这显然是一个XHR请求,这里的_0x5890a6就是kg,那么它是在哪里定义的呢
这里从某个地方取到了一段JSON,这段JSON有一个字段叫generation就是kg的值。所以这个_0x3b0c()函数就是关键。
这个函数在几乎最前面就被定义了,而且经常被调用。
可以猜测,这个函数首先是在没有初始化的时候进行一段初始化操作。从_0xb0c3中取出一些值进行处理。
_0xb0c3是一个数组,定义好的,不会变。我把它重命名为globalVars。
这里的初始化操作很复杂。。先不分析,把那个globalVars打出来看看:
一看就是一堆base64,解之
一眼看到了我们要的东西。一段JSON。我们发现在取这个gerneration的时候,key是0x35,然而初始状态这个JSON并不是在0x35。所以我们有理由相信这个所谓的初始化操作可能是类似于打乱之类的操作。
基于以上分析……我们可以暂时认为kg是一个常数315.
Step.2 解析授权服务器返回信息
按以上信息发送请求,授权服务器返回内容为
{cid: "386-71_s2_p1", k: "GPSpC5yhFWUqosnVvKaniy5"}
cid是视频url中就有的,应该是标识视频的。k应该就是重点了。
往上看了一眼就发现了这个醒目的错误提示,显然这一段就是处理返回的内容。(上面一大段看起来就是XHR的回调,状态码判断什么的)
箭头指向的应该就是重要的,解密函数。单步跟入。
继续跟入。
key被分为最后一个字符和其他。
根据我的直觉。。这个函数应该是判断相等。
那么应该是根据key的最后一个字符来确定解密模式。
后面的_0x3031b5的三个参数分别是cid
、localStorage.abm_userId
、key去掉最后一个字符,继续跟入.
后面的JSON是和全局数组里那个一样的。里面有个固定的key,取出来。
到这里我后面分析了很久,但是密码学部分还是太复杂了,决定还是换个思路,抄代码。只要能模拟运行就可以解密了反正。
==================无奈的分割线===============
经过长期的奋战,我还是没能把这东西弄出来。
原因主要是这玩意不仅混淆的十分恶心,还有反调试,还有一套奇妙的基于cookie的函数调用方法,实在是模拟不出来,最后我又换了种思路:前端劫持。
JavaScript新特性里有一个好东西叫Proxy,可以给Object的get、set等操作搞一个trap。
经过研究代码,我发现Abema依旧是把XHR封装了一层。把原来的XHR放到了XMLHttpRequest.protorype.o
然后在外面加了一层转发,并且加了一些属性。
Key通过解密函数解出来之后会存到外层!于是我想能不能劫持XHR试试。
这个真的也调了挺久的。
一开始我直接把XMLHttpRequest
覆盖成一个Proxy发现不行不起作用,后来试着把它放到了window.onload
里面去就行了(这里原理未知
随后我本来用handler.set
来截获set属性,然后直接obj[prop] = value
这样来执行原来的复制操作结果发现各种报错(原理还是未知)(2018年11月1日更新:原因知道了,大概是XHR被修改之后还需要一段时间请求才完成,等几秒就好了)
于是换成了Reflect
,最后得到代码如下
window.onload = function(){ const maps = {}; XMLHttpRequest.prototype = new Proxy(XMLHttpRequest.prototype, { set: function(obj, prop, value) { if (prop === 'proxy' && maps.proxy) { console.log(Object.values(JSON.parse(JSON.stringify(new Uint8Array(maps.proxy.response)))).map(i => i.toString(16).length === 1 ? '0' + i.toString(16) : i.toString(16)).join('')) } maps[prop] = value; return Reflect.set(...arguments); } }); }
弄出这个玩意简直花了我无数的时间,希望abema慢点改吧(((
追记于2019年4月27日
Abema更新之后,不再把修改过的XHR暴露,此外我也找不到任何从外部可以访问的接口,无奈之下只好采取最终手段。
劫持内建函数。
由于加解密绕不开JavaScript的TypedArray,于是我选择劫持Uint8Array。
直接劫持了 Uint8Array 的构建函数,筛选所有经过这个函数的 bytes,筛出 16bytes 的就是 key 了。
其实不是很想这么做,只是想把这种方法作为最终手段,只是实在没找到其他方法,只好大招了。
有解法了
https://egg.5ch.net/test/read.cgi/streaming/1524231434/272
搞出key很简单,我的目标是弄出从abema-license到key的算法或者模拟运行。