某音系列视频key解密

查看 326|回复 10
作者:我是不会改名的   
某音系列视频解密
一、web端
目标地址:aHR0cHM6Ly9kZW1vLnZvbGN2aWRlby5jb20vY29tbW9uL3ZlcGxheWVyL2Jhc2ljL2V4YW1wbGU/dGFiPWZ1bmN0aW9uLWRpc3BsYXk=
1.hls加密
看了下m3u8内容,一眼就看出来就是类似某浪,根据之前的分析,类似的可以直接找出key,结果还是没加密的


2.mp4和dash加密
直接下载播放,发现是花屏的

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

返回的内容,后面有一串base64编码的,但解码后长度是37,很明显不对,下个断点分析一下
{
    "play_licenses": {
        "v02dfag10001cb37d5nft13gcp27jebg": {
            "62c67696efe8470664479b970002dfab": "z+Yb/QHhI/9vtE6VANockAKPIMU45yP9Pd1w/Ti3cJdox3KHhw=="
        }
    },
    "base_resp": {
        "code": 0,
        "message": "success"
    }
}

getPlayLicenses:

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

大胆猜测这就是key,播放一下
ffplay播放
ffplay  8bde7560b20b4e79ba8f7a46aec89530.mp4 -decryption_key  79f8ad6b4ee74dec944334e0b9c3547b
解密
ffmpeg.exe -decryption_key 79f8ad6b4ee74dec944334e0b9c3547b -i 8bde7560b20b4e79ba8f7a46aec89530.mp4 dec8bde7560b20b4e79ba8f7a46aec89530.mp4

播放没有问题,继续分析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

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

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

同时注意到,在这前面也已经存在了解密后的数据,只不过前后多了一个1,在往上一个函数查看,
执行前内存是下面图片,执行后就是上面的图片,很大可能就是移了个坑,这个函数就没有必要分析了(当然其他视频可能用的到,但我试了很多都没有用到)

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

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

还有一个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 > 24) + 21) + num)
      } else {
         dddd[dddd0] = byte(((((dddd5 ^ int(dddd3)) > 24) + 21) + num)
      }
      if dddd6 == 0 {
         dddd5 = int(dddd3)
      }
      if dddd6 == 1 {
         dddd4 = int(dddd3)
      }
      dddd2++
      dddd0++
   }
   return dddd
}
继续往上找,注意到就是传入的数据取了34位,再传给下一个函数,然后还有一堆杂七杂八的不知道有啥用

提取有用的就一行代码,最终完整代码
package main
import (
    "encoding/base64"
    "fmt"
    "strconv"
    "strings"
)
func fff(dddd1 int, dddd []byte) []byte {
    dddd0 := 0
    dddd2 := 0
    dddd4 := 85
    dddd5 := 250
    for dddd2 >24 + 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]
运行对比一下没有问题

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

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

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

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

基本逻辑一样,就不分析了,对照指令表还原就行http://www.dwenzhao.cn/profession/netbuild/webassemblyfunc.html
尤其关注malloc和store指令
二、app
初步分析
app端有很多抖音旗下的基本都用了,以某浪(mp4)和番茄畅听(mp3)为例,注意这条信息里面有个类似网页的参数,但是用网页的解密不行,hook常用算法也没有结果,只有分析代码了

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

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;
};
});

某浪类似的,改下就行
let Native = Java.use("com.ss.ttm.ttvideodecode.Native");
Native["_getEncryptionKey"]

解密前后的音频,视频


rpc调用
比较简单就不分析了,看代码
import json
import uvicorn
import frida, sys
from fastapi import FastAPI
from loguru import logger
app = FastAPI()
@app.get("/get_encryption_key")
def get_encryption_key(data: str):
    result = GetEncryptionKey(data.replace(" ", "+"))
    return result
def on_message(message, data):
    if message['type'] == 'send':
        if type(message['payload']) == str:
            try:
                json_data = json.loads(message['payload'])
                for key in json_data:
                    logger.info(f"{key} : {json_data[key]}")
            except:
                logger.info(message['payload'])
    else:
        print(message)
if __name__ == '__main__':
    with open("./hook.js", encoding='utf-8') as f:
        jscode = f.read()
    device = frida.get_remote_device()
    process = device.attach("番茄畅听")
    script = process.create_script(jscode)
    script.on('message', on_message)
    print(' Running Hook')
    script.load()
    GetEncryptionKey = script.exports.a
    uvicorn.run(app, host="127.0.0.1", port=8000)
    sys.stdin.read()
rpc.exports = {
               a: function (bArr) {
                   //base6str to '[B')
                   let base64 = Java.use("android.util.Base64");
                   let b64 = base64.decode(bArr, 2);
                   console.log('b64: ' + b64);
               var clazz = Java.use("com.ss.ttvideoengine.JniUtils");
               var result = clazz.getEncryptionKey(b64);
               console.log('result: ' + result);
               return result;
           },
             //ublic static synchronized void a()
               b:function  () {
                   var clazz = Java.use("com.ss.ttvideoengine.JniUtils");
                   clazz.a();
               }
           };

算法还原
两个appso不太一样,先看看番茄的,导出函数里面刚好就有,就可以值hook了

先静态分析一下,几乎和网页一样,先获取了字节,获取长度,解密,再转换为str

然后再跟进去,很明显有两个函数,和网页很类似,直接hook一下两个函数

导出函数直接findExportByName就行了,打印成hexdump方便查看
const getConvertMethodAndKey = Module.findExportByName("libvideodec.so", "getConvertMethodAndKey");
const transformKey = Module.findExportByName("libvideodec.so", "transformKey");
Interceptor.attach(getConvertMethodAndKey, {
    onEnter(args) {
        //getConvertMethodAndKey(_BYTE *a1, int a2, void **a3, signed int *a4, _DWORD *a5, size_t *a6)
        console.log("getConvertMethodAndKey called");
        console.log(hexdump(args[0], {
                    length: 40, header: true, ansi: true
                }) + "\n");
        console.log(args[1].toInt32());
console.log(hexdump(args[2], {
                    length: 40, header: true, ansi: true
                }) + "\n");
console.log(hexdump(args[3], {
                    length: 40, header: true, ansi: true
                }) + "\n");
console.log(hexdump(args[4], {
                    length: 40, header: true, ansi: true
                }) + "\n");
console.log(hexdump(args[5], {
                    length: 40, header: true, ansi: true
                }) + "\n");
console.log(hexdump(args[6], {
                    length: 40, header: false, ansi: false
                }) + "\n")},
    onLeave(retval) {
                console.log("getConvertMethodAndKey returned: " + retval);
    }
} );
Interceptor.attach(transformKey, {
    onEnter(args) {
        console.log("transformKey called");
        console.log(hexdump(args[0], {
                    length: 40, header: true, ansi: true
                }) + "\n");
        console.log(args[1].toInt32());
    },
    onLeave(retval) {
                console.log("getConvertMethodAndKey returned: " + retval);
    }
} );
然后用前面写的rpc主动调用一下,

很明显了,核心部分就是transformKey,和网页基本一致,第二个函数是解密函数
int __fastcall transformKey(int result, int a2)
{
  int v2; // r5
  int v4; // r4
  int v5; // r7
  int v6; // r6
  int v7; // r3
  int v8; // r0
  v2 = result;
  v4 = 0;
  v5 = 250;
  v6 = 85;
  while ( v4
简单还原一下和网页的对比,发现只有这一行不一样

改写一下网页的,测试,解密和app结果一样,

其他函数就不分析,感觉没用上,感兴趣看附件吧,基本还原了

然后就是某浪的,首先体积从14k变到了40多k,其次导出函数全变动态注册

看着篇安卓某app_sign逆向分析过程,一样的分析


然后进入关键函数,可以看到和之前几乎一模一样

然后就是sub_1558函数,和前面一样,传入的地址和长度,解密部分也基本一样,只不过把几个函数写在一起来了

直接抄就行了,简单改写了一下
func DECKEY(a1 []byte, a2 int) string {
   var v6, v7, v8, v9, v10, v11 int
   v6 = 0
   v7 = 0
   v8 = 0
   v9 = 0
   v10 = 0
   v11 = 0
   if a2 >= 3 {
      v6 = 1
      v7 = int(a1[0] ^ a1[1] ^ a1[2])
      v9 = a2 - v7
      v11 = v9 + 47
      if v7-48  0 {
            v19++
            v20 &= v20 - 1
         }
         if v15&1 == 0 {
            v17 = byte(v16)
         }
         v22 := (v18 ^ v17) - byte(v19)
         v17 = v21
         v12[v15] = byte(v22 - 21)
         if v15&1 == 0 {
            v16 = int(v18)
         }
         v15++
      }
      return string(v12[1 : v11-1])
   }
   return ""
}
到此基本就分析完了,不是很难适合入门练习
后记
这里只分析了解密key的过程,其余的参数官网均有说明或者SDK直接调用即可,app端也只分析了一种,还有其他的感兴趣的自行分析

附件.zip
(3.67 MB, 下载次数: 194)
2022-12-13 12:59 上传
点击文件名下载附件
下载积分: 吾爱币 -1 CB

函数, 的是

dou520dou   

感谢楼主分享
y8160000   

没看到相关的分析工具的下载地址呀,学习一下,                                                         
Yalw   

[i]
changshan   

大佬还是牛的
FIFA23   

大佬,厉害,学习中
wxg7793158   

大佬啊,可惜我看不懂唉
8970665   

膜拜大佬啊 酷
eeeeda   

牛B就是牛B
ITtongxue   

期待分享一下爱奇艺的4K加密
您需要登录后才可以回帖 登录 | 立即注册

返回顶部