获取m3u8 key
1.播放视频,搜索key关键字,找到请求key文件
搜索key
2.下载key之后发现,是33字节文件,这明显不是正常的key(正常为16字节)
key文件信息
3.接下来就去js,看看是哪个程序调用这个请求,发现有个叫onkeyload的堆栈,感觉像生成key的程序,进去看看
js调试1
4.把onkeyload这段设置断点,刷新浏览器并播放,发现key没获取到,
获取key1
5.但发现loadsuccess这段函数,把这段也打个断点。重新播放视频,发现有16字节的key了,拿去下载试试。
获取key2
6.先把这16字节转换成base64先得到:uGD7jfQ3cVBooMDg7tv7PA==
7.打开m3u8发现没补全链接,手动补全一下
m3u8文件
m3u8文件2
8.使用逍遥一仙 大神的m3u8下载工具,成功下载。
编写批量下载脚本
- 既然要编写批量脚本,最方便的当然是尝试获取加密算法
- 继续跟踪js,多次调试发现loadsuccess,上一个堆栈为r.onsuccess,进去瞧一瞧
2.发现key是从gt这里传过来的,继续跟。
3.发现是从decoderModule来的,继续
4.跟进来发现是wasm,研究半天搞不懂算了算了。。。开摆!
- 换思路:直接从gt这截取数组,通过油猴脚本,发送base64到本地py程序,py程序用来修改key链接,最后直接把m3u8文件拖进下载器下载。
这里就直接贴代码了,代码基本来自gpt。
油猴脚本
(function() {
'use strict';
const targetURL = 'https://www.xiao.com/courselab/public/v1.0/course-videos:play';
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
this._url = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(data) {
if (this._url === targetURL) {
this.addEventListener('readystatechange', function() {
if (this.readyState === 4 && this.status === 200) {
try {
setTimeout(() => {
const buffer = this.response;
const uint8Array = new Uint8Array(decoderModule.HEAPU8.buffer,5244968,16);
console.log('uint8Array Data:', uint8Array);
const base64String = uint8ArrayToBase64(uint8Array);
console.log('Base64 Data:', base64String);
console.log('Payload Data:', data);
const videoId = JSON.parse(data).videoId;
console.log('videoId:', videoId);
send(base64String,videoId);
}, 500);
} catch (error) {
console.error('Error processing ArrayBuffer:', error);
}
}
});
this.responseType = 'arraybuffer';
}
return originalSend.apply(this, arguments);
};
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';
let data = `#KEY,${base64String}\r\n`;
let gbkEncodedData = btoa(unescape(encodeURIComponent(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);
}
});
}
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__':
app.run(host='0.0.0.0', port=8789)
|