咪咕视频m3u8地址解析及ddCalcu参数加密逆向

news/2024/11/14 12:59:10/文章来源:https://www.cnblogs.com/kiradiana/p/18354802

咪咕视频m3u8地址解析及ddCalcu参数加密逆向

概述

本文主要讲述咪咕视频m3u8地址的解析以及使用Wasm对视频的m3u8地址进行加密得到ddCalcu参数的方法。

使用视频ID获取未加密的视频URL

对咪咕视频进行抓包发现,通过接口

https://webapi.miguvideo.com/gateway/playurl/v3/play/playurl?contId=926412678&rateType=3&xh265=true&chip=mgwww&channelId=0132_10010001005

可以获取到视频的m3u8地址,接口的参数contId指定视频ID,这里是926412678,rateType指定视频分辨率,2为标清540P,3为高清720P,4为超清1080P(但超清一般只有前6分钟的试看时间)。

访问此接口需要使用GET方法,并设定以下请求头:

Appcode: miguvideo_default_www
Appid: miguvideo
Channel: H5
x-up-client-channel-id: 0132_10010001005

接口返回JSON,JSON的body->urlInfo->url键值就是视频的m3u8地址

http://gslbmgspvod.miguvideo.com/depository_nas03/asset/zhengshi/5105/544/165/5105544165/media/5105544165_5270812321_56.mp4.m3u8?msisdn=20240811111225909a4fdfb1964b7b802c2c47241b024c&mdspid=&spid=800033&netType=0&sid=5700895378&pid=2028597139&timestamp=20240811111225&Channel_ID=0132_10010001005&ProgramID=926412678&ParentNodeID=-99&assertID=5700895378&client_ip=183.6.24.190&SecurityKey=20240811111225&promotionId=&mvid=5105544165&mcid=1003&mpid=120000510768&playurlVersion=WX-A1-7.7.2.5-RELEASE&userid=&jmhm=&videocodec=h264&bean=mgspwww&puData=41a05276e61f2220e3a57b9eddf625a8

若直接访问获取到的m3u8地址并不能正常得到m3u8文件,需要对其进行加密最终得到加密后的m3u8地址

http://gslbmgspvod.miguvideo.com/depository_nas03/asset/zhengshi/5105/544/165/5105544165/media/5105544165_5270812321_56.mp4.m3u8?msisdn=20240811195035d062a3247070440fa88a380accb49b6a&mdspid=&spid=800033&netType=0&sid=5700895378&pid=2028597139&timestamp=20240811195035&Channel_ID=0132_10010001005&ProgramID=926412678&ParentNodeID=-99&assertID=5700895378&client_ip=183.6.24.190&SecurityKey=20240811195035&promotionId=&mvid=5105544165&mcid=1003&mpid=120000510768&playurlVersion=WX-A1-7.7.2.5-RELEASE&userid=&jmhm=&videocodec=h264&bean=mgspwww&puData=021d5e2e680350a1d51a47bdb1cec090&ddCalcu=0092z01ycdwe5zce12bed6b87043a5105ad1&sv=10000&ct=www

加密后的URL主要是多了ddCalcu参数,接下来就要知道网站是如何进行加密的。

视频URL的加密方法

既然ddCalcu是加密得到的参数,首先想到的是直接搜索这个关键词,发现在pcPlayer.js文件中能找到

打个断点调试,刷新网页发现这行代码没有被执行,看来这个并不是用于加密的代码。

换个思路,找到加密后的m3u8地址的网络请求,查看这个请求的调用栈并进行断点调试,最后在pcPlayer.js中误打误撞找到了用于加密的代码

这段代码中的var n = r._getEncrypt(l)十分可疑,很可能就是加密函数,打断点逐行调试这段代码,逐个查看这里的变量,结果发现在执行_getEncrypt函数前,这里的变量t就是未加密的URL

当执行_getEncrypt函数以及i = r.UTF8ToString(n)这行代码后,i变量就得到了加密后的URL(带有ddCalcu参数)

到这里基本就可以确定这就是用于加密URL的代码了。

继续查看_getEncrypt函数实现,发现这是个wasm函数,wasm文件名是pickproof1000.wasm,这就对了,pickproof就是防盗的意思,这也说明URL是使用wasm进行加密的。

既然URL是用wasm进行加密的,那么在加密前就必须先加载wasm模块,只要找到加载wasm模块的代码,从加载开始一步步调试,抽丝剥茧,应该就不难找到URL加密的方法了。

直接在pcPlayer.js中搜索WebAssembly.instantiate找到WebAssembly.instantiateStreaming(e, t).then(n, ...,这是用于创建wasm实例的函数,直接断点调试

这里wasm在实例化时导入了10个js函数,这些函数虽然参数列表不同,但逐个查看发现均返回0或无返回值,应该是用于混淆代码的。

此外,在wasm实例化后又立即将wasm的Module和Instance对象穿给函数n

函数n又将wasm实例对象传给了函数r

函数r中,变量t是wasm实例,wasm实例导出的对象赋值给了e.asm,变量y是从wasm实例中导出的对象k,结合pickproof1000.wasm的代码(memory $k (;0;) (export "k") 256 256)可知导出的对象k实际上是wasm实例中一段初始大小和最大大小均为256页的线性内存,由于wasm每页内存大小固定为64KB,所以导出的内存大小为16777216字节,紧接着非本地变量be.HEAPU8被赋值为可以读写此内存的Uint8Array数组。

function r(t, r) {  // t是WebAssembly.Instance对象var n, i, o = t.exports;e.asm = o,  // wasm实例导出的对象y = e.asm.k,  // wasm导出的对象k,查看wasm源码可知其实是wasm内存n = y.buffer,e.HEAPU8 = b = new Uint8Array(n)  // 得到可读写wasm内存的Uint8Array数组// 省略一些代码
}

接着就开始执行加密代码了,首先是l = r._malloc(4 * t.length + 1),这里的变量t是未加密的URL,_malloc函数是wasm实例中导出的p函数,这行代码的作用是在wasm内存中申请特定大小的空间,然后返回申请得到空间的起始地址,即变量l,这里得到的地址是5266520。

// 加密代码
l && r._free(l),
l = r._malloc(4 * t.length + 1),  // 申请空闲的wasm内存,返回起始地址
r.stringToUTF8(t, l, 4 * length + 1);  // 将未加密的URL写入刚刚申请的空闲内存
var n = r._getEncrypt(l)  // , i = r.UTF8ToString(n);

随后执行r.stringToUTF8(t, l, 4 * t.length + 1),这行代码的作用是将未加密的URL的ASCII码逐一写入刚刚申请得到的wasm实例的内存,其写入的起始地址是前面通过_malloc申请内存得到的起始地址,而这具体读写wasm内存的操作正是通过读写前面在r函数中赋值给变量b的Uint8Array数组实现的。

然后就是var n = r._getEncrypt(l)_getEncrypt实际上是wasm实例中导出的m函数,向加密函数传入wasm实例内存中未加密URL的起始地址,用于指定URL在内存中的位置,得到完成加密后的URL的起始地址n,这里得到的是5272888,至于其具体的算法是如何实现的不用关心。

最后是i = r.UTF8ToString(n),向UTF8ToString函数传入完成加密后的URL在wasm实例内存中的起始地址,该函数能通过给出的起始地址提取出加密后的URL,然后赋值给变量i

另外,这里加密代码的r对象就是前面在r函数中的非本地变量er对象的HEAPU8就是r函数中的e.HEAPU8,也就是可以用于读写wasm实例内存的Uint8Array数组。

在内存查看器中查看可以看到wasm实例的内存,定位到加密URL的起始地址可以直接看到加密后的URL。

总结,URL加密的过程是这样的,在加载完wasm模块后进行一些变量的初始化,接着调用_malloc申请用于存放未加密URL的wasm内存,得到空闲内存的起始地址,再通过stringToUTF8函数将未加密的URL逐字符写入申请得到的空闲内存,然后调用_getEncrypt对wasm内存中未加密的URL进行加密,得到完成加密后的URL再wasm内存中的地址,最后通过UTF8ToString获取加密后的URL地址。

其实不使用_malloc申请wasm内存,直接将空闲内存的起始地址设为0,将URL存放在内存的最开头也是可行的。

代码实现

javascript实现

const importObj = {a: {a: (a,b,c)=>{},b: (a)=>{return 0}, c: ()=>{},d: (a,b,c,d)=>{return 0},e: (a)=>{return 0},f: (a,b,c,d,e)=>{return 0},g: (a,b)=>{return 0},h: (a,b)=>{return 0},i: (a)=>{return 0},j: (a,b,c,d,e)=>{return 0}}
}
// Load wasm module and get wasm instance.
const wasmURL = "https://www.miguvideo.com/mgs/player/prd/v_20240727173237_cfa34b34/dist/pickproof1000.wasm"
const wasm = await WebAssembly.instantiateStreaming(fetch(wasmURL), importObj).then(w => w.instance)// Get wasm memory.
const memory = wasm.exports.k
const memoryView = new Uint8Array(memory.buffer)// Get encryption function.
const getEncrypt = wasm.exports.mfunction encrypt(url) {// Write the origin URL into the wasm memory.let i;for (i = 0; i < url.length; ++i) {memoryView[i] = url.charCodeAt(i)}memoryView[i] = 0// Get the beginning index of encrypted URL in wasm memory.let start = getEncrypt(0)// Read the encrypted URL from wasm memory.encryptedURL = ""for (let i = start; memoryView[i] != 0; ++i) {encryptedURL += String.fromCharCode(memoryView[i])}return encryptedURL
}var vid = 926412678
var rateType = 3
var apiURL = `https://webapi.miguvideo.com/gateway/playurl/v3/play/playurl?contId=${vid}&rateType=${rateType}&xh265=true&chip=mgwww&channelId=0132_10010001005`
var resp = await fetch(apiURL, {headers: {"Appcode": "miguvideo_default_www","Appid": "miguvideo","Channel": "H5","x-up-client-channel-id": "0132_10010001005",}
})var videoURL = (await resp.json()).body.urlInfo.url
var encryptedURL = encrypt(videoURL)
console.log(encryptedURL)

python实现

需要先安装以下三个包

requests
wasmer
wasmer_compiler_cranelift

测试代码:

import requests
from wasmer import Store, Function, Module, Instancedef a(a: int, b: int, c: int): pass
def b(a: int) -> int: return 0
def c(): pass
def d(a: int, b: int, c: int, d: int) -> int: return 0
e = b
def f(a: int, b: int, c: int, d: int, e: int) -> int: return 0
def g(a: int, b: int) -> int: return 0
h = g
i = e
j = fstore = Store()
import_obj = {'a': {'a': Function(store, a),'b': Function(store, b),'c': Function(store, c),'d': Function(store, d),'e': Function(store, e),'f': Function(store, f),'g': Function(store, g),'h': Function(store, h),'i': Function(store, i),'j': Function(store, j),},
}wasm_url = 'https://www.miguvideo.com/mgs/player/prd/v_20240727173237_cfa34b34/dist/pickproof1000.wasm'
wasm_binary = requests.get(wasm_url).content
module = Module(store, wasm_binary)
wasm = Instance(module, import_obj)
memory = wasm.exports.k
memory_view = memory.uint8_view()def encrypt(url: str):for i,c in enumerate(url.encode()):memory_view[i] = cstart = wasm.exports.m(0)encrypted_url = ''i = startwhile memory_view[i]:encrypted_url += chr(memory_view[i])i += 1return encrypted_urldef get_url(vid, rate_type=3):channel_id = '0132_10010001005'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4343.0 Safari/537.36 Edg/89.0.727.0','Appcode': 'miguvideo_default_www','Appid': 'miguvideo','Channel': 'H5','x-up-client-channel-id': channel_id,}url = f'https://webapi.miguvideo.com/gateway/playurl/v3/play/playurl?contId={vid}&rateType={rate_type}&xh265=true&chip=mgwww&channelId={channel_id}'resp = requests.get(url, headers=headers)video_url = resp.json()['body']['urlInfo']['url']return video_urldef main():video_id = 926412678video_url = get_url(video_id)encrypted_url = encrypt(video_url)print(encrypted_url)if __name__ == '__main__':main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/781734.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vs+Qt添加ui子页面

在Qt项目中,如果要实现一个弹出子界面并包含返回按钮的功能,最合适的选择是使用 Qt Dialog Form File 来创建子界面。 Qt Dialog Form File 与 Qt Widget Form File 的区别 Qt Dialog Form File (Button Bottom) 和 Qt Dialog Form File (Button Right): 这两种文件类型用于创…

ubuntu 22.04 安装 docker(服务器从毛胚到精装)

1、用户操作 阿里云默认是 root 用户,我们一般要自己创建一个用户,然后给该用户 sudo 权限 添加用户 sudo adduser newUserName赋予sudo权限 sudo usermod -aG sudo newUserName删除用户 sudo deluser --remove-home --remove-all-files newUserName切换用户 sudo su - newUs…

Mac os的防火墙导致开的热点手机连不上

在工位上用Mac给手机开热点用,结果今天手机一直连不上Mac开的热点,最后把Mac的防火墙关了就能让手机连上了,连上了再把防火墙打开也不影响连接。

k8s技术总结(一)

在学习完docker之后,也很有必要再学习k8s的一些理论和技术。 一、基础概念 kubernetes(k8s)和docker在容器管理领域具有不同的侧重点和优势。当前docker主要关注单个容器的构建、运行和管理,而k8s则专注于大规模的容器编排和管理。k8s在以下几个特性中,更具有优势。 特性1…

TIM-定时器

TIM简介定时器类型

C# 创建 Windows Service 项目

C# 创建 WindowsService 服务项目空白处,右键选择“添加安装程序”可以看到两个控件,点击右击第一个控件,打开属性设计器,将其中的Account设置为LocalSystem(本地服务),接下来,右击上面的第二个控件,打开属性界面,设置ServiceName,和将其中的StratType设置为Automatic…

【日记】我也想喝 AD 钙(978 字)

正文周五写了一整天学海计划,周六跟了一天的编曲教程,今天则是把第三章剩下的教程跟完。最后,回旋曲写是写出来了,就是把自己听笑了。写得实在太烂了。昨天晚上买了回来的机票。9 月 10 号一早。该说不说机票是真的贵啊……同时,我发现我过去的机票,执飞飞机是波音 737-8…

Kubernetes-二进制高可用部署v1.23.x

目录高可用架构k8s集群组件ectdkube-apiserverkube-schedulerkube-controller-managerkubeletkube-proxykubectl高可用分析负载均衡节点设计1.环境准备1.1 环境规划1.2 所有节点配置host解析1.3 安装必备工具1.4 所有节点关闭防火墙、selinux、dnsmasq、swap1.5 Master01节点免…

使用Pandas和NumPy实现数据获取

公众号本文地址:https://mp.weixin.qq.com/s/Uc4sUwhjLTpOo85ubj0-QA以某城市地铁数据为例,通过提取每个站三个月15分钟粒度的上下客量数据,展示Pandas和Numpy的案例应用。数据:http://u6v.cn/5W2i8H http://u6v.cn/6hUVjk初步发现数据有三个特点::1、地铁数据的前五行是…

Datawhale X 魔搭 AI夏令营(二)

一.AI生图的伦理与道德 1.虽然AI生图的能力强大,但是极易被使用在不正当的场景,未来的挑战不仅仅是技术的突破,更有攻防技术的跟进。 二.使用通义千问工具对内核代码进行解析,更快速的搭建代码 1.通义千问是具有信息查询、语言理解、文本创作等多能力的AI助手 2.使用方法三…

docker 详细教程(通俗易懂,带有应用示例)

1、Docker 基本概念 什么是 Docker? Docker 是一个开源的容器化平台,允许开发者封装他们的应用程序及其所有依赖项到一个标准化的单元中,这个单元被称为“容器”。容器可以在任何支持 Docker 的环境中运行,从而确保应用程序的可移植性和一致性。 Docker 的优势一致性和可移…