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

ast解密of及Container is falsy解决

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    657

    主题

    5330

    回帖

    6218

    积分

    管理员

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

    积分
    6218

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

    发表于 2023-9-20 15:26:56 | 显示全部楼层 | 阅读模式

    观察结构发现有多层字符串解密赋值
    图片.png
    其本质多层调用函数,我们可以对CallExpression做解析
    判断为字符串和数字的结构下,就通过scope找到原函数
    判断函数是否是一个return,然后调用了函数继续传入
    如果符合条件就进行拍平
    直接上代码

    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)
                }
            }
        },
    });

    这样变成了对字符串解密函数的直接引用
    图片.png
    我们直接对字符串解密进行处理
    crackCharFunc直接对抄网页中的解密函数做抽取即可

    traverse(ast, {
        CallExpression(path) {
            if (path.node.arguments.length === 2) {
                if (path.node.callee.name !== 'e') {
                    return
                }
                if (path.node.arguments[0].type !== 'NumericLiteral') {
                    return;
                }
                if (path.node.arguments[1].type !== 'StringLiteral') {
                    return;
                }
                const nodeResult = crackCharFunc(path.node.arguments[0].value, path.node.arguments[1].value)
                path.replaceInline(types.stringLiteral(nodeResult))
            }
        },
    });

    这个时候还存在花指令混淆
    图片.png
    判断CallExpression和MemberExpression函数,在符合的条件下重写花指令,去除花指令的函数调用

    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);

    一开始我遍历MemberExpress对象触发了Container is falsy
    因为n["Pvaqs"](n["Pvaqs"])需要先遍历里层后遍历外层
    所以需要exit,但是如果使用MemberExpress,此时里层不存在层级
    所以会直接替换,然后内层的再被遍历时已经丧失了上下文
    所以一定要注意遍历的节点,确保需要遍历的节点一定保持正确的前后顺序
    为了让里面的节点被先遍历
    我将MemberExpress改成了CallExpression来进行递归函数的解析,往上挪了一层
    此时因为有内层节点,可以触发exit先遍历内层后遍历外层

    这时就得到了正确解密的内容
    图片.png

    已有1人评分好评 油猫币 理由
    wyn665817 + 1 + 6 ggnb!

    查看全部评分 总评分:好评 +1  油猫币 +6 

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

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    开心
    2024-3-13 10:14
  • 签到天数: 211 天

    [LV.7]常住居民III

    296

    主题

    3993

    回帖

    3887

    积分

    管理员

    积分
    3887

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

    发表于 2023-9-20 16:29:11 | 显示全部楼层
    太强了,看不懂,根本看不懂
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。/ 微信公众号:一之哥哥
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    昨天 06:23
  • 签到天数: 668 天

    [LV.9]以坛为家II

    30

    主题

    537

    回帖

    1436

    积分

    荣誉开发者

    积分
    1436

    荣誉开发者新人进步奖油中2周年生态建设者新人报道挑战者 lv2油中3周年喜迎中秋

    发表于 2023-9-20 18:16:31 | 显示全部楼层
    太强了,看不懂,根本看不懂
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    657

    主题

    5330

    回帖

    6218

    积分

    管理员

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

    积分
    6218

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

    发表于 2023-9-20 21:29:15 | 显示全部楼层
    王一之 发表于 2023-9-20 16:29
    太强了,看不懂,根本看不懂

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

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

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    657

    主题

    5330

    回帖

    6218

    积分

    管理员

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

    积分
    6218

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

    发表于 2023-9-20 21:29:22 | 显示全部楼层
    steven026 发表于 2023-9-20 18:16
    太强了,看不懂,根本看不懂

    禁止复制粘贴!
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

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

    使用道具 举报

    发表回复

    本版积分规则

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