数美滑块动态参数获取-ast详解

描述:听说数美现在的混淆是个改版ob,一个des(ecb模式) 加密 这里不讲,只讲ast部分,通过ast 还原了大数组和Object对象 想要获取动态的key,这两个是必须的,不定时天数换个小版本,目前写的145-158(2022/6/14最新)版本可以成功解析获取key,之前的版本还要继续写匿名函数实参形参替换才能获取key。通用太难写了。​


1.visitor1,最经典的还原Unicode和16进制

let visitor1 = {
    // 最经典的,把Unicode和16进制的字符串数字还原
    "NumericLiteral|StringLiteral"(path) {
        delete path.node.extra;
    },
    // 获取自执行函数,第一个自执行会改变大数组的顺序,所以这里遍历一下把自执行函数的代码保存给全局 后面运行
    "CallExpression"(path) {
        let {node} = path;
        if (!node.callee || node.callee.type !== "FunctionExpression") return;
        if (!node.arguments || node.arguments.length !== 2) return;
        if (node.arguments[0].type !== "Identifier") return;
        // 修改大数组的是一个匿名函数,这里加个感叹号让他语法没问题
        // 转ast再转js代码 使用了压缩,防止toString检测导致内存爆破卡死。
        let changeAst = parser.parse("!" + path.toString());
        let ChangeArrayOrder = generator(changeAst, opts = {"compact": true}).code;
        console.log("改变数组顺序的自执行代码:", ChangeArrayOrder)
        global["ChangeArrayOrder"] = ChangeArrayOrder;
        path.remove();
    }
}

在最外层那个自执行会改变大数组的顺序,通过ast 一大堆判断 得到正确的匿名函数,再保存到全局(node里全局用global)等下次和大数组,解密函数执行后执行。有的会有toString检测,直接格式化。

2.还原通过函数传入一个数字来得到结果(大数组解密)
这里visitorArray通过遍历Var类型加一大堆判断 比如 var a = ["1","aa"],再往上找函数,如果找不到说明就是最外层定义的数组,再把大数组保存到全局,再通过数组的父级path来得到解密的函数 比如"_0x1e4a2f(0x614)"得到一个字符串_0x1e4a2f就是解密函数,现在获取整一个_0x1e4a2f函数的内容,并且保存到全局。
还有个快速写的思路,就是通过最外层的body来写,但这个太多局限性 仅用于单个不会变的js解混淆
然后往下看。

//解决大数组
let visitorArray = {
    "VariableDeclarator"(path) {
        let {id, init} = path.node;
        // 判断var右边是不是数组类型
        if (!init || init.type !== "ArrayExpression" || init.elements.length < 1) return;
        // 获取作用域
        let binding = path.scope.getBinding(id.name);
        // 如果作用域的引用次数等于0,就说明不是,那就删除并且返回
        if (binding.references === 0) {
            path.remove;
            return
        }
        // every 遍历数组 判断全部的成员的是否为StringLiteral
        if (init.elements.every(element => element.type === "StringLiteral")) {
            //获取父级函数,如果没有就说明是最外层定义的数组
            let funcName = path.getFunctionParent();
            if (funcName === null) {
                console.log("大数组名字应该是:", id.name)
                global["largeArray"] = path.toString();
                path.remove;
                // 父级作用域传给另外一个函数操作
                getDecName(path.parentPath)
            } else {
                // 获取最后一个返回的函数名 和父级函数名对比,如果一样 就eval
                let lastElement = funcName.node.body.body.at(-1)
                if (!lastElement.argument || !lastElement.argument.callee) return;
                if (lastElement.type !== "ReturnStatement" && lastElement.argument.callee.name !== funcName.node.id.name) return;
                console.log("大数组名字应该是函数名:", funcName.node.id.name)
                let largeArrayAst = parser.parse(funcName.toString());
                global["largeArray"] = generator(largeArrayAst, opts = {"compact": true}).code;
                // 父级作用域传给另外一个函数操作 获取解字符串函数
                getDecName(funcName.parentPath)
                // 移除掉大数组
                funcName.remove()
            }
        }
    },
}
function RecursiveAssignment(path, pPathString) {
    eval(global["largeArray"]); //加载大数组
    eval(pPathString); //加载解密函数
    eval(global["ChangeArrayOrder"])
    if (!pPathString || !global["largeArray"] || !global["ChangeArrayOrder"]) {
        throw "有些没解析全,退出"
    }
    // 递归函数,因为他的引用又是一个赋值新变量操作
    //代码长这样子的:var _0x1e4a2f = _0x4a9d; console.log(_0x4a9d(100));
    let {name} = path.node.id;
    let binding = path.scope.getBinding(name);
    for (p of binding.referencePaths) {
        let pPath = p.parentPath;
        let {node} = pPath;
        if (node.type === "VariableDeclarator") {
            let leftName = node.id.name;
            let rightName = node.init.name;
            // console.log("似乎要把解密函数"+rightName+"赋值给", leftName);
            // // 先改成解密函数的名字,
            pPath.scope.rename(leftName, rightName);
            RecursiveAssignment(pPath, pPathString);
            // // 移除掉赋值新变量名
            pPath.remove();
        }
        //console.log(pPath.toString());
        // 判断是调用函数,替换节点
        if (node.type === "CallExpression" && node.arguments.length === 1 && && node.arguments[0].type === "NumericLiteral") {
            let pPathCode = pPath.toString();
            let result = eval(pPathCode)
            // console.log(pPathCode, ":", result);
            pPath.replaceWith(types.valueToNode(result))
        }
    }
}
// 获取解密函数代码
let getDecryption = {
    "FunctionDeclaration"(path) {
        // 判断函数是不是需要传两个参数
        let {node} = path;
        let {body} = node;
        if (!node.params || node.params.length !== 2) return;
        if (!body || !body.body) return;
        let lastReturn = body.body.at(-1)
        if (lastReturn.type !== "ReturnStatement" || !lastReturn.argument || !lastReturn.argument.expressions) return;
        let lastCall = lastReturn.argument.expressions.at(-1)
        if (lastCall.type !== "CallExpression" || lastCall.arguments.length !== 2) return;
        let name = node.id.name;
        console.log("解密函数的名字是:", name);
        let decryAst = parser.parse(path.toString());
        let decryCode = generator(decryAst, opts = {"compact": true}).code;
        RecursiveAssignment(path, decryCode);
        // 移除解字符串函数
        path.remove();
    }
}
var getDecName = (path) => {
    // 这个函数 通过大数组的作用域传入同级作用域 来获得解密函数
    let {scope} = path;
    scope.traverse(scope.block, getDecryption);
}

获取到了解密的函数后 通过遍历作用域引用路径来替换节点解大数组,这里RecursiveAssignment是一个递归函数,因为他把解密函数赋值给新的一个函数了,那么获取绑定标识符就要改成新的。所以我先把赋值的及它的作用域名字全改成解密函数的名字,用了一个递归 传新标识符过来重新走一遍解密。它赋值的次数还挺多的
像楼下这图还能翻10多张。。。

image.png

3.visitor2 优化加减乘除操作和解对象
比如:这种代码 "-0x7 * 0xe3 + -0x1 * 0xaae + -0x2 * -0x872"
和这种代码
// var _0x4b4e24 = {
// "Xpwku": "<div id="",
// "WuoUF": function (_0x3e6935, _0x10f1b1) {return _0x3e6935 + _0x10f1b1;}
// }
加减乘除的看代码注释吧。讲讲解对象。
一开始还是一系列判断是否为Object,再新建个新对象,把解析的成员node全部丢给它,后续引用路径来和它node里面的东西判断
根据多个对象结合 对象成员的返回值来看,一共两大类,三小类
先if判断是字符串还是函数,字符串没啥说的直接替换,如果是函数,再根据它的返回类型 继续做判断
返回类型也一共就那三小类

image.png


折叠的代码比较好看,建议折叠代码然后根据上面说的来看
三小类介绍:
logical和binary两个类型好像都差不多,就是logical的opertor是"||" ,这两个直接套娃替换
CallExpression的话,好像都是第一个参数是函数,然后剩下参数当函数调用的参数

let visitor2 = {
    // 优化加减乘除操作
    "BinaryExpression": {
        exit(path) {
            let {node} = path;
            let {left} = node;
            let {right} = node;
            if ((left.type === "UnaryExpression" || left.type === "NumericLiteral") && (right.type === "UnaryExpression" || right.type === "NumericLiteral")) {
                let leftValue;
                let rightValue;
                if (left.type === "NumericLiteral") {
                    leftValue = left.value
                } else {
                    if (!left.argument.value) return;
                    if (left.operator === "-") {
                        leftValue = -left.argument.value
                    }
                }
                if (right.type === "NumericLiteral") {
                    rightValue = right.value
                } else {
                    if (!right.argument.value) return;
                    if (left.operator === "-") {
                        rightValue = -right.argument.value
                    }
                }
                if (!leftValue) return;
                //console.log(leftValue, node.operator, rightValue);
                path.replaceWith(types.valueToNode(path.evaluate(leftValue + node.operator + rightValue).value));
            }
        }
    },
    //解对象
    "VariableDeclarator"(path) {
        // 其他的话对局部数组做的,比如代码:
        // var _0x4b4e24 = {
        //     "Xpwku": "<div id=\"",
        //     "WuoUF": function (_0x3e6935, _0x10f1b1) {return _0x3e6935 + _0x10f1b1;}}
        let {node} = path;
        let {id, init} = node;
        if (!init || init.type !== "ObjectExpression") return;
        if (!init.properties || init.properties.length < 1) return;
        let Array2Name = id.name;
        //新建一个对象。把原来的对象右边(value)保存为Node
        let NewObject = {};
        for (v of init.properties) {
            NewObject[v.key.value] = v.value;
        }
        let binding = path.scope.getBinding(Array2Name)
        // 新建一个操作次数,如果数组的引用节点都操作了的话 次数加一 如果等于所有次数,就说明全修改了,可以删除定义的节点了
        let modificationNum=0
        for (p of binding.referencePaths.reverse()) {
            // 此时的p是数组名称,要父级path才有成员名
            let pPath = p.parentPath;
            let {node} = pPath;
            // 获取调用数组成员的名字,再放进新对象查一下是字符串还是函数,如果是函数,还要往上找父级path,类型才是CallExpression
            // 如果没有property 说明不是调用某个成员,可能是要调用或赋值自己给其他的
            if (!node.property) return;
            let keyName = node.property.value;
            let rightNode = NewObject[keyName]
            if (!NewObject[keyName]) return
            if (rightNode.type === "StringLiteral") {
                // console.log("旧代码111", pPath.toString())
                pPath.replaceWith(types.valueToNode(rightNode.value));
                modificationNum+=1
                // console.log("新代码111", pPath.toString())
                // console.log("-------------------")
            } else if (rightNode.type === "FunctionExpression") {
                // 此时这里的pPath.node为_0xa7a3b2["JHTcH"] 还要上一级才会有arguments的东西
                node = pPath.parentPath.node;
                let {parentPath} = pPath
                if (node.type !== "CallExpression") return;
                if (!rightNode.body || !rightNode.body.body) return;
                let bodyResult = rightNode.body.body[0];
                if (bodyResult.type !== "ReturnStatement") return;
                let {arguments} = node;
                //BinaryExpression 就是长这样的代码 return _0x398914 == _0x5433f6;
                if (bodyResult.argument.type === "BinaryExpression") {
                    let {operator} = bodyResult.argument
                        // 只有一个参数,说明是判断类型的,右边固定
                        if (arguments.length === 1) {
                            parentPath.replaceWith(types.binaryExpression(operator, node.arguments[0], bodyResult.argument.right))
                            modificationNum+=1
                        } else if (arguments.length === 2) {
                                // console.log("旧代码222", pPath.toString())
                                parentPath.replaceWith(types.binaryExpression(operator, node.arguments[0], node.arguments[1]))
                                modificationNum+=1
                                // console.log("新代码222", parentPath.toString())
                                // console.log("-------------------")
                        }
                }else if(bodyResult.argument.type==="LogicalExpression"){
                    parentPath.replaceWith(types.logicalExpression("||", node.arguments[0], node.arguments[1]))
                    modificationNum+=1
                }else if (bodyResult.argument.type === "CallExpression") {
                    if (node.arguments.length >= 1) {
                                // console.log("旧代码", pPath.parentPath.toString())
                                pPath.parentPath.replaceWith(types.callExpression(node.arguments[0], node.arguments.slice(1)));
                                modificationNum+=1
                                // console.log("新代码", pPath.parentPath.toString())
                                // console.log("-------------------")
                    }
                }
            }
        }
        if(modificationNum===binding.referencePaths.length){
            // console.log("删除了一个小数组。")
            path.remove()
        }
    }
}

4.获取动态的数据

// 获取动态post参数和加密的key
let key_dict=[];
let visitor3={
    "AssignmentExpression"(path){
        let {node}=path;
        let {left,right}=node;
        if(!left.property||left.property.type!=="StringLiteral")return
        if(!right||!right.arguments||right.arguments.length!==2)return;
        if(right.arguments[1].type!=="StringLiteral"||right.arguments[1].value.length!==8)return;
        // console.log(left.property.value,right.arguments[1].value)
        key_dict.push([left.property.value,right.arguments[1].value])
    }
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值