一个有意思的python软件逆向

正文

 

 

首先第一层是标准的OB加密
我们先大概规整一下代码

 

 复制代码 隐藏代码
    traverse(ast, {
        CallExpression(path) {
            if (path.node.arguments.length === 2) {
                const type0 = path.node.arguments[0].type
                const type1 = path.node.arguments[1].type
                const isLikelyNumber = (type) => {
                    return type === 'UnaryExpression' || type === 'NumericLiteral'
                }
                if ((type0 === 'StringLiteral' && isLikelyNumber(type1)) || (type1 === 'StringLiteral' && isLikelyNumber(type0))) {
                    const funcBinding = path.scope.getBinding(path.node.callee.name)
                    const funcNode = funcBinding.path.node
                    if (funcNode?.params?.length !== 2) {
                        return
                    }
                    if (funcNode.body.body.length !== 1) {
                        return
                    }
                    if (funcNode.body.body[0].type !== 'ReturnStatement') {
                        return
                    }
                    const funcArgs0 = funcNode.params[0].name
                    const funcArgs1 = funcNode.params[1].name
                    const bodyCallArgs = funcNode.body.body[0].argument.arguments
                    let isSwap = false
                    for (let index = 0; index < bodyCallArgs.length; index++) {
                        const item = bodyCallArgs[index];
                        if (item.type === 'Identifier') {

                            if (item.name === funcArgs0 && index === 1) {
                                isSwap = true
                            } else if (item.name === funcArgs1 && index === 0) {
                                isSwap = true
                            }
                            break;
                        }
                    }
                    const handleExpression = (bodyExpress, argsIdentifier) => {
                        if (bodyExpress.type !== 'BinaryExpression') {
                            return argsIdentifier
                        }
                        const handleIdentifier = (item) => {
                            if (item.type !== 'Identifier') {
                                return item
                            } else {
                                return argsIdentifier
                            }
                        }
                        const numAst = types.binaryExpression(bodyExpress.operator, handleIdentifier(bodyExpress.left), handleIdentifier(bodyExpress.right))
                        const numResult = eval(generator(numAst).code)
                        return types.numericLiteral(numResult)
                    }
                    const firstIdentifier = path.node.arguments[0]
                    const secondIdentifier = path.node.arguments[1]
                    let newCalleeArgs = [handleExpression(bodyCallArgs[0], isSwap ? secondIdentifier : firstIdentifier), handleExpression(bodyCallArgs[1], isSwap ? firstIdentifier : secondIdentifier)]
                    let newNode = types.callExpression(funcNode.body.body[0].argument.callee, newCalleeArgs);
                    path.replaceInline(newNode)
                }
            }
        },
    });

然后获取解密的函数,这里因为比较偷懒,所以直接使用了正则表达式计算关键函数

 复制代码 隐藏代码
function generatorHandleCrackStringFunc(text) {
    const matchResult = text.match(/\d{4,}\);\s?(function.*),\s?[A-Za-z].[A-Za-z]\s?=\s?[A-Za-z]/)
    if (matchResult.length !== 2) {
        throw new Error('代码解析失败!')
    }
    const funcName = matchResult[1].match(/function ([A-Za-z])\([A-Za-z],\s?[A-Za-z]\).*(?=abc)/)[1]
    return {
        crackName: funcName,
        crackCharFunc: new Function([], matchResult[1] + ';return function(num,char){return ' + funcName + '(num, char)}')()
    }
}

然后调用解密函数

 复制代码 隐藏代码
    traverse(ast, {
        CallExpression(path) {
            if (path.node.arguments.length === 2) {
                if (path.node.callee.name !== name) {
                    return
                }
                if (path.node.arguments[0].type !== 'NumericLiteral') {
                    return;
                }
                if (path.node.arguments[1].type !== 'StringLiteral') {
                    return;
                }
                const nodeResult = handleStringFunc(path.node.arguments[0].value, path.node.arguments[1].value)
                path.replaceInline(types.stringLiteral(nodeResult))
            }
        },
    });

然后对解密后的字符串和数字等做一下合并

 复制代码 隐藏代码
    const handleObfs = {
        CallExpression: {
            exit(outerPath) {
                const node = outerPath.node.callee
                const parentPath = outerPath
                if (node?.object?.type === 'Identifier' && node?.property?.type === 'StringLiteral') {
                    const objBinding = outerPath.scope.getBinding(node.object.name)
                    if (objBinding === undefined) {
                        return;
                    }
                    const objNode = objBinding.path.node
                    const funcList = objNode.init?.properties ?? []
                    const funcInstance = funcList.find((item) => {
                        const keyName = item.key.name
                        return keyName === node.property.value
                    })
                    if (funcInstance) {
                        const parentNode = parentPath.node

                        let replaceAst = null
                        if (funcInstance.value.type === 'FunctionExpression') {
                            const originNode = funcInstance.value.body.body[0].argument
                            //函数
                            if (originNode.type === 'CallExpression') {
                                replaceAst = types.callExpression(parentNode.arguments[0], [...parentNode.arguments].splice(1))
                            } else if (originNode.type === 'BinaryExpression') {
                                replaceAst = types.binaryExpression(originNode.operator, parentNode.arguments[0], parentNode.arguments[1])
                            }
                        } else {
                            //字符串
                            debugger
                            replaceAst = types.stringLiteral(funcInstance.value.value)
                        }
                        if (replaceAst) {
                            parentPath.replaceWith(replaceAst)

                        }

                    }
                }
            }
        },
        MemberExpression: {
            enter(path) {
                const node = path.node
                if (node?.object?.type === 'Identifier' && node?.property?.type === 'StringLiteral') {
                    const objBinding = path.scope.getBinding(node.object.name)
                    if (objBinding === undefined) {
                        return;
                    }
                    const objNode = objBinding.path.node
                    const funcList = objNode.init?.properties ?? []
                    const funcInstance = funcList.find((item) => {
                        const keyName = item.key.name
                        return keyName === node.property.value
                    })
                    if (funcInstance) {
                        let replaceAst = null
                        if (funcInstance.value.type === 'StringLiteral') {
                            replaceAst = types.stringLiteral(funcInstance.value.value)
                        }
                        if (replaceAst) {
                            path.replaceWith(replaceAst)
                        }

                    }
                }
            }
        }
    }

    traverse(ast, handleObfs);

我们可以从已经解密的文件里提取一些关键字符串

 复制代码 隐藏代码
    const mathRsult = code.match(/\[\"(.*)\", [a-zA-Z]\[\"time\"\][\s\S]*\[\"sign\"\] = \[\"([0-9]*)\".*function \(([a-zA-Z])\) {([\s\S]*)}\([a-zA-Z]\)\,.*?"([a-zA-Z0-9]{3,})"/)
    if (mathRsult.length !== 6) {
        throw new Error('密钥解析失败!')
    }
    const signPrefix = mathRsult[2]
    const signEnd = mathRsult[5]
    const prefixToken = mathRsult[1]
    const hashFunc = new Function(mathRsult[3], mathRsult[4])

接下来直接调试可以解出来BCToken的算法

 复制代码 隐藏代码
    function generateBcToken() {
        if (bcToken !== "") {
            return bcToken
        }
        const V = () => 1e12 * Math.random()
        const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
        const hash = sha1.create();
        const text = [(new Date).getTime(), V(), V(), UA].map(btoa).join(".")
        console.log(text)
        hash.update(text);
        bcToken = hash.hex()
        return bcToken
    }

Sign加密算法也可以解出来了

 复制代码 隐藏代码
    function generateSha({ url, auth_id }) {
        const fixPrefix = prefixToken;
        let time = +new Date();
        const toeknURL = [fixPrefix, time, url, auth_id || 0].join(`\n`);
        const hash = sha1.create();
        hash.update(toeknURL);
        return {
            token: hash.hex(),
            time: time
        }
    }
       function  getSign({ url, auth_id }) {
            const { time, token } = generateSha({ url, auth_id })
            return {
                sign: [signPrefix, token, hashFunc(token), signEnd].join(':'),
                time: time
            }
        }

那基本的算法解密就搞定了,但是最近还更新了DRM

 

 

其中给了一个mpt和m3u8
分别有不同的密钥
根据测试DRM的密钥是需要写在Cookies里的
但是诡异的事情来了
postman可以测试成功,cmd测试失败,代码测试失败,powershell测试成功
ffmpeg测试也失败

 

我的第一反应可能是TLS指纹校验了
这部分事后发现1.1也可以了,只要同ip就行,我也不确定到底是我测试错误还是后期改了
所以这部分可以直接忽略,但是因为我自己觉得补上HTTP2的代码有利于思路的连贯性分析和大家下次直接抄轮子
思虑之后决定保留了下来
于是在https://github.com/nodejs/undici/issues/1983
抄了一段,改成OF网站的,这里就按下不表了

 复制代码 隐藏代码
本站资源来自互联网收集,仅提供信息发布
一旦您浏览本站,即表示您已接受以下条约:
1.使用辅助可能会违反游戏协议,甚至违法,用户有权决定使用,并自行承担风险;
2.本站辅助严禁用于任何形式的商业用途,若被恶意贩卖,利益与本站无关;
3.本站为非营利性网站,但为了分担服务器等运营费用,收费均为赞助,没有任何利益收益。
死神科技 » 一个有意思的python软件逆向

死神科技,因为专业,所以领先。

网站首页 24小时自动发卡
在线客服
24小时在线客服
阿里云自动发卡,购卡进群售后
12:01
您好,有任何疑问请与我们联系!

选择聊天工具: