JavaScript之AST学习记录

1.前言

学习一下语法分析,感觉很有用,于是决定使用JavaScript。因为javascript ATS感觉好用,最主要是复习一下javascript语法。使用babel依赖包,定下一个小任务:浅浅的反混淆一个东西。

2.正文

我首先选中的就是阿里云AWF的js文件,因为它混淆的足够猛,网上公开的反混淆工具似乎都没有办法。js链接(https://aeu.alicdn.com/waf/jquery_220224.min.js),随后就是(AST explorer)

参考资料(Appendix A. Syntax Tree Format — Esprima master documentation)

2.1进行观察

我们首先对代码进行格式化,首先印入眼帘的就是一个_0x512b数组,随后就是一个_0x1885变量函数,我们再仔细观察一下下面的方法,无一不是使用上面两个就是进行混淆的。

 

数组没有什么好说的,看一下_0x1885变量函数,可以进行简化。

var _0x1885 = function(_0x512bf4, _0x18850b) {
    _0x512bf4 = _0x512bf4 - 0x0;
    var _0x44b9d3 = _0x512b[_0x512bf4];
    return _0x44b9d3;
};
//简化后
var _0x1885 = function (_0x512bf4, _0x18850b) {
    return _0x512b[_0x512bf4];
};

简化后,第二个参数是没有用,那么我们就可以进行第一个反混淆优化了,那就是替换_0x1885函数我们再观察一下调用_0x1885函数的地方。

    var _0x1fb9d1 = function(_0x53210e, _0x3aa6a6, _0x53e0c4, _0x217c5b, _0x297cf0) {
        return _0x1885(_0x53e0c4 - 0x19d, _0x3aa6a6);
    };

已经知道第二个参数没有用,那么我们只需要记下第一个参数,然后将其改造成如下就可以了。

    var _0x1fb9d1 = function(_0x53210e, _0x3aa6a6, _0x53e0c4, _0x217c5b, _0x297cf0) {
        return _0x512b[_0x53e0c4 - 0x19d];
    };

首先这是一个CallExpression,其次Callee是_0x1885,我们需要将这个CallExpression替换成一个MemberExpression。我们写出如下代码

import types from '@babel/types'
import parser from '@babel/parser';
import traverse from '@babel/traverse';
import generator from '@babel/generator';
import fs from 'fs';

let sourceCode = fs.readFileSync('./jquery_220224.min.js', {encoding: 'utf-8'});
let ast = parser.parse(sourceCode);

/*去掉_0x1885函数的调用*/
traverse.default(ast, {
    CallExpression(path) {
        const node = path.node;
        if (node.callee.type === 'Identifier' && node.callee.name === '_0x1885') {
            path.replaceWith(
                types.memberExpression(types.identifier("_0x512b"), node.arguments[0], true))
        }
    }
})

//一定会报错,需要添加err回调函数,有没有更加优雅的方式呢?
fs.writeFile('./new_jquery_220224.min.js', generator.default(ast).code, (err) => {
});

于是就会变成如下这样子

那么自然而言的就有了下一步,把这些函数也给它优化掉。老规矩,首先观察一下是怎么调用。

'LGpeF': _0x1fb9d1(0x24b, 0x20b, 0x19d, 0x21f, 0x156),

 传入的是全部都是常量,但是并无啥用,只有一个参数能用到,每个函数都是不一样,所以我们需要记录下参数的index值。第二个就是传入的参数的offset量,下面还有不少的这个值都是不同的,为了方便我就不放在一起。其次就是我们可以看的出来,这些都是函数,并且都是ReturnStatement,然后的话就是上面MemberExpression,MemberExpression的argment.object.name是_0x512b。里面的话就是没有好说的了。

const paramIndexData = new Map();
const variableNumberData = new Map();
traverse.default(ast, {
    ReturnStatement(path) {
        const node = path.node;
        if (types.isMemberExpression(node.argument)
            && types.isIdentifier(node.argument.object)
            && node.argument.object.name === "_0x512b") {
            const property = node.argument.property;
            //判断是否是二进制表达式,方便记录offset和param index
            if (types.isBinaryExpression(property)) {
                const paramName = property.left.name;
                if (types.isBlockStatement(path.parent)) {
                    path.findParent(parent => {
                        if (!types.isFunctionExpression(parent.parent)) {
                            return
                        }
                        const params = parent.parent.params
                        for (let i = 0; i < params.length; i++) {
                            if (types.isIdentifier(params[i]) && params[i].name === paramName) {
                                parent.findParent(it => {
                                    if (types.isVariableDeclarator(it)) {
                                        paramIndexData.set(it.node.id.name, i);
                                        variableNumberData.set(it.node.id.name, property.right.value)
                                    }
                                })
                            }
                        }
                    })
                }
            }
        }
    }
})

我们记录下之后就进行替换。又是一个CallExpression,首先判断是否在hashmap中存在,存在的话才会去替换。为了方便,我直接把_0x512b拷贝进来了。

traverse.default(ast, {
    CallExpression(path) {
        const node = path.node;
        //判断是否在hashMap中存在
        if (types.isIdentifier(node.callee) && paramIndexData.has(node.callee.name)) {
            let param = node.arguments;
            const index = paramIndexData.get(node.callee.name);
            console.log(node.callee.name)
            console.log(node.loc.start)
            console.log(node.loc.end)
            //根据index取的传入的常量
            if (types.isNumericLiteral(param[index])) {
                //常量减去offset
                const value = _0x512b[param[index].value - variableNumberData.get(node.callee.name)]
                if (value !== undefined) {
                    path.replaceWith(types.stringLiteral(value))
                }
            }
        }
    }
})

其中获取到的值可能是undefined?不知道为啥,可能是我转成十进制?也有可能是因为这里根本不走?不知道,反正是学习,能用就用吧,看下结果。

上面以oBlnb为例,肯定就是又是一个BinaryExpression表达式,直接就进行替换就可以了,只不过我懒得写了,可以参考第一个。只不过我接下来写了一下低级版的控制流平坦化。

因为在替换出常量后出现了不少的这种if语句,于是我决定写一个试下水。

代码如下

/*控制流平坦化*/
traverse.default(ast, {
    IfStatement(path) {
        const node = path.node;
        if (types.isBinaryExpression(node.test)) {
            let expression = node.test;
            if (types.isStringLiteral(expression.left)
                && types.isStringLiteral(expression.right)) {
                /* 判断一下操作符,有没有什么好办法可以直接获取其运算结果呢?*/
                if (expression.operator === "===") {
                    path.replaceWith(expression.left.value === expression.right.value ? node.consequent : node.alternate)
                } else if (expression.operator === "!==") {
                    path.replaceWith(expression.left.value !== expression.right.value ? node.consequent : node.alternate)
                }
            }
        }
    }
})

 还有好几种可以进行判断。比如,当然,方案都是大同小异的。

if (_0x3521ae["kNAeh"] !== _0x3521ae["lkRrr"]) {
    ****
}
if ("oBQxD" === _0x3521ae["hCctw"]) {
    ****
}

最后希望能够请教一下大家,有没有什么什么更加优雅的方式来实现,一起学习。今天的笔记就到这里吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值