某网址视频下载解密流程分析及脚本实现
前言
由于个人原因,需要下载某视频网站的某个视频,本以为右键能直接看到mp4的链接,然后事实并没有那么简单,然后就有了后面完整的分析流程及脚本实现
分析视频链接
第一步自然是先看看这个视频到底是个啥玩意儿。f12查看源代码内容如下:
访问那个src链接以后居然是一个404的页面
于是此路不通,换另一条思路,查看该视频页面访问时的流量,发下如下链接:
m3u8文件即视频流媒体文件,下载查看之,可以看到一些关键信息(m3u8文件介绍请点击此处查看),包含n个链接及对应的加密算法和相关加密所需信息(iv及key):
继续查找流量信息,发现/v1/tokenVideoKey的访问记录:
其response内容可以看到一个encryptedVideoKey:
request中的三个参数,videoKeyId存在于m3u8文件中,playerId应该是个固定值,token参数则在流量中找到了另一个获取的链接:
该获取token的链接request参数如下图:
其中f为m3u8的链接,m为上一个链接中的videoKeyId,e和_为时间戳,k应该也是个固定值。
仿佛已经看到了希望,似乎只要下载m3u8中的ts文件然后用这个key进行解密就可以了,而key又可以用一个token去获取,token可以用一些已知的参数去获取,在burp中尝试发包,成功获取token和key:
然而事情并没有这么简单,我尝试下载一个ts文件,ts文件的下载链接就是那个m3u8的链接拼接m3u8文件中的ts文件名,具体如图:
然后调用openssl进行解密,解密失败:
!
我尝试了其他cfb,ecb等,都以解密失败告终。然后我百度查了其他人对m3u8文件的解密,发现流程都是直接下载文件,找到密钥,然后调用py或者openssl的解密算法进行解密的,唯独在52破解上找到一个不一样的解密形式,链接请点击此处查看,但是原帖中没有具体解密形式,只是说密钥需要算出来。于是继续回到原来的前端中。
分析解密密钥获取
由于视频解密必定是在前端js中获取的,所以可知js中必定存在相关参数,用来存储key作为解密参数,于是在sources中,查找encryptedVideoKey参数,找到以下内容,根据查找结果看,应该是同一段代码,打开其中一个,格式化查看:
虽然代码有混淆,但是可读性很高,关键代码如下
if ("media-drm-token" === e.playlist.keyFormat || "media-drm-player-binding" === e.playlist.keyFormat) {
for (var o = JSON.parse(r), f = o.encryptedVideoKey, c = u["default"].utils.utf8.toBytes("72Fhskjglp8qjpqx"), h = new u["default"].ModeOfOperation.ecb(c), p = [], m = 0; m < f.length; m += 2)
p.push(parseInt(f[m] + f[m + 1], 16));
var y = h.decrypt(p)
, _ = u["default"].utils.utf8.fromBytes(y)
, v = function(e) {
for (var t = new ArrayBuffer(e.length), i = new Uint8Array(t), n = 0, r = e.length; n < r; n++)
i[n] = e.charCodeAt(n);
return t
}(_);
l = s = new DataView(v)
}
分析函数的执行流程,代码中r参数即为获取密钥的response,f为encryptedVideoKey,代码中硬编码了一个字符串”72Fhskjglp8qjpqx”,后续ModeOfOperation.ecb利用该字符串进行了解密。
大概清楚以后,在for循环处下一个断点,然后刷新页面,成功停在断点处,进行单步调试查看传参和函数调用流程:
跟进ModeOfOperation.ecb函数,跳转到如下函数,可以很明显看到这个是个aes-ecb,传入的e参数即为之前的c:
继续跟进,回到主函数,可以得到h函数的密钥为c,即那串固定字符串,然后往下执行,for循环主体为一个为p赋值的过程,即将f参数转化成字符串,f参数即encryptedVideoKey,接着,调用h解密p参数,将得到的值赋值给y:
y转化成字符串后赋值给_,然后进入下一个函数,传入参数为_,到此,js将利用token获取的encryptVideoKey,以”72Fhskjglp8qjpqx”为解密密钥,使用aes-ecb方法进行解密。
推测y就是解密ts文件的密钥,利用y参数作为密钥,使用openssl,成功解密ts文件:
实现脚本
完成解密流程后,就可以用python实现自动下载解密拼接视频,达到最终目的,部分关键代码如下:
def get_token(key_url):
token_url = 'http://xxxxxx.com/v1/authex/bce/valid?f=' + m3u8_url + '&m=' + key_url.rsplit('=', 1)[1] + '&e=' + str(int(time.time())) + '&k=abc'
# print token_url
return json.loads(requests.get(token_url).content).get('token')
def get_key(key_url):
# print key_url
key_url_all = key_url+'&playerId='+get_pid()+'&token='+get_token(key_url)
return json.loads(requests.get(key_url_all).text).get('encryptedVideoKey')
def key_decrypt(key):
cryptor = AES.new('72Fhskjglp8qjpqx',AES.MODE_ECB)
return cryptor.decrypt(key.decode('hex'))
def file_decrypt_and_save(url, file_uri, key_path, iv):
file_url = url.rsplit("/", 1)[0] + "/" + file_uri # 拼出第二层m3u8的URL
request_content = requests.get(file_url)
# request_content.encoding = 'utf8'
file_content = request_content.content
# print (download_path_decode + '\\' + file_uri)
# print download_path_encode
with open(os.path.join(download_path_encode, file_uri), 'ab') as f:
f.write(file_content)
f.flush()
key = key_decrypt(get_key(key_path))
#print key
if len(key): # AES 解密
iv = hex(int(iv,16))[2:-1].zfill(32).decode('hex')
#print len(iv)
#print iv.encode('hex')
cryptor = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv)
#with open(os.path.join(download_path_decode, file_uri.replace('enc', 'dec')), 'ab') as f:
with open(os.path.join(download_path_decode, file_uri.replace('enc', 'dec')[:57] + '.ts'), 'ab') as f:
f.write(cryptor.decrypt(file_content))
f.flush()
总结
整个流程主要困难的地方是调试那个密钥,而且js文件已经被混淆,调试存在一定的难度。后续内容中,我也在js调试中证明y参数就是解密密钥,具体流程及完整python实现批量下载不再赘述。
python部分代码参考链接:
https://blog.csdn.net/liujiayu2/article/details/86083400
来源:freebuf.com 2021-02-02 01:05:20 by: 6FyY2p
请登录后发表评论
注册