获取m3u8 key
1.播放视频,搜索key关键字,找到请求key文件
2.下载key之后发现,是33字节文件,这明显不是正常的key(正常为16字节)
3.接下来就去js,看看是哪个程序调用这个请求,发现有个叫onkeyload的堆栈,感觉像生成key的程序,进去看看
4.把onkeyload这段设置断点,刷新浏览器并播放,发现key没获取到,
5.但发现loadsuccess这段函数,把这段也打个断点。重新播放视频,发现有16字节的key了,拿去下载试试。
6.先把这16字节转换成base64先得到:uGD7jfQ3cVBooMDg7tv7PA==
7.打开m3u8发现没补全链接,手动补全一下
8.使用逍遥一仙 大神的m3u8下载工具,成功下载。
编写批量下载脚本
- 既然要编写批量脚本,最方便的当然是尝试获取加密算法
- 继续跟踪js,多次调试发现loadsuccess,上一个堆栈为r.onsuccess,进去瞧一瞧
2.发现key是从gt这里传过来的,继续跟。
3.发现是从decoderModule来的,继续
4.跟进来发现是wasm,研究半天搞不懂算了算了。。。开摆!
- 换思路:直接从gt这截取数组,通过油猴脚本,发送base64到本地py程序,py程序用来修改key链接,最后直接把m3u8文件拖进下载器下载。
这里就直接贴代码了,代码基本来自gpt。
油猴脚本
// ==UserScript==
// @name 推送base64密码到python
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 请求完某个API,打印其中对应的全局参数
// @AuThor Your Name
// @match https://www.xiao.com/*
// @grant GM_xmlhttpRequest
// @connect *
// ==/UserScript==
(function() {
'use strict';
// 目标API地址
const targetURL = 'https://www.xiao.com/courselab/public/v1.0/course-videos:play';
// 保存原始的 XMLHttpRequest 原型方法
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
// 覆盖 open 方法
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
this._url = url; // 保存请求的 URL
return originalOpen.apply(this, arguments);
};
// 覆盖 send 方法
XMLHttpRequest.prototype.send = function(data) {
if (this._url === targetURL) {
// 监听 readyState 变化
this.addEventListener('readystatechange', function() {
if (this.readyState === 4 && this.status === 200) {
try {
setTimeout(() => {
// 将响应处理为 ArrayBuffer
const buffer = this.response;
// 将 ArrayBuffer 转换为 Uint8Array
const uint8Array = new Uint8Array(decoderModule.HEAPU8.buffer,5244968,16);
console.log('uint8Array Data:', uint8Array);
// 将 Uint8Array 转换为 Base64
const base64String = uint8ArrayToBase64(uint8Array);
// 打印 Base64 编码的数据
console.log('Base64 Data:', base64String);
// 打印videoId
console.log('Payload Data:', data);
const videoId = JSON.parse(data).videoId;
console.log('videoId:', videoId);
// 复制 Base64 数据到剪贴板
// copyToClipboard(base64String);
// 带着 Base64 字符串请求本地服务器
send(base64String,videoId);
// 你可以在此处对 Uint8Array 数据进行进一步处理
}, 500); // 延迟500毫秒
} catch (error) {
console.error('Error processing ArrayBuffer:', error);
}
}
});
// 设置 responseType 为 arraybuffer
this.responseType = 'arraybuffer';
}
// 调用原始的 send 方法
return originalSend.apply(this, arguments);
};
// 将 Uint8Array 转换为 Base64 编码的函数
function uint8ArrayToBase64(uint8Array) {
let binaryString = '';
for (let i = 0; i < uint8Array.length; i++) {
binaryString += String.fromCharCode(uint8Array[i]);
}
return btoa(binaryString);
}
// 复制文本到剪贴板的函数
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
console.log('Base64 data copied to clipboard');
}).catch(err => {
console.error('Failed to copy data to clipboard:', err);
});
}
function send(base64String,videoId) {
'use strict';
// Format data
let data = `#KEY,${base64String}\r\n`;
// Convert data to GBK encoding
let gbkEncodedData = btoa(unescape(encodeURIComponent(data)));
// Function to send data
function sendData() {
GM_xmlhttpRequest({
method: 'POST',
url: 'http://127.0.0.1:8789/modify-file',
data: JSON.stringify({
video_id: videoId,
base64_string: base64String
}),
headers: {
'Content-Type': 'application/json'
},
onload: function(response) {
console.log('Response from server:', response.responseText);
},
onerror: function(error) {
console.error('Error communicating with server:', error);
}
});
}
// Call the sendData function
sendData();
}
})();
py程序(需要手动设置文件夹)
from flask import Flask, request, jsonify
import os,re
app = Flask(__name__)
# 设置文件夹路径
TARGET_FOLDER = "E:\study\Python\m3u8" # 修改为你的目标文件夹路径
@app.route('/modify-file', methods=['POST'])
def modify_file():
# 获取请求参数
data = request.json
video_id = data.get("video_id")
base64_string = data.get("base64_string")
if not video_id or not base64_string:
return jsonify({"error": "缺少参数video_id和base64_string"}), 400
# 搜索目标文件
target_file = None
for root, dirs, files in os.walk(TARGET_FOLDER):
for file in files:
if video_id in file:
target_file = os.path.join(root, file)
break
if target_file:
break
if not target_file:
return jsonify({"error": f"No file found with video_id: {video_id}"}), 404
try:
# 读取文件内容
with open(target_file, 'r', encoding='utf-8') as f:
content = f.read()
print('搜索到文件:',target_file)
# 替换内容
modified_content = re.sub(r'URI=".*?"', f'URI="base64:{base64_string}"', content)
# 保存修改后的文件
with open(target_file, 'w', encoding='utf-8') as f:
f.write(modified_content)
return jsonify({"message": f"File {target_file} modified successfully"}), 200
except Exception as e:
return jsonify({"error": f"Failed to modify file: {str(e)}"}), 500
if __name__ == '__main__':
# 运行服务器,监听端口 8788
app.run(host='0.0.0.0', port=8789)
|