吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3172|回复: 85
上一主题 下一主题
收起左侧

[Web逆向] 某网课平台m3u8 key分析以及脚本下载

  [复制链接]
跳转到指定楼层
楼主
boomx7 发表于 2024-12-5 14:09 回帖奖励
获取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)

免费评分

参与人数 21威望 +1 吾爱币 +37 热心值 +17 收起 理由
hugbug + 1 我很赞同!
hackerxj + 1 弱弱问一句,端口的注释是不是写错了?
xiaoshan208 + 1 用心讨论,共获提升!
echoofw + 1 热心回复!
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
whdfog + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
keranb + 1 谢谢@Thanks!
nshark + 1 + 1 我很赞同!
yangxfan + 1 + 1 用心讨论,共获提升!
Jackalone + 1 谢谢,学习了。
唐小样儿 + 1 + 1 我很赞同!
laozhang4201 + 1 + 1 我很赞同!
许我浅笑而安 + 2 + 1 用心讨论,共获提升!
koukoncd + 1 + 1 我很赞同!
liuxuming3303 + 1 + 1 谢谢@Thanks!
xuewujianlee + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wcu1117 + 1 我很赞同!
helian147 + 1 + 1 热心回复!
beihai1314 + 1 + 1 我很赞同!
hehehero + 1 + 1 热心回复!
SVIP008 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
SherlockProel 发表于 2024-12-5 16:54
我看懂了,先这样再那样,相当于勾股定理
推荐
koukoncd 发表于 2024-12-6 00:07
眼前一亮!!
还有油猴脚本加本地脚本组合这一招,以往遇到这种直接selenium页面模拟加抓包全套了,要么就啃论坛大佬对wasm逆向的帖子慢慢试,还是楼主这套组合拳轻量化
沙发
lxxfhtd 发表于 2024-12-5 15:49
3#
johnsonbo 发表于 2024-12-5 16:17
虽然看不懂,不过感谢分享
4#
鹿鸣 发表于 2024-12-5 16:38
发个地址学习下  地址编码下
5#
xuwei179 发表于 2024-12-5 16:47
学好数理化,走遍天下都不怕
6#
Tianshan 发表于 2024-12-5 16:49
机器可以解决的问题,决不再手动
8#
 楼主| boomx7 发表于 2024-12-5 17:29 |楼主
鹿鸣 发表于 2024-12-5 16:38
发个地址学习下  地址编码下

aHR0cHM6Ly93d3cueGlhb3FpcWlhby5jb20v
不过需要账号权限
9#
m066061 发表于 2024-12-5 17:53
不知道怎么用代码
10#
鹿鸣 发表于 2024-12-5 17:55
boomx7 发表于 2024-12-5 17:29
aHR0cHM6Ly93d3cueGlhb3FpcWlhby5jb20v
不过需要账号权限

好的谢谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-14 17:49

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表