逆向:某酷自研DRM解密思路分析 时间: 2022-10-02 18:29 分类: 修仙日记,逆向 ####前言 上一篇文章只是讲了扒取解密js代码拿出来直接调用,看到有些人写了相关工具出来,有人问为什么用`AES-128-ECB`直接解密`TS`文件为什么不行,博主还在那卖关子,很是不爽。 这里我猜测80%的可能,那些工具也都是扒取的`JS`解密代码出来直接调用,因为经过分析,整个解密过程是需要按帧读取`TS`文件进行解密的,若自己来实现的话不太现实,当然你可以调库去读取。 但是,调试发现,好像并不是所有帧都进行解密,读一帧就跳过很多帧,由于每个`TS`文件帧数比较多,也可能是异步不按顺序解密(但这种可能比较小,因为最后不太好合并) 下面就抛砖引玉,有兴趣的自行去实现看到底是否每帧数据都需要解密。 ####分析 在分析解密过程之前,先去了解`TS`文件格式还是非常有必要的,这里有一篇稍微比较详细的文章(但还是有点模棱两可,但了解文件结构应该差不多足够了):https://blog.csdn.net/coloriy/article/details/79852682 根据上一篇文章的分析,我们知道了整个解密代码都是在`Worker`中,直接去`Worker`中搜索`decrypt`,在`decryptAES128ECB`函数中打下断点。 追踪调用栈,定位到`splitTsPacket`函数,当然你也可以直接从传入加密数据的地方开始一步一步往下走,也能定位到这里,不过在`decryptAES128ECB`打下断点可以省下一步一步走的麻烦: ![微信截图_20221002170253.png](https://0o0.me/usr/uploads/2022/10/3515241015.png) `splitTsPacket`代码还是很容易看懂的,在了解`TS`文件结构之后,可以知道都是按照每`188`个字节进行解析的,所以上面的`JS`代码明显就是依次读取`188`个字节进行解析。 这里没啥好看的,接着往下走,进入`push`函数,关键代码在调用栈中的`_write`函数,注意它有很多`_write`的实现,这里我们看下面这个实现: ![微信截图_20221002170908.png](https://0o0.me/usr/uploads/2022/10/3675691565.png) 分析这个函数之前,我们需要知道如何判断一帧数据的结束,因为一帧数据可能包含多个`188`片段: 那就是看`第九位`,也就是`47`后面一个字节的`前一个数字`,当它为非`0`时,表示`有效载荷单元起始符 为1`(这里不考虑错误情况,实际上它的值为0、4、6,其他值就是包错误) 这里推荐一个`TS`文件解析工具`EasyICE`,效果如下: ![微信截图_20221002171709.png](https://0o0.me/usr/uploads/2022/10/2467620168.png) 上面说的也就是截图中的`Payload`,当`Payload`为`1`时,表示一帧的开始,也就是说遇到`Payload`为`1`时,也意味着上一帧的结束。 这个时候再来分析前面的`_write`函数: > e = !!(64 & A[1]) // 64 二进制为 01000000 这个`e`就代表着`Payload`的值,这里转成了`True`或者`False`,表示一帧的开始和上一帧的结束。 接着看: > n = 31 & A[1] // 31 二进制为 00011111 > n <<= 8, n |= A[2] 实际上面计算的`n`就是`PID`,`PID`说明如下: > PID(packet ID) 是每个节目的唯一标识 如当前为 0 0 0 (第一个包为 000)(除了pid=000 标识第一个包 其它数字都是随机可选的 无须按某种序列排序) 再看: > (48 & A[3]) >>> 4 > 1 && (t += A[t] + 1) `(48 & A[3]) >>> 4`计算的是`负载类型`的值,`负载类型`的值说明如下: ``` 0、保留值,供未来使用 (碰到丢弃即可) 1、负载中只有有效载荷 2、负载中只有自适应字段 3、先有自适应字段,再有有效载荷 自适应字段长度, 占一个字节 自适应字段内容,占得字节由长度自适应字段长度决定,多余的字节用0xFF填充 ``` 所以`(48 & A[3]) >>> 4 > 1`判断是否为只有有效载荷,实际上上面计算的`t`是:有效载荷的起始位置。 举个例子,下面这个`188`片段: ``` 47 01 00 3B 75 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 22 BA 22 11 39 41 3C 17 03 D0 48 32 77 02 83 60 0B 20 61 A1 E7 43 FB 30 9B A2 DB AB 23 81 95 78 A2 40 47 28 02 2B 30 92 1A 56 DF 49 7F B1 97 2F 77 E6 75 7B 4B 22 A1 48 EA 22 E8 B2 C5 23 8F 58 D9 51 ``` 可以看到`47`后面是`01`,看第一个数字`0`表示`有效载荷单元起始符为0`,再看`3B`第一个数字,也就是前面`(48 & A[3]) >>> 4`计算的值,这里是3,大于1,所以它意味着`先有自适应字段,再有有效载荷`。 所以后面`75`表示`自适应字段长度`,后面`00`表示`有效载荷`开始,前面说了它的`起始符为0`,所以后面`FF`为填充位。从22BA开始为我们需要的`有效载荷`。 这个时候再看`(t += A[t] + 1)`实际上计算的就是`22BA`的起始位置。 回到函数中,可以看到`PAT`、`PMT`,何为`PAT`、`PMT`? >第一个TS包 一般叫做 PAT (Program Association Table,节目相关表) 第二个TS包 一般叫做PMT (Program Map Table,节目映射表) 我们前面也说了`n`计算的是`PID`,所以`if`判断`0 === n`正好是判断是否为`PAT`。 好了,关于`TS`文件的解析就说到这吧,要继续下去没完没了了,我们主要关心的还是它解密的部分,前面也说了,它似乎并没有解密所有帧。 根据调用栈看到的是`processPes`调用了解密函数,至于它是否所有帧都需要解密,这里看不出来,只是在调试的时候发现它解密不是按`TS`包顺序进行解密的,可能中间跳过很多帧?有待验证,有能力的自行尝试,反正我是没那功夫去弄了(最近最新版的Chrome已经支持H265视频了,相信将来不久各大视频平台就能网页看4K了,当然也不排除为了留住客户端用户还是不开放4K,但只要支持H265,那么某酷的解密`JS`必定会更新支持`H265`解密,到时候直接扒`JS`解密好了)。。 回到`processPes`,`PES`包头只存在于`有效载荷单元起始符为1`的包中,也可以视为它只在每帧的起始`188`片段中,所以上面的`JS`函数`processPes`实际上是解析当前帧的信息(因为当前帧开始,意味着上一帧结束),以及解密上一帧数据。 ####总结 整个过程就是读取`TS`帧数据解密,只不过是用`JS`实现,要是我们自己做的话,如果对于`TS`文件格式不熟悉那是很难实现的,所以要么去磕`TS`文件格式结构,要么找现成的库去读取`TS`数据帧。 还有某酷的`JS`解析中,最后是将音视频流分离出来的,然后再根据读取到的音视频流信息进行封装,不知道直接将这些流二进制合并最后`ffmpeg`转成`MP4`是否可行,我猜应该是可以的。 以上为抛砖引玉,不会提供任何代码,仅供学习交流。 ---------------------------- 2022.10.2 22:32 更新: 虽然`JS`中`H265`相关代码尚未完善,但我们需要的只是它的按帧读取并解密部分,不管是老代码还是新版本的解密代码,都可以通过改写来让它正确读取帧并解密。 但可惜的是,新老代码对于`H265`编码在解密后都无法做到正确封装,从而返回不了解密的数据。 那么换个思路,我们在它解密的地方将解密后的视频数据进行拦截写入到文件,是不是就可以获取到解密后的视频了。 经过测试,思路是可行的,这是偷懒的办法,也就是借已有的`JS`代码来完成`TS`文件解析并解密视频帧。 前面说现有的那些工具,应该说错了,不是直接调的`JS`解密,而是通过工具按帧读取并解密的,或者就只能跟我上面说的改写部分`H264`参数,最后拦截解密数据。 最后说下要改写的参数,老版本是将`H264_STREAM_TYPE: 27`改为`36`,新版本有两处`m.AVC`改为`m.HEVC`即可,后面在`decrypt`函数中拦截部分大同小异。 ---------------- 2022.10.3 16:25 更新: 上面说的拦截方法还是有问题的,由于没有`PTS`、`DTS`信息,最后能是能播放,但有重复画面以及时长不对的问题。 最终还是回到逐帧解码,找了下现有的库,发现没有什么好的办法去逐帧读取解码`TS`,所以尝试改官方`js`文件版本号,结果有惊喜,最新还未发布的版本已经实现`h265`码流封装,于是直接扒出来使用即可,跟目前的版本代码差不多,就一些初始化的方法调用有些不同。 ![微信截图_20221003163142.png](https://0o0.me/usr/uploads/2022/10/485886277.png) 问题至此完美解决,最后再次声明:仅供学习前端逆向,不会提供任何代码,他人实现用作侵权商业后果自负。 标签: 无