阿里云Web播放器m3u8的ts文件解密下载过程实现

前言

现在网页上所播放的视频,都是m3u8的片段式的数据格式,ts本质是mpeg-2编码,优势就是可拆分为多个文件,这有利于网络传输。为防止视频被传播,ts文件会被加密。 一般的加密方式都是基于ts文件整体使用AES加密,这种只要拿到key,都可以解密成功。 阿里云播放器加密有3种,但万变不离其宗,道高一尺魔高一丈。只要认真,没有翻不过的火焰山。 因此,本文将介绍 鄙人过去这几天对阿里云ts文件解密的过程。 本人非js专业,有不专业之处,还望指点,本文着重叙述 实现的过程、思路、以及遇到的问题

工具与环境

Safari浏览器、Sublime文本编辑器

分析视频

打开视频地址(这里以51CTO的的视频(登录vip才能看)为例,地址就不公布了),打开网页检查器,清空缓存,重新刷新网页,在网页检查器中选择网络 — XHR ,观察网络请求。
阿里云Web播放器m3u8的ts文件解密下载过程实现
可以发现,有四个主要http请求,ts文件是我们想要的文件,如果直接下载ts,是无法播放的,因为加密了。所以我们的目的就是解密ts。那么既然解密,就要找到解密的代码。
如何找解密代码?因为数据都是由http请求最终返回,所以我们从http请求入手。一步一步追踪返回的数据,看数据最终如何被处理。

分析http请求

由上步,我们知道主要的4个http请求,其中有一部分是ts文件的,就不必分析了,所以我们就依次分析vod-play-auth、vod.cn-shanghai.aliyuncs.com、m3u8的这3个http请求的数据内容。
阿里云Web播放器m3u8的ts文件解密下载过程实现
先看m3u8的内容,可以发现其中包含,ts的链接地址(虽然不全),加密类型以及解密的key(显然已加密)。
vod-play-auth返回的数据,主要有 playAuth字段以及数据格式和加密类型,由此猜测此请求应该是用来提供解密的key的。
vod.cn-shanghai.aliyuncs.com 对于此接口请求,可以发现返回内容中有加密类型以及 m3u8的请求地址。
综上,按如下思路:

  • 找到vod.cn-shanghai.aliyuncs.com 请求代码
  • 得到m3u8的的请求代码
  • 根据m3u8返回内容,找到ts地址
  • 根据ts内容,找到解密代码

查找代码,断点调试

按上面分析的4个步骤继续,为了查找vod.cn-shanghai.aliyuncs.com请求的代码在哪里,我们直接全局搜索请求的名称。
阿里云Web播放器m3u8的ts文件解密下载过程实现
没有搜索到结果,别灰心。 既然接口名搜索无结果,我们看看这个请求的参数,按参数搜索。
打开网页检查器–>网络–>XHR–>选中vod.cn-shanghai.aliyuncs.com接口–>选择右侧子菜单栏的“标头”
阿里云Web播放器m3u8的ts文件解密下载过程实现
由上图可看到,在请求的URL中,问号后面的字段是AccessKeyId,这就是参数之一。我们接下来,全局搜索该字段。
依旧在右上角的输入框输入,输入AccessKeyId后回车。
阿里云Web播放器m3u8的ts文件解密下载过程实现
依次点击搜索结果,根据经验观察哪些可能是请求的参数,就在哪个文件的AccessKeyId所在的函数处打上断点。
也可以在AccessKeyId所在的全部函数都打上断点。
接下来,点击网页检查器上的刷新按钮,左上带小箭头图片的按钮。然后观察程序是否停在了断点处。

我这里断点在aliplayer.cto.js|v=9196257513 文件中的第23076行,程序停在了这个断点处。
当程序停在断点处时,注意页面布局,这时是在“来源”菜单下的,左侧有函数调用堆栈,调试按钮,文件名,代码区,右侧就是代码文件的信息。
函数调用堆栈可以让我们知道调用顺序,此时大家可以自行点击调用堆栈的各个函数观察一下。
阿里云Web播放器m3u8的ts文件解密下载过程实现
我们必须靠函数调用堆栈以及调试按钮 才能发现解密代码。
这里介绍下 调试按钮的各个作用:

  • 第一个:断点开关,作用是:是否开启断点。
  • 第二个:跳过断点,继续运行
  • 第三个:单步执行(一行一行代码的执行,遇到函数调用会跳过,如果想进入函数用第四个按钮)
  • 第四个:进入函数
  • 第五个:跳出函数

OK,接下来我们分析,当前停止在断点处的函数。看函数的内容,我们发现,在23096行处的代码和我们的要找的接口名字有点像,那么我们就验证下。
点击单步执行按钮,一行一行的执行到23097行,停在这里,把鼠标放到23096行处的 i 变量上,就会弹出该变量的值。
阿里云Web播放器m3u8的ts文件解密下载过程实现
看i的值,和我们查找的接口请求很像,但是i值没有展示全,此时可以调试窗口中 使用 console.log(i) 来打印i的值。
经过对比,确认这就是我们要找的vod.cn-shanghai.aliyuncs.com接口请求代码所在处,在 js文件 aliplayer.cto.js|v=9196257513 中的第23097行。
接下来,我们继续分析,把鼠标放在23097行处的get的后面,查看get函数的内容。(或者使用“进入函数”的按钮直接跳入该函数,但一般都是先看看函数内容)
阿里云Web播放器m3u8的ts文件解密下载过程实现
由上图,根据get内容中调用的函数名猜测,get函数就是一个一个ajax请求的封装。因此猜测get函数的第3个参数(参数类型为函数),应该就是请求的返回数据处了。
因此,我们在23098行,打下断点,按下调试按钮的第二个,就是跳过断点的按钮。 程序会停留在23098行,若没有停留,则可能是超时,则重新刷新网页。查看变量e的值。
阿里云Web播放器m3u8的ts文件解密下载过程实现
由此可以看到,变量e的值就是我们vod.cn-shanghai.aliyuncs.com 请求返回的数据内容。也可以打印e的值,这样看的更全。此时e的数据是还未解析的json字符串,下一行的代码就是json解析。
接下来就顺着这个数据,继续往下走。
接着,一行一行的单步执行,遇到函数,可以查看下函数的内容,这里就不过多查看,直接到我已分析过的函数中。
单步执行到23108行,查看 l 的值,以及函数h的值。
阿里云Web播放器m3u8的ts文件解密下载过程实现
这里图上,没有放 l 的值,l的值是m3u8的地址。h函数的参数中有 l,可以确定,数据会在h函数中处理,那么我们查看h函数的值,在h的函数的右上角,会显示函数所在文件以及所在行,点击即进入该函数,并在该函数内的第一行也就是22830行处设置断点,使用跳过断点按钮,程序会进入h函数,停留在22830行。
阿里云Web播放器m3u8的ts文件解密下载过程实现
查看传过来的参数e的值,确认m3u8的地址传递过来了。
接下来,单步执行,一行一行的分析(限于篇幅,这里就不分析了),这里我直接跳到我们需要的函数了。单步执行到22875行,查看22973行的变量t、h、rand、plaintext以及函数_sce_dlgtqred的值。通过各个参数的值,特别是变量t是个16位的整型数组,以及函数内容,以及函数名等,大概猜测变量t的值是 解密的秘钥。因此我们暂时记住变量t的值。
阿里云Web播放器m3u8的ts文件解密下载过程实现
继续单步执行至22878行,我们查看d.initPlay函数,点右上角的函数行号,进入函数initPlay内部。然后在initPlay函数内部第一行设置断点,然后使用“跳过断点”按钮,继续执行,程序会停留在initPlay函数内的断点上。如下图:
阿里云Web播放器m3u8的ts文件解密下载过程实现
单步执行到22342行,到这里我们发现,这一行是initPlay函数的最后一行了,如果继续使用单步执行,就会执行到函数末尾了,接下来就只能跳出函数。
仔细观察这一行的代码,有1个参数是个函数,其实这个函数的作用是加载hls的js, 这一步很关键,当然这也是我后来发现的,一开始也不知道是干啥的,因为反复调试,代码看的多了,就发现了猫腻。
因此,这里我们直接在22348行(也就是loadJS函数内部)设置断点,然后使用“跳过断点,继续执行”按钮,你会发现,程序会停留在loadJS内部的断点上。
阿里云Web播放器m3u8的ts文件解密下载过程实现
接下来,使用进入函数按钮进入22351行,然后继续使用进入函数,跳转到r函数内部,即22299行,单步执行到22309行,然后反复使用进入和跳出函数,直到停留在s._hls.attachMedia(s.tag) 函数上。如下图
阿里云Web播放器m3u8的ts文件解密下载过程实现
进入attachMedia函数内部,继续进入attachMedia函数内部的 trigger函数(8802行),使用进入函数按钮,直到停留在8819行,在8821行设置断点,并执行到此处
阿里云Web播放器m3u8的ts文件解密下载过程实现
继续使用“进入函数”按钮,跳转进emit函数内,然后使用单步调试,跳转到3153行,然后继续使用“进入函数”按钮,进入2646行,继续使用“进入函数”,进入到onEventGenericha函数,并查看参数 t 的值。
阿里云Web播放器m3u8的ts文件解密下载过程实现
观察参数t的值,是不是有点熟悉,还记得我们是从哪个函数进来的吗?没错,前面调用了attachMedia函数。分析到这里,似乎发现了点什么,是的,现在正在执行的代码是 aliplayer-hls-min.js 中的代码。
但是现在这个onEventGeneric函数还是让我疑惑,为什么会调用到这里。因此,就打断点吧,多跑几次看看,最后发现,断点经常停留在这个函数。
而且参数 t 的值,似乎有