上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

onlyfans的OB解密及DRM过校验思路(六)

[复制链接]
  • TA的每日心情
    擦汗
    2024-7-16 09:20
  • 签到天数: 192 天

    [LV.7]常住居民III

    691

    主题

    5563

    回帖

    6462

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6462

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2024-6-27 16:00:27 | 显示全部楼层 | 阅读模式

    之前我们已经实现了python的DRM视频解密
    但是我是nodejs,因为之前已经写了大量的爬虫代码
    并且由于js目前没找到cdm的解密库
    所以干脆考虑自己封一下
    我决定把之前cdm解密的python代码抽象一下
    并且引入flask,通过pyinstaller打包成exe
    然后封装一个nodejs的库唤起,本地服务器如果一定时间没有心跳就自动销毁
    首先封装一下python的flask代码

    from flask import Flask
    from flask import request
    from flask import jsonify
    from threading import Timer
    from inspect import signature
    import threading
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import argparse
    import time
    import os
    import socket
    import signal
    import requests
    
    parser = argparse.ArgumentParser(description='command', formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('--autoClose', '-c', help='是否自动关闭,默认为300s,设置为0则不自动关闭',default='300')
    parser.add_argument('--port', '-p', help='设置端口号')
    args = parser.parse_args()
    args.autoClose=int(args.autoClose)
    
    cdmInstance=None
    
    app = Flask(__name__)
    PID = os.getpid()
    
    @app.route("/ping",methods=["GET"])
    def ping():
        print('run ping')
        closeServer()
        return jsonify(status="success")
    
    @app.route("/close",methods=["GET"])
    def close():
        shutdown()
        return jsonify(status="success")
    
    def debounce(wait):
        def decorator(fn):
            sig = signature(fn)
            caller = {}
    
            def debounced(*args, **kwargs):
                nonlocal caller
    
                try:
                    bound_args = sig.bind(*args, **kwargs)
                    bound_args.apply_defaults()
                    called_args = fn.__name__ + str(dict(bound_args.arguments))
                except:
                    called_args = ''
    
                t_ = time.time()
    
                def call_it(key):
                    try:
                        # always remove on call
                        caller.pop(key)
                    except:
                        pass
    
                    fn(*args, **kwargs)
    
                try:
                    # Always try to cancel timer
                    caller[called_args].cancel()
                except:
                    pass
    
                caller[called_args] = Timer(wait, call_it, [called_args])
                caller[called_args].start()
    
            return debounced
    
        return decorator
    
    @app.route("/loadDevice",methods=["POST"])
    def loadDevice():
        global cdmInstance
        form = request.form
        device=None
        print(form.get("path"))
        try:
            device = Device.load(form.get("path"))
        except:
            return jsonify(status="error")
        cdmInstance = Cdm.from_device(device)
        return jsonify(status="success")
    
    @app.route("/getKeys",methods=["POST"])
    def getKeys():
        form = request.form
        license_url = form.get("url")
        headers= form.get("headers")
        pssh= form.get("pssh")
        pssh_value = PSSH(pssh)
        cdm_session_id = cdmInstance.open()
        challenge = cdmInstance.get_license_challenge(cdm_session_id, pssh_value)
        licence = requests.post(
            license_url, data=challenge
        )
        licence.raise_for_status()
        cdmInstance.parse_license(cdm_session_id, licence.content)
        keys = []
        for key in cdmInstance.get_keys(cdm_session_id):
            if "CONTENT" in key.type:
                keys.append({
                    "kid":key.kid.hex,
                    "key":key.key.hex()
                })
        cdmInstance.close(cdm_session_id)
        return jsonify(status="success",data=keys)
    
    def shutdown():
        if args.autoClose==0:
            return
        print('自动销毁')
        os._exit(1)
    
    @debounce(args.autoClose)
    def closeServer():
        shutdown()
    
    @app.errorhandler(Exception)
    def framework_error(e):
        print(e)
        return jsonify(status="error")
    
    if __name__ == '__main__':
        if args.port==None:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.bind(('localhost', 0))
            args.port = sock.getsockname()[1]
            sock.close()
        closeServer()
        app.run(host='0.0.0.0',port= args.port)
    

    很简单,然后我们打包成exe,再写一下nodejs的库代码

    const { default: axios } = require("axios");
    const { spawn } = require("child_process");
    const net = require("net");
    const path = require('path')
    const { exec } = require('child_process');
    const querystring = require('querystring');
    
    function sleep(time) {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve()
            }, time)
        })
    }
    exports.openCDMServer = async function openCDMServer(option) {
        let port = option.port
        const wvdPath = option.wvdFullPath
    
        if (port === undefined) {
            port = await getPortFree()
        }
    
        const portOccupyStatus = await checkPortOccupy(port)
        if (!portOccupyStatus) {
            //no use!
            exec(path.join(__filename, '../cdmServer.exe')+' --port '+port, (error, stdout, stderr) => {
                if (error) {
                    console.error(`exec error: ${error}`);
                    return;
                }
                console.log(`stdout: ${stdout}`);
                console.error(`stderr: ${stderr}`);
            });
        }
        let serverOpen = false
        const serverAddr = 'http://127.0.0.1:' + port
        for (let index = 0; index < 60; index++) {
            try {
                const { data } = await axios.get(serverAddr + '/ping')
                if (data?.status === 'success') {
                    serverOpen = true;
                    break;
                }
            } catch (error) {
                await sleep(1000)
                continue;
            }
            await sleep(1000)
        }
        if (!serverOpen) {
            return {
                status: 'error',
                content: "server open failed!"
            }
        }
        const timer = setInterval(async () => {
            try {
                const { data } = await axios.get(serverAddr + '/ping')
                if (data?.status === 'success') {
                    serverOpen = true;
                }
            } catch (error) {
                console.log('heart:the cdm server is loss')
            }
        }, 60 * 1000)
    
        const closeFunc = () => {
            clearInterval(timer)
            axios.get(serverAddr + '/close')
        }
    
        let loadWvdStatus = false
    
        try {
            const { data } = await axios.post(serverAddr + '/loadDevice', querystring.stringify({
                path: wvdPath
            }))
            if (data?.status === 'success') {
                loadWvdStatus = true;
            }
        } catch (error) {
            console.log('loadWvd Post Error')
        }
        if (!loadWvdStatus) {
            closeFunc()
            return {
                content: "wvd load Error",
                status: "error"
            }
        }
        async function getKeys(url, pssh, headers) {
            return axios.post(serverAddr + '/getKeys', querystring.stringify({
                url,
                pssh,
                headers
            }))
        }
        return {
            close: closeFunc,
            port: port,
            status: "success",
            getKeys: getKeys
        }
    }
    
    function checkPortOccupy(port) {
        return new Promise((resolve, reject) => {
            const server = net.createConnection({ port });
            server.on('connect', () => {
                server.end();
                resolve(true);
            });
            server.on('error', () => {
                resolve(false);
            });
        });
    }
    
    async function getPortFree() {
        return new Promise(res => {
            const srv = net.createServer();
            srv.listen(0, () => {
                const port = srv.address().port
                srv.close((err) => res(port))
            });
        })
    }

    由于我们还没上传到npm,这个时候需要通过本地软连接测试,修改package.json中的name属性
    然后在库项目输入npm link
    紧接着在测试的项目中输入 npm link 项目名即可实现本地导入
    接下来我们写一下测试代码,其中node-widevine-decrypt就是我软连接库的名字

        const { openCDMServer } = require('node-widevine-decrypt')
        const path = require('path')
    
        async function main() {
            const { getKeys, port, status } = await openCDMServer({
                wvdFullPath: path.join(__filename, '../aosp.wvd')
            })
            if (status === 'success') {
                const {data} =await getKeys("URL地址","pssh数据")
                console.log(data)
            } else {
                console.log('server error')
            }
    
        }
        main()

    跑一下看看,发现成功解密~
    图片.png

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。

    发表回复

    本版积分规则

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