记一次加密的m3u8视频下载方法

前言

问题来源于一个视频网站的下载需求。

视频网站常用方案

一般视频网站常用的方案我个人目前遇到过的有两种,m3u8 格式和 mp4 格式。

视频播放流程为:在进行登录认证后,点击对应视频链接,使用自定义 / 开源播放器播放对应的视频链接。

mp4 格式

由于 mp4 不支持在播放时自动解密,一般比较好分析,找到带有 mp4 后缀的链接带上认证信息(Cookie 或者 URL 参数)就可以下载了,这种方案用的比较少,尤其是付费课程之类的,因为不能很好的防止下载。

m3u8 格式

m3u8 在播放时支持动态解密,所以在付费课程类网站使用的比较多,而且防下载的方式也比较丰富:

  1. 将视频加密,加密密钥放到服务器上特定位置,在 m3u8 文件中通过 #EXT-X-KEY:METHOD=AES-128,URI= 字段指向服务器上的加密密钥,当播放视频时播放器会自动访问服务器上的加密密钥,此时可以验证请求的身份信息(通过 Cookie 或 URL 参数)来确认是否是正常用户观看视频,同时在访问 m3u8 文件时也可以验证身份信息(可选)

    优点:使用普通的视频播放器不能直接下载视频文件,因为在访问服务器上的加密密钥时需要带上认证信息,普通视频播放器不支持自定义请求头,在这里可以防住一部分人

    缺点:这种方案不利于动态的改变密钥,因为加密后的视频文件一般是通过 CDN 加速的,如果想要动态的改变视频文件的加密密钥,那么就需要提前使用不同的加密密钥加密同一个视频,这样会产生比较大的存储空间占用,同一套视频有多少个加密密钥就要存储多少份文件。而如果是动态加密(不知道有硬件支持的话效率会不会可以接受?)的话会占用比较多的 CPU 时间,而且可能不能使用 CDN 缓存(暂不了解是否有支持动态加密的 CDN?)

    下载方法:既然需要用到服务器上的加密密钥,那么使用程序批量下载密钥保存在本地,然后替换掉 m3u8 文件中对应字段的内容就好了,播放器会自动从本地加载加密密钥,而且一般情况下视频文件是不验证身份信息的,这时就可以使用 ffmpeg 等播放器直接播放了。如果视频文件本身也验证身份信息的话那么只能使用程序下载手动解密后再合并视频了。

  2. 将视频加密,加密密钥放到混淆后的 JS 代码中,需要逆向 JS 才可以得到正确的解密密码。

    优点:加密密钥比较隐蔽,可以使用难以分析的混淆方法保护加密密钥,加密方式很灵活。

    缺点:这种方案和上一种方案类似,动态的改变密钥都需要预先加密多份视频文件,存储空间占用比较大

    下载方法:逆向 JS

  3. 视频不加密,将 m3u8 文件加密后传到客户端,动态的解密 m3u8 文件。

    优点:可以动态的改变加密密钥,因为视频文件没有加密,只加密了比较小的 m3u8 文件,改变加密密钥的代价小了很多,比较灵活。即使被抓包抓到了视频文件的链接,也无法猜测后续的视频文件链接,因为每个视频的长度可能都会不一样。

    缺点:视频文件没有加密,如果被人逆向 JS 代码找到了 m3u8 文件的话,就和没有加密一样了。而且使用此方案可能需要对视频播放器进行 hack,加入自定义的解密方法,实时的解密并播放。因为普通的视频播放器直接传入 m3u8 文件链接就可以自动播放,如果传入加密后的内容就需要修改播放器的代码。

    下载方法:逆向 JS

等等,以上方法并不完全,只是举个例子,不同方法可以灵活的搭配,在收益和成本之间进行取舍选择最适合的方案。

扯远了扯远了,回到这次的网站上来。

视频链接分析

寻找视频链接一般使用逆推的方法,先找到视频的播放链接,查看使用了什么方案播放视频。

如果是 mp4 的话直接带着认证信息下载就好了。

如果是 m3u8 的话需要观察 m3u8 文件链接的参数,查找参数的来源,一步一步的反向查找,直到找到网站首页,搞清楚链接中各种参数的来源就完成了一大部分了。在此期间可能会遇到参数动态变化的情况,此时可能需要去逆向 JS 文件,去查找参数的生成算法。

本次所分析的网站没有使用到 JS 动态生成的参数,下面开始。

分析视频方案

先随意打开一个视频播放页面,打开谷歌开发者工具,抓包,过滤 XHR 请求,如图:

image-20201223151847264

image-20201223152002086

如上图所示,可以看到,网站使用了 m3u8 格式,并且下面还有一个 getkey 请求,可能是在请求加密密钥,再往下就是真正的视频文件(加密后的)

image-20201223152322936

打开 m3u8 文件看一下,还好,文件没有被加密,只是视频文件加密了,将加密密钥放到了 #EXT-X-KEY:METHOD=AES-128,URI= 字段中,需要访问后面的链接才可以获取到加密密钥,验证了一下,视频文件是加密的,getkey 请求返回的是十六字节二进制数据,应该是加密密钥了,基本确定就是上边所说的方案一。

先在本地测试下目前的结论是否正确,防止后面做无用功。

经过测试,视频文件下载不需要认证信息,密钥文件需要认证信息。

将 m3u8 文件下载一个下来,通过浏览器访问加密密钥链接,手动下载密钥文件,放到同一个目录中,将视频文件的 URL 补充完整,效果如下图:

image-20201223153107907

运行以下命令:

image-20201223153423175

结果如下:

image-20201223153626563

经过测试可以正常运行,证明我们的结论是正确的。接下来的任务就是找到视频链接是怎么来的并且将流程自动化。

查找视频链接来源

因为加密密钥的链接在 m3u8 文件中,视频文件的链接也在 m3u8 文件中,所以我们只需要找到如何下载 m3u8 文件就可以了。

查找的思路就是一步步往上翻所有的请求,m3u8 文件链接中用到的参数肯定是通过某个链接获取到的,或者是本地计算得到的,以此类推,所有的链接都可以使用不同的参数串联起来。

下面的内容比较枯燥(可能是我写的不好),不感兴趣的小伙伴可以跳过,学到了思路就可以。

查看 m3u8 文件的请求方法,是 GET 类型。

image-20201223154157065

查看 m3u8 文件的 URL 参数,一共有三个,有一个类似于签名的参数。

image-20201223154237727

往上翻看所有请求,查找出现 m3u8 文件链接的请求或者出现 sign 参数的请求。

值得高兴的是,在一个请求的响应中找到了完整的 m3u8 文件 URL。也就是说 sign 参数不是 JS 生成的,也就省去了逆向 JS 文件的步骤,大大简化了分析流程。

image-20201223154538263

接下来需要找到这个请求所需的参数是哪里来的,和刚才找 m3u8 文件的链接一样的思路,以此类推。

请求中有四个参数,测试了下,第一个参数是会变的,类似于一个认证参数,下面需要找到这个参数的来源。

image-20201223154939203

第一个参数的值,是通过这个链接获取到的,如下图:

image-20201223155531356

而这个链接需要三个参数,第一个参数是会变的,如下图:

image-20201223155649684

接下来需要找这个会变的参数,如下图:

image-20201223155926456

而可以获取到此参数的链接中需要的参数正好包含在我们访问视频页面的链接中,如下图:

image-20201223160753001

到目前为止,如果有了视频页面的地址,就可以根据刚才的思路正确的获取到 m3u8 文件的地址。

但是还不够,视频页面有好多个,可不能一个个的手动去获取,我们需要一个可以获取所有视频地址的方法。

返回到包含视频列表的页面,此页面中肯定包含了获取所有视频页面链接的方法,如下图:

image-20201223161310911

到这里,获取所有视频播放地址的流程就全部搞清楚了,将以上流程从下到上反向连接起来,就是获取所有视频链接的流程:获取所有视频页面链接 -> 单个视频播放页面链接 -> 获取fileId参数 -> 获取pSign参数 -> 获取视频的m3u8 URL -> 获取加密密钥 -> 完整的视频

自动获取链接

根据以上流程,可以写出获取所有 m3u8 链接的代码,由于一些原因,代码这里就不放了。

运行代码可以获取到所有的 m3u8 文件,并保存到本地,在保存 m3u8 文件之前,稍微修改下可以将上面手动修改密钥路径和手动补全 URL 的动作自动化,保存好的 m3u8 文件直接可以使用 ffmpeg 来播放。

image-20201223162339479

总结

以上就是分析视频链接的流程,比较重要的是查找的思路,根据 URL 的参数后往前反着推,一环扣一环,最后将整个流程连接起来即可。

注:以上分析流程及结果仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果与作者无关。

参考链接

https://ffmpeg.org/ffmpeg.html

本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接https://blog.d77.xyz/archives/cae5c558.html