前言
现在网页上所播放的视频,都是m3u8的片段式的数据格式,ts本质是mpeg-2编码,优势就是可拆分为多个文件,这有利于网络传输。为防止视频被传播,ts文件会被加密。 一般的加密方式都是基于ts文件整体使用AES加密,这种只要拿到key,都可以解密成功。 阿里云播放器加密有3种,但万变不离其宗,道高一尺魔高一丈。只要认真,没有翻不过的火焰山。 因此,本文将介绍 鄙人过去这几天对阿里云ts文件解密的过程。 本人非js专业,有不专业之处,还望指点,本文着重叙述 实现的过程、思路、以及遇到的问题
工具与环境
Safari浏览器、Sublime文本编辑器
分析视频
打开视频地址(这里以51CTO的的视频(登录vip才能看)为例,地址就不公布了),打开网页检查器,清空缓存,重新刷新网页,在网页检查器中选择网络 — XHR ,观察网络请求。
可以发现,有四个主要http请求,ts文件是我们想要的文件,如果直接下载ts,是无法播放的,因为加密了。所以我们的目的就是解密ts。那么既然解密,就要找到解密的代码。
如何找解密代码?因为数据都是由http请求最终返回,所以我们从http请求入手。一步一步追踪返回的数据,看数据最终如何被处理。
分析http请求
由上步,我们知道主要的4个http请求,其中有一部分是ts文件的,就不必分析了,所以我们就依次分析vod-play-auth、vod.cn-shanghai.aliyuncs.com、m3u8的这3个http请求的数据内容。
先看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请求的代码在哪里,我们直接全局搜索请求的名称。
没有搜索到结果,别灰心。 既然接口名搜索无结果,我们看看这个请求的参数,按参数搜索。
打开网页检查器–>网络–>XHR–>选中vod.cn-shanghai.aliyuncs.com接口–>选择右侧子菜单栏的“标头”
由上图可看到,在请求的URL中,问号后面的字段是AccessKeyId,这就是参数之一。我们接下来,全局搜索该字段。
依旧在右上角的输入框输入,输入AccessKeyId后回车。
依次点击搜索结果,根据经验观察哪些可能是请求的参数,就在哪个文件的AccessKeyId所在的函数处打上断点。
也可以在AccessKeyId所在的全部函数都打上断点。
接下来,点击网页检查器上的刷新按钮,左上带小箭头图片的按钮。然后观察程序是否停在了断点处。
我这里断点在aliplayer.cto.js|v=9196257513 文件中的第23076行,程序停在了这个断点处。
当程序停在断点处时,注意页面布局,这时是在“来源”菜单下的,左侧有函数调用堆栈,调试按钮,文件名,代码区,右侧就是代码文件的信息。
函数调用堆栈可以让我们知道调用顺序,此时大家可以自行点击调用堆栈的各个函数观察一下。
我们必须靠函数调用堆栈以及调试按钮 才能发现解密代码。
这里介绍下 调试按钮的各个作用:
- 第一个:断点开关,作用是:是否开启断点。
- 第二个:跳过断点,继续运行
- 第三个:单步执行(一行一行代码的执行,遇到函数调用会跳过,如果想进入函数用第四个按钮)
- 第四个:进入函数
- 第五个:跳出函数
OK,接下来我们分析,当前停止在断点处的函数。看函数的内容,我们发现,在23096行处的代码和我们的要找的接口名字有点像,那么我们就验证下。
点击单步执行按钮,一行一行的执行到23097行,停在这里,把鼠标放到23096行处的 i 变量上,就会弹出该变量的值。
看i的值,和我们查找的接口请求很像,但是i值没有展示全,此时可以调试窗口中 使用 console.log(i) 来打印i的值。
经过对比,确认这就是我们要找的vod.cn-shanghai.aliyuncs.com接口请求代码所在处,在 js文件 aliplayer.cto.js|v=9196257513 中的第23097行。
接下来,我们继续分析,把鼠标放在23097行处的get的后面,查看get函数的内容。(或者使用“进入函数”的按钮直接跳入该函数,但一般都是先看看函数内容)
由上图,根据get内容中调用的函数名猜测,get函数就是一个一个ajax请求的封装。因此猜测get函数的第3个参数(参数类型为函数),应该就是请求的返回数据处了。
因此,我们在23098行,打下断点,按下调试按钮的第二个,就是跳过断点的按钮。 程序会停留在23098行,若没有停留,则可能是超时,则重新刷新网页。查看变量e的值。
由此可以看到,变量e的值就是我们vod.cn-shanghai.aliyuncs.com 请求返回的数据内容。也可以打印e的值,这样看的更全。此时e的数据是还未解析的json字符串,下一行的代码就是json解析。
接下来就顺着这个数据,继续往下走。
接着,一行一行的单步执行,遇到函数,可以查看下函数的内容,这里就不过多查看,直接到我已分析过的函数中。
单步执行到23108行,查看 l 的值,以及函数h的值。
这里图上,没有放 l 的值,l的值是m3u8的地址。h函数的参数中有 l,可以确定,数据会在h函数中处理,那么我们查看h函数的值,在h的函数的右上角,会显示函数所在文件以及所在行,点击即进入该函数,并在该函数内的第一行也就是22830行处设置断点,使用跳过断点按钮,程序会进入h函数,停留在22830行。
查看传过来的参数e的值,确认m3u8的地址传递过来了。
接下来,单步执行,一行一行的分析(限于篇幅,这里就不分析了),这里我直接跳到我们需要的函数了。单步执行到22875行,查看22973行的变量t、h、rand、plaintext以及函数_sce_dlgtqred的值。通过各个参数的值,特别是变量t是个16位的整型数组,以及函数内容,以及函数名等,大概猜测变量t的值是 解密的秘钥。因此我们暂时记住变量t的值。
继续单步执行至22878行,我们查看d.initPlay函数,点右上角的函数行号,进入函数initPlay内部。然后在initPlay函数内部第一行设置断点,然后使用“跳过断点”按钮,继续执行,程序会停留在initPlay函数内的断点上。如下图:
单步执行到22342行,到这里我们发现,这一行是initPlay函数的最后一行了,如果继续使用单步执行,就会执行到函数末尾了,接下来就只能跳出函数。
仔细观察这一行的代码,有1个参数是个函数,其实这个函数的作用是加载hls的js, 这一步很关键,当然这也是我后来发现的,一开始也不知道是干啥的,因为反复调试,代码看的多了,就发现了猫腻。
因此,这里我们直接在22348行(也就是loadJS函数内部)设置断点,然后使用“跳过断点,继续执行”按钮,你会发现,程序会停留在loadJS内部的断点上。
接下来,使用进入函数按钮进入22351行,然后继续使用进入函数,跳转到r函数内部,即22299行,单步执行到22309行,然后反复使用进入和跳出函数,直到停留在s._hls.attachMedia(s.tag) 函数上。如下图
进入attachMedia函数内部,继续进入attachMedia函数内部的 trigger函数(8802行),使用进入函数按钮,直到停留在8819行,在8821行设置断点,并执行到此处
继续使用“进入函数”按钮,跳转进emit函数内,然后使用单步调试,跳转到3153行,然后继续使用“进入函数”按钮,进入2646行,继续使用“进入函数”,进入到onEventGenericha函数,并查看参数 t 的值。
观察参数t的值,是不是有点熟悉,还记得我们是从哪个函数进来的吗?没错,前面调用了attachMedia函数。分析到这里,似乎发现了点什么,是的,现在正在执行的代码是 aliplayer-hls-min.js 中的代码。
但是现在这个onEventGeneric函数还是让我疑惑,为什么会调用到这里。因此,就打断点吧,多跑几次看看,最后发现,断点经常停留在这个函数。
而且参数 t 的值,似乎有规律。因此,决定搜索其值,按Command+F文件内搜索,最后发现,有很多类似的值。
因此,我们猜测大部分函数的执行都会经过这里,那么我们通过打印日志来验证下。在网页检查器上,选择 网络 –> js –> aliplayer-hls-min.js ,右键点击创建本地覆盖。有了覆盖文件,我们就可以编辑js文件了,相当于可以直接写代码了。
回到“来源”菜单下,在左侧的“本地覆盖”栏目下,找到我们创建的aliplayer-hls-min.js覆盖文件,找到onEventGeneric函数,在内部输入console.log(“bill_”+t)打印参数t的值。然后再safari菜单中, 选择清空缓存,然后重加载页面,在网页检查器中,把断点全部停用。在控制台页面,观察我们的日志打印。我们发现,打了很多值, 确认了我们的猜测。
最后,我们发现,几乎所有的事件处理,都会调用本方法,包括ts文件的加载以及数据处理。
其中,hlsFragLoading 加载的是ts的数据。由此,我们可以根据 断点–函数调用栈–找到ts的http请求在哪里(限于篇幅,此处不再分析)。
我们直接开始分析 数据处理的部分,hlsFragLoaded 代表ts文件数据加载完成,接下来就是开始处理数据(解密–>保存–>添加至播放器)。
因此我们在onEventGeneric函数中设置断点,观察参数 t 的值,如果不是hlsFragLoaded则使用跳过按钮,继续执行,直到参数t的值为 hlsFragLoaded时,准备好单步调试。
单步执行到 2659行,使用进入函数按钮,会跳到2653行的函数内部,继续单步执行到2656行,然后使用进入函数按钮,会重新跳到2659行,此时继续使用进入函数按钮,则进入 e.prototype.onFragLoaded = function(t) { } 函数。
注意!!!
因为onFragLoaded函数有很多个,有可能进入的不是我们进行数据处理。因为数据加载后,需要做其他工作,所以未必一开始就开始处理数据。那么此时我们跳过断点继续执行,会发现程序又停在onEventGenegic函数的断点上,而且参数t的值依然是hlsFragLoaded. 然后依次重复上面的 操作步骤,直到我们进入到下图的onFragLoaded的函数,并单步执行到17817行,即d.push… 函数行。 (若单步执行,没有到达这一行,则跳过断点继续执行,按以上步骤再来一次)
去除worker动态调试的限制
分析到此处,如果继续按之前的方法继续,那么我们会发现,部分函数的断点失效了,日志会打印,但是断点却不走。这是因为为了防止大家反调试,因此他们使用woker创建了混淆代码,每次运行都会新建一个原来js的副本文件,因为每次都会新建副本,所以我们设置的断点就会失效,这个副本是在woker内用js代码自动生成的。关于worker大家自行百度之。
解决方案: 关闭worker。不同的代码可能实现方式不同,这里是有一个worker开关,猜测是为了兼容老版本。
具体步骤:
1. 在aliplayer-hls-min.js 中搜索”enableWorker “,可以看到分号后面的值是!0, 我们知道0代表否,!代表取反,所以!0代表true,因此我们把 ! 去掉,则关闭了woker.
2. 新建 aliplayer.cto.js 本地覆盖文件,然后操作步骤同1,这两个文件的woker都需要关闭。
接下来,就愉快的打断点吧
开始数据解密
继续接上面的步骤,一步一步的分析,直到我们找到数据解密函数。 这里就不过多分析了。我们直接跳到数据解析函数在18481行,t.prototype.append = function(e, r, i, n) {}
,并设置断点。
这里我们简单介绍下ts的文件结构:ts文件由多个ts包组成,每个ts包188字节,ts包内含有pes包,一个pes包可存在于多个ts中。
分析t.prototype.append = function(e, r, i, n) {}
函数,我们发现:此函数就是在解析ts文件内容,找到每个ts包的数据,同时会解析出pes包,然后对pes包进行解密。 解密函数是 D,到这里,我们进入解密函数D,最后发现D函数返回的数据就是解密后的pes包数据。
我们进入解密函数D,分析下其内容。我们直接跳到D函数,断点。分析后,我们发现,此函数就是parsePES, 即通过ts包的数据,解析出pes数据,同时对pes数据进行aes解密。看下图:
我们分析下o = l(o, p)
代码中的 l 函数,进入 I 函数, 查看参数t 和 e的值,我们发现 t 为刚刚解析的pes的数据, e 为我们之前猜测的解密key。如下图:
至此,我们就明白了,ts数据是如何解密的,一般的ts片段都是整个文件直接aes加密,而阿里云的是在pes数据上加密,因此解密起来就要了解ts文件结构,还有的就是调试起来比较困难。
下载解密后的ts文件
我们已经知道如何解密的了,接下来就是保存解密后的文件。那么如何保存呢?
思路如下,既然ts中的pes是加密的,那么我们把解密后的pes数据,替换掉原有的pes数据,然后重新保存ts文件,就可以了。
接下来开始分析具体操作:
因为解密的时候,是解密一个完整的pes包,而一个pes包,有可能存在于多个ts包中,且pes包长度不定,每个ts包中都有ts包头,也就是说pes包可能分散于多个ts包中,且pes包的数据并不在 ts中连续,举个例子 如果某个pes包存在于3个ts包中,也就是说pes包存在 3个188字节中,这3个188字节中,会有3个ts包头,以及一个pes包包头,pes包也有包头,pes包头不会别加密,所以替换的时候,还需要考虑pes包头数据。
鉴于以上分析,我们替换的时候,必须知道pes包中的数据,分别存在于那些ts包中,以及存在于该ts包的中起始位置索引。
因此,我们在开始解析pes数据的时候,记录下每个ts包中的pes数据的起始索引以及该ts包中的pes长度。在pes包解密后,再把pes数据重新分割都ts中。
这样,我们就可以将ts包中pes片段数据,插入到ts文件数据中了。
下面开始代码实现:
首先在parsePES中,将解密后的pes数据,分割到对应的ts文件中
带有bill注释的代码,都是我们添加的代码
接下来,我们在数据解析的函数中接收,我们带有正确pes数据的ts包数据。以及替换ts包中的数据,当数据解析完毕后,我们保存解密后的ts文件。请看下图:
至此,就完成了阿里云ts文件解密下载的过程。看下结果:
像web页面的反分析,最大的难处就是核心代码的寻找,只要耐心,剩下的就是时间问题。结合解密过程,本人也写了一个mac版本的小工具。
写总结文章,也会收获很多,让逻辑更清晰,当然真实的分析过程,肯定比文章中的要困难一些,有时候一个思路很久都想不到。
坚持就会胜利!!!
原文链接52破解
评论前必须登录!
注册