茶园资源网 Design By www.iooam.com

某音系列视频解密

一、web端

目标地址:aHR0cHM6Ly9kZW1vLnZvbGN2aWRlby5jb20vY29tbW9uL3ZlcGxheWVyL2Jhc2ljL2V4YW1wbGU/dGFiPWZ1bmN0aW9uLWRpc3BsYXk=

1.hls加密

看了下m3u8内容,一眼就看出来就是类似某浪,根据之前的分析,类似的可以直接找出key,结果还是没加密的

某音系列视频key解密

某音系列视频key解密

2.mp4和dash加密

直接下载播放,发现是花屏的

某音系列视频key解密

然后注意到有条链接,发现有kid,估计类似谷歌的wv或者就是

某音系列视频key解密

返回的内容,后面有一串base64编码的,但解码后长度是37,很明显不对,下个断点分析一下

{    "play_licenses": {        "v02dfag10001cb37d5nft13gcp27jebg": {            "62c67696efe8470664479b970002dfab": "z+Yb/QHhI/9vtE6VANockAKPIMU45yP9Pd1w/Ti3cJdox3KHhw=="        }    },    "base_resp": {        "code": 0,        "message": "success"    }}

某音系列视频key解密

getPlayLicenses:

某音系列视频key解密

然后可以看到进入了,openbox函数里面,然后返回了一个值,

某音系列视频key解密

大胆猜测这就是key,播放一下

ffplay播放ffplay  8bde7560b20b4e79ba8f7a46aec89530.mp4 -decryption_key  79f8ad6b4ee74dec944334e0b9c3547b解密 ffmpeg.exe -decryption_key 79f8ad6b4ee74dec944334e0b9c3547b -i 8bde7560b20b4e79ba8f7a46aec89530.mp4 dec8bde7560b20b4e79ba8f7a46aec89530.mp4

某音系列视频key解密

播放没有问题,继续分析openbox函数,发现很类似wasm,但却看不到wasm文件

关键部分就是_openBox,函数,这里返回了一个地址,然后在用UTF8ToString,从该地址开始取出一定长度(32)的值并转换为String。

{            key: "openBox",            value: function(e) {                try {                    var t = window.atob(e)                      , r = util.str2hex(t)                      , n = this.module._malloc(Number(r.length));                    this.module.HEAP8.set(r, n);                    var i = this.module._openBox(Number(n), r.length)                      , a = this.module.UTF8ToString(i);                    return this.module._free(n),                    a                } catch (error) {}            }        }

继续跟进去,到了这里很明显就是wasm了,只不过把wasm转换为了js

某音系列视频key解密

在openBox里面有很多匿名函数,最后返回的是i,在匿名函数入口打个断点,从下往上分析

某音系列视频key解密

返回的是i=10016,在内存中查看就是我们需要的值,根据wasm特性,v函数就是一个释放内存的

某音系列视频key解密

同时注意到,在这前面也已经存在了解密后的数据,只不过前后多了一个1,在往上一个函数查看,

执行前内存是下面图片,执行后就是上面的图片,很大可能就是移了个坑,这个函数就没有必要分析了(当然其他视频可能用的到,但我试了很多都没有用到)

某音系列视频key解密

继续往上看,很明显了运行前后不一样了

某音系列视频key解密

简单分析一下函数,传入的地址和一个数字,根据上的内存从1-1,刚好34,同时有一个奇葩的for循环,u,o都是memory,只是数据类型不一样而已,先取值赋值给s,在运行后写入内存,o[o|l]

某音系列视频key解密

还有一个b函数,就是popcount 函数

 b(e) {                                for (var t = 0, r = 0; r = t,                                e; )                                    e &= e - 1,                                    t = t + 1 | 0;                                return r                            }

简单还原一下这匿名函数

func fff(dddd1 int, dddd []byte) []byte {   dddd0 := 0   dddd2 := 0   dddd4 := 85   dddd5 := 250   for dddd2 < dddd1 {      num := strings.Count(strconv.FormatInt(int64(dddd2), 2), "1")      dddd3 := dddd[dddd0]      dddd6 := dddd2 & 1      if dddd6 == 1 {         dddd[dddd0] = byte(((((dddd4 ^ int(dddd3)) << 24)  24) + 21) + num)      } else {         dddd[dddd0] = byte(((((dddd5 ^ int(dddd3)) << 24)  24) + 21) + num)      }      if dddd6 == 0 {         dddd5 = int(dddd3)      }      if dddd6 == 1 {         dddd4 = int(dddd3)      }      dddd2++      dddd0++   }   return dddd}

继续往上找,注意到就是传入的数据取了34位,再传给下一个函数,然后还有一堆杂七杂八的不知道有啥用

某音系列视频key解密

提取有用的就一行代码,最终完整代码

package mainimport (    "encoding/base64"    "fmt"    "strconv"    "strings")func fff(dddd1 int, dddd []byte) []byte {    dddd0 := 0    dddd2 := 0    dddd4 := 85    dddd5 := 250    for dddd2 < dddd1 {        num := strings.Count(strconv.FormatInt(int64(dddd2), 2), "1")        dddd3 := dddd[dddd0]        dddd6 := dddd2 & 1        dddd[dddd0] = byte((dddd6&0x1)*dddd4 + (1-dddd6&0x1)*dddd5 ^ int(dddd3)<<2424 + num + 21)        dddd5 = dddd6&0x1*dddd5 + (1-dddd6&0x1)*int(dddd3)        dddd4 = dddd6&0x1*int(dddd3) + (1-dddd6&0x1)*dddd4        dddd2++        dddd0++    }    return dddd}func ff(n []byte) []byte {    return fff(34, n[1:35])[1:33]    /*        dddd := []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}            aaaa := []byte{}            aaaa = n            dddd[1] = len(n)            dddd[6] = int(n[2] ^ n[1] ^ n[0])            var data1 = byte(dddd[6] - 48)            dddd[7] = dddd[1] - dddd[6] + 47            var data2 = byte(dddd[1] - dddd[6] + 47)            dddd[6] = int(data1)            dddd[7] = dddd[6]            if data1 > 0 {                for {                    dddd[7]--                    dddd[6] = 0                    if dddd[7] < 1 {                        break                    } else {                        dddd[6] = int(data1)                    }                }            }            var data3 = aaaa[1 : 1+int(data2)]            return fff(int(data2), data3)[1 : len(data3)-1]    */}func main() {    enckey := ""    fmt.Println(enckey)    a, _ := base64.StdEncoding.DecodeString(enckey)    deckey := ff(a)    fmt.Println(string(deckey))}

运行对比一下没有问题

某音系列视频key解密

这里分析的是mp4,dash解密和mp4一样的,只不过音视频分离的而已,解密位置稍微不同,解密函数基本一样

某音系列视频key解密

3.真wasm

网站aHR0cHM6Ly93d3cuZGVkYW8uY24vbGl2ZS9kZXRhaWw/aWQ9Wm1PSldEem1iN3ZWSzFMNEVsQVhlOGs5cEtiMXVPZzJickFMeEVCd04wQmcyeWRaYVlvclJNajZHeDNucTUyWA==

这个网站也是用的同样的播放器,根据上面的直接搜索,找到关键位置,不能说类似,只能说完全一样,只不过这里是wasm文件了,下载下来简单看一下

某音系列视频key解密

使用jeb,查看代码,根据前面的分析只有前两个函数有用,也就是f13,主要是f12

某音系列视频key解密

简单看下f12,这看起来比js那个for循环舒服多了

某音系列视频key解密

基本逻辑一样,就不分析了,对照指令表还原就行http://www.dwenzhao.cn/profession/netbuild/webassemblyfunc.html

尤其关注malloc和store指令

二、app

初步分析

app端有很多抖音旗下的基本都用了,以某浪(mp4)和番茄畅听(mp3)为例,注意这条信息里面有个类似网页的参数,但是用网页的解密不行,hook常用算法也没有结果,只有分析代码了

某音系列视频key解密

逐步搜索定位关键函数,初步确定位于com.ss.ttvideoengine.JniUtils.getEncryptionKey里面

某音系列视频key解密

hook一下,传进来的是byte,转一下base64,hook代码

Java.perform(function x() {    function showStacks() {      console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));   }let JniUtils = Java.use("com.ss.ttvideoengine.JniUtils");JniUtils["getEncryptionKey"].implementation = function (bArr) {    showStacks()    let base64 = Java.use("android.util.Base64");    let b64 = base64.encodeToString(bArr, 2);    console.log("b64: " + b64);    console.log('getEncryptionKey is called' + ', ' + 'bArr: ' + bArr);    let ret = this.getEncryptionKey(bArr);    console.log('getEncryptionKey ret value is ' + ret);    return ret;};});

某音系列视频key解密

某浪类似的,改下就行

let Native = Java.use("com.ss.ttm.ttvideodecode.Native");Native["_getEncryptionKey"]