AST基础+混淆JS还原的逐步演示

基础部分

AST:Abstract Syntax Tree(抽象语法树),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

我知道,看完定义的你八成是一脸懵逼,什么叫做源代码语法结构的抽象表示?不用害怕,我在这里简单使用一句话为例来帮助大家理解:

今天你喝水了吗?

大家都知道这句话的意思就是问今天你喝水了吗,但你知道这句话是由哪些成分组成的吗?
在这里插入图片描述
以前上学的时候语文课肯定都学过,句子里有主语,谓语,宾语,定语等等,句子中的词又分为名词、动词、介词…各种词,当这些词按照某种规律形式组合以后,就形成了上面的那句话,

而现在正在看文章的你肯定知道并理解上面那句话的意思,但是,你不一定知道这句话中每个词的词性,不知道哪个是主语,哪个是谓语,这就跟你知道 JavaScript,却不了解AST是一回事。

所以:

不会AST这件事本身不会对你学习使用JavaScript有任何影响,但如果你理解并掌握了AST,那么JavaScript 可能在你眼里就有些不太一样了,从某种程度上来说,了解掌握AST可以帮助你真正吃透 JavaScript 的语言精髓。

既然如此,那怎样才能快速的了解AST?

答案很简单,随写写句JS拿去AST分析一下,然后对着看看,相信聪明的你很快就能有所了解。

在线 AST语法结构解析网站:https://astexplorer.net/

下面来看看最最简单的JS

var a=1;

代码对应的AST语法树结构:
AST基础+混淆JS还原的逐步演示_第1张图片
从上往下看,有Program、declarations、VariableDeclarator、Identifier、Literal,这些都AST的结构类型,除了这些还有很多,列表如下:
AST基础+混淆JS还原的逐步演示_第2张图片
知道了这些有什么用呢?除了上面说的可以加深对JS的深层理解外,最大的用处就是对 JavaScript 代码进行混淆以及还原了。

了解JavaScript 的小伙伴应该都知道,JS有非常非常多的语法糖,而对JS的加密混淆,其实就是在对这些语法糖的充分利用,当你既熟练掌握JavaScript ,又理解并掌握了AST以后,你就可以从AST语法树结构的视角,去对混淆后的JS代码进行某种程度的还原,有点类似于降维打击,因为JS加密混淆都是在JS代码的外在表现形式上做的手脚,而AST的视角则更偏向底层。

混淆JS还原演示

PS:环境问题请自行百度解决。
测试代码:

function MyFun(a,b){
        var c=a+b;
        var d=a*b+c;
        return c+d;
}
console.log(MyFun(10,20))

加密混淆后的代码:

var _0x305a = ['B8OBMcKcUQ==', 'w4Y2wrrCl8OT', 'wpHCpMKLRsOu', 'wpgfwo7DgMO4', 'wpVywozCqwHCucKV', 'HMK1VsOS', 'wrfCiSgfw7c0NGHCoAXCuH7CnyzDlMOdfXxHLDorfsOCwrzCnFNPw6I+cwQ=', 'SsO0PUdW', 'PcKRVQ==', 'UHbDj3xcw7ZY', 'UsO6OA==', 'wrjChmcfw70=', 'W8OnLURB', 'w6/CrATDncKcw6lF', 'IcOxw4DDiMOx', 'wr3Dp07DtUUfwrI=', 'N8KOesOsw58=', 'FR7DgS0p', 'AgPDjj0jcUI=', 'AsOYGMKFdg==', 'bcK0wrHCiMOt', 'w7HClAPDo8KVSsKe', 'wrbDhGR1', 'ABzDkCI1', 'Fg1fRsOE', 'w4XCocOJBGg=', 'w60AwpHCv8OJ', 'NcKLwpBPHW01', 'w4QGIWLDiw==', 'wqXCmmAT', 'FT9dMg==', 'LF7Du2sqwrrCqsKDRw==', 'wr/DsknDs3s=', 'wrXDhHR3BQ==', 'P8KKwphT', 'RHjDk2E=', 'w7FCY21yw7BV', 'w7tDa3E=', 'w77ClAo=', 'AcOWJMKKRsOCwpbDsRLDlHkTw5g/wqfCgDzCusOC', 'w6fDpAHDiRlpWQ==', 'wpgGwo3Dtg==', 'w4c+wqHDh2E=', 'fXjDpUspw5kAH1jDvcOGAMKhfiHCiFvCkcOLGMOqw5HDtsKTw5jDom7DmMOaYMKdwrs=', 'QhFqYQg=', 'wqHCsm3Ds0DCpsKB', 'w4cdwo4=', 'w75Cag==', 'w4YiEcO9wq1aIw==', 'w48XwovCvcOY', 'e1vDnHbCg8Kkdkp5', 'cEHDtUhR', 'asKwwonCjMOw', 'TsOaw4QMGsKswp4=', 'wqrDqULDqk8=', 'w7DCjHcsw4A=', 'w4N6worDksOo', 'WMKywpDDonI=', 'wo0GwqkKwrA=', 'DjRPKMKlwpROa8OCV8KxY8KYWGEuwobDgcO2', 'CcOZw5jDlcOO', 'bsK4wpLClsOT', 'w6YJw5APwoo=', 'w4EoHcO7wqU=', 'w4lHTTnCpcKkw6jCgUc=', 'wpNlwoHCvR7CocKZwoMe', 'w6XCt8O1I1A=', 'w7dff3Fv', 'FMKrQMORw6M=', 'w7fCgw7DtcKKUsKSLyA=', 'DMOyw6vDucO6', 'L1/Dt1nDuMOlwpQ=', 'w5MbLnLDgcKlwps=', 'wrXCvHHDrg==', 'w5EsHcOiwqc=', 'N8KUwo5QCw==', 'w6sdwo3Co8OUw7xB', 'VABoYw==', 'wrF7LhFwwo5O', 'Rj9O']; (function(_0x1f4b85, _0x305a4c) {
    var _0x796962 = function(_0x4b72d1) {
        while (--_0x4b72d1) {
            _0x1f4b85['push'](_0x1f4b85['shift']());
        }
    };
    _0x796962(++_0x305a4c);
} (_0x305a, 0xe7));
var _0x7969 = function(_0x1f4b85, _0x305a4c) {
    _0x1f4b85 = _0x1f4b85 - 0x0;
    var _0x796962 = _0x305a[_0x1f4b85];
    if (_0x7969['UkyMFX'] === undefined) { (function() {
            var _0x344906;
            try {
                var _0x1d345f = Function('return\x20(function()\x20' + '{}.constructor(\x22return\x20this\x22)(\x20)' + ');');
                _0x344906 = _0x1d345f();
            } catch(_0x1d3b2a) {
                _0x344906 = window;
            }
            var _0x14f0f1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
            _0x344906['atob'] || (_0x344906['atob'] = function(_0x3128ab) {
                var _0x23f475 = String(_0x3128ab)['replace'](/=+$/, '');
                var _0x32bdd4 = '';
                for (var _0x3da026 = 0x0,
                _0x372bba, _0x104d49, _0x5eb7a8 = 0x0; _0x104d49 = _0x23f475['charAt'](_0x5eb7a8++);~_0x104d49 && (_0x372bba = _0x3da026 % 0x4 ? _0x372bba * 0x40 + _0x104d49: _0x104d49, _0x3da026++%0x4) ? _0x32bdd4 += String['fromCharCode'](0xff & _0x372bba >> ( - 0x2 * _0x3da026 & 0x6)) : 0x0) {
                    _0x104d49 = _0x14f0f1['indexOf'](_0x104d49);
                }
                return _0x32bdd4;
            });
        } ());
        var _0x570ba5 = function(_0x257551, _0x1f613d) {
            var _0x3e9282 = [],
            _0x434903 = 0x0,
            _0x33fc11,
            _0x249601 = '',
            _0x2f07c3 = '';
            _0x257551 = atob(_0x257551);
            for (var _0x464f7e = 0x0,
            _0x4b3e63 = _0x257551['length']; _0x464f7e < _0x4b3e63; _0x464f7e++) {
                _0x2f07c3 += '%' + ('00' + _0x257551['charCodeAt'](_0x464f7e)['toString'](0x10))['slice']( - 0x2);
            }
            _0x257551 = decodeURIComponent(_0x2f07c3);
            var _0x94001b;
            for (_0x94001b = 0x0; _0x94001b < 0x100; _0x94001b++) {
                _0x3e9282[_0x94001b] = _0x94001b;
            }
            for (_0x94001b = 0x0; _0x94001b < 0x100; _0x94001b++) {
                _0x434903 = (_0x434903 + _0x3e9282[_0x94001b] + _0x1f613d['charCodeAt'](_0x94001b % _0x1f613d['length'])) % 0x100;
                _0x33fc11 = _0x3e9282[_0x94001b];
                _0x3e9282[_0x94001b] = _0x3e9282[_0x434903];
                _0x3e9282[_0x434903] = _0x33fc11;
            }
            _0x94001b = 0x0;
            _0x434903 = 0x0;
            for (var _0x27a73e = 0x0; _0x27a73e < _0x257551['length']; _0x27a73e++) {
                _0x94001b = (_0x94001b + 0x1) % 0x100;
                _0x434903 = (_0x434903 + _0x3e9282[_0x94001b]) % 0x100;
                _0x33fc11 = _0x3e9282[_0x94001b];
                _0x3e9282[_0x94001b] = _0x3e9282[_0x434903];
                _0x3e9282[_0x434903] = _0x33fc11;
                _0x249601 += String['fromCharCode'](_0x257551['charCodeAt'](_0x27a73e) ^ _0x3e9282[(_0x3e9282[_0x94001b] + _0x3e9282[_0x434903]) % 0x100]);
            }
            return _0x249601;
        };
        _0x7969['tMJLOX'] = _0x570ba5;
        _0x7969['vDRtQv'] = {};
        _0x7969['UkyMFX'] = !![];
    }
    var _0x4b72d1 = _0x7969['vDRtQv'][_0x1f4b85];
    if (_0x4b72d1 === undefined) {
        if (_0x7969['SqRviR'] === undefined) {
            _0x7969['SqRviR'] = !![];
        }
        _0x796962 = _0x7969['tMJLOX'](_0x796962, _0x305a4c);
        _0x7969['vDRtQv'][_0x1f4b85] = _0x796962;
    } else {
        _0x796962 = _0x4b72d1;
    }
    return _0x796962;
};
function _0x556652(_0x4a2332, _0x2634dc) {
    var _0x94d946 = function() {
        if (_0x7969('0xb', 'X*5E') !== _0x7969('0xc', 'WhTf')) {
            if (fn) {
                var _0x33221e = fn[_0x7969('0x35', 'hsTm')](context, arguments);
                fn = null;
                return _0x33221e;
            }
        } else {
            var _0x43616c = !![];
            return function(_0x550c05, _0x13d9c2) {
                if (_0x7969('0xa', '9uv1') !== _0x7969('0x1d', 'wdO$')) {
                    var _0x296878 = _0x43616c ?
                    function() {
                        if (_0x7969('0x22', 'NGkH') !== _0x7969('0x40', 't$pJ')) {
                            if (_0x13d9c2) {
                                if (_0x7969('0x21', '9kDW') === _0x7969('0x17', 'k(YQ')) {
                                    that[_0x7969('0x16', 'r8p)')] = function(_0x1c2524) {
                                        var _0x2972cc = {};
                                        _0x2972cc[_0x7969('0x11', 'k3XH')] = _0x1c2524;
                                        _0x2972cc[_0x7969('0x1f', 'Cu4J')] = _0x1c2524;
                                        _0x2972cc[_0x7969('0x3a', 'R*hj')] = _0x1c2524;
                                        _0x2972cc[_0x7969('0x26', 'V6wH')] = _0x1c2524;
                                        _0x2972cc[_0x7969('0x41', 'Bt5v')] = _0x1c2524;
                                        _0x2972cc[_0x7969('0x4e', 'kamb')] = _0x1c2524;
                                        _0x2972cc[_0x7969('0x10', 'a4uQ')] = _0x1c2524;
                                        _0x2972cc[_0x7969('0x9', 'uP&h')] = _0x1c2524;
                                        return _0x2972cc;
                                    } (func);
                                } else {
                                    var _0x34a69a = _0x13d9c2[_0x7969('0x20', 'sT$v')](_0x550c05, arguments);
                                    _0x13d9c2 = null;
                                    return _0x34a69a;
                                }
                            }
                        } else {
                            that = window;
                        }
                    }: function() {};
                    _0x43616c = ![];
                    return _0x296878;
                } else {
                    var _0x2c3174 = {};
                    _0x2c3174[_0x7969('0x8', 'pE[N')] = func;
                    _0x2c3174[_0x7969('0x32', 'WhTf')] = func;
                    _0x2c3174[_0x7969('0x47', 'sDkG')] = func;
                    _0x2c3174[_0x7969('0x27', 'YgD$')] = func;
                    _0x2c3174[_0x7969('0x23', '9uv1')] = func;
                    _0x2c3174[_0x7969('0x4a', '(^8)')] = func;
                    _0x2c3174[_0x7969('0x2a', 'Cu4J')] = func;
                    _0x2c3174[_0x7969('0x25', 'AWI1')] = func;
                    return _0x2c3174;
                }
            };
        }
    } ();
    var _0x23c1f6 = _0x94d946(this,
    function() {
        var _0x6e83c1 = function() {};
        var _0x3f3777;
        try {
            if (_0x7969('0x33', 'JKNM') !== _0x7969('0x45', 'k(YQ')) {
                var _0x584a01 = Function(_0x7969('0x30', 'uP&h') + _0x7969('0xf', 'V6wH') + ');');
                _0x3f3777 = _0x584a01();
            } else {
                var _0x30c130 = Function(_0x7969('0x44', 'YgD$') + _0x7969('0x34', 'Jler') + ');');
                _0x3f3777 = _0x30c130();
            }
        } catch(_0x5365b4) {
            if (_0x7969('0x19', 'D3T3') !== _0x7969('0x1c', 'uP&h')) {
                _0x3f3777 = window;
            } else {
                _0x3f3777[_0x7969('0x1', 'AWI1')][_0x7969('0x38', 'sJv5')] = _0x6e83c1;
                _0x3f3777[_0x7969('0x12', 'O(f6')][_0x7969('0x2c', 'O(f6')] = _0x6e83c1;
                _0x3f3777[_0x7969('0x1e', 'kamb')][_0x7969('0x48', 'aCn%')] = _0x6e83c1;
                _0x3f3777[_0x7969('0x3e', ']zyO')][_0x7969('0xe', 'D3T3')] = _0x6e83c1;
                _0x3f3777[_0x7969('0x31', 'jA(H')][_0x7969('0x4c', 'sJv5')] = _0x6e83c1;
                _0x3f3777[_0x7969('0x24', '*20m')][_0x7969('0x3b', 'G1i2')] = _0x6e83c1;
                _0x3f3777[_0x7969('0x7', '1MEH')][_0x7969('0x2a', 'Cu4J')] = _0x6e83c1;
                _0x3f3777[_0x7969('0x0', 'QGrn')][_0x7969('0x14', 'V6wH')] = _0x6e83c1;
            }
        }
        if (!_0x3f3777[_0x7969('0x39', 'aCn%')]) {
            if (_0x7969('0x4b', 'NGkH') !== _0x7969('0x3c', 'O(f6')) {
                _0x3f3777[_0x7969('0x36', 'WU*x')] = function(_0x4bdab5) {
                    if (_0x7969('0x29', 'zEsY') === _0x7969('0x46', 'wdO$')) {
                        var _0x5f33bc = {};
                        _0x5f33bc[_0x7969('0x37', 'R*hj')] = _0x4bdab5;
                        _0x5f33bc[_0x7969('0x6', 'hsTm')] = _0x4bdab5;
                        _0x5f33bc[_0x7969('0x43', 'JQ7t')] = _0x4bdab5;
                        _0x5f33bc[_0x7969('0x2e', 'sJv5')] = _0x4bdab5;
                        _0x5f33bc[_0x7969('0x3d', 'wdO$')] = _0x4bdab5;
                        _0x5f33bc[_0x7969('0x49', 'U(TC')] = _0x4bdab5;
                        _0x5f33bc[_0x7969('0x3f', 'zEsY')] = _0x4bdab5;
                        _0x5f33bc[_0x7969('0x1a', 'sT$v')] = _0x4bdab5;
                        return _0x5f33bc;
                    } else {
                        var _0x1b640b = firstCall ?
                        function() {
                            if (fn) {
                                var _0x3dd5c2 = fn[_0x7969('0x4d', 'D3T3')](context, arguments);
                                fn = null;
                                return _0x3dd5c2;
                            }
                        }: function() {};
                        firstCall = ![];
                        return _0x1b640b;
                    }
                } (_0x6e83c1);
            } else {
                var _0x345d2c = fn[_0x7969('0x4', '*20m')](context, arguments);
                fn = null;
                return _0x345d2c;
            }
        } else {
            _0x3f3777[_0x7969('0x36', 'WU*x')][_0x7969('0x2f', 'kamb')] = _0x6e83c1;
            _0x3f3777[_0x7969('0x5', '9uv1')][_0x7969('0x2', 'WU*x')] = _0x6e83c1;
            _0x3f3777[_0x7969('0x12', 'O(f6')][_0x7969('0x42', 'vJ8P')] = _0x6e83c1;
            _0x3f3777[_0x7969('0x2d', 'sJv5')][_0x7969('0x2b', '*20m')] = _0x6e83c1;
            _0x3f3777[_0x7969('0xd', '(^8)')][_0x7969('0x15', 'a4uQ')] = _0x6e83c1;
            _0x3f3777[_0x7969('0x1b', 'sT$v')][_0x7969('0x28', 'ijQK')] = _0x6e83c1;
            _0x3f3777[_0x7969('0x1b', 'sT$v')][_0x7969('0x3', 'aCn%')] = _0x6e83c1;
            _0x3f3777[_0x7969('0x18', 'zEsY')][_0x7969('0x4f', 'k(YQ')] = _0x6e83c1;
        }
    });
    _0x23c1f6();
    var _0x245e10 = _0x4a2332 + _0x2634dc;
    var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    return _0x245e10 + _0x5d196d;
}
console[_0x7969('0x13', 'a4uQ')](_0x556652(0xa, 0x14));

检查混淆代码是否可以正常执行:
AST基础+混淆JS还原的逐步演示_第3张图片
混淆后的代码可以正常执行,

对混淆后的代码进行初步分析函数 _0x7969 在整个被混淆的JS中大量出现,并且,都是以 _0x7969[‘xxx’] 或 _0x7969(‘xxx’,‘xxx’) 的形式出现,其中的xxx则全部都是乱七八糟的字符串,由此可以得出结论:

_0x7969 这个函数就是这个混淆JS 加密字符串的解密函数

使用支持JS格式的文本编辑器,对混淆后的代码进行收缩,如下:
AST基础+混淆JS还原的逐步演示_第4张图片
我给标注出了解密函数跟执行函数(或者叫原始功能函数,叫啥无所谓,明白意思就行)

混淆JS的大体结构我们清楚了,下面第一步,就是对执行函数的加密字符串进行解密

我们先将混沌JS中的解密函数复制出来保存到de.js,将执行函数部分复制出来保存为en.js,再新建一个obTest.js,用于编写AST还原代码
AST基础+混淆JS还原的逐步演示_第5张图片
de.js 需要添加一行,导出函数,名称就是上面的 _0x7969,如下:
AST基础+混淆JS还原的逐步演示_第6张图片

打开obTest.js,开始进入AST 搬砖环节

const fs = require("fs");
const esprima = require('esprima'); //ECMAScript(JavaScript) 解析架构,主要用于多用途分析。
const estraverse = require('estraverse'); //语法树遍历辅助库(提供了两个静态方法,estraverse.traverse 和 estraverse.replace。前者单纯遍历 AST 的节点,通过返回值控制是否继续遍历到叶子节点;而 replace 方法则可以在遍历的过程中直接修改 AST,实现代码重构功能。)
const escodegen = require('escodegen');//AST的 ECMAScript (也称为JavaScript)代码生成器
const iconv = require("iconv-lite");
const de = require("./de");   
 
//读取加密混淆的执行函数Js
var content = fs.readFileSync('./en.js',{encoding:'binary'});  
var buf = new Buffer.from(content,'binary');
var code = iconv.decode(buf,'utf-8');
 
//将混淆后的执行函数Js转换为AST
var ast = esprima.parse(code);
//字符串解密
var ast = esprima.parse(code);
ast = estraverse.replace(ast, {
    enter: function (node) {
        if (node.type == 'CallExpression' &&  //标注1
            node.callee.type == 'Identifier' && //标注2
            node.callee.name == "_0x7969" &&  //解密函数名
            node.arguments.length == 2 &&  
            node.arguments[0].type == 'Literal' && //标注3
            node.arguments[1].type == 'Literal')  //标注4
        {
            var val = de._0x7969(node.arguments[0].value,node.arguments[1].value);  //标注5
            return {
                type: esprima.Syntax.Literal,
                value: val,
                raw: val
            }
        }
    }
});
 
code = escodegen.generate(ast)  //将AST转换为JS
console.log(code)

上面代码看不懂?没关系,一张图搞定
AST基础+混淆JS还原的逐步演示_第7张图片
左边为混淆代码,右边为AST语法树结构,上面我们讲过了,所有加密的字符串都是以 _0x7969(‘xxx’,‘xxx’) 这样的结构出现,所以,我们需要对结构进行筛查判断,找到所有这一类的节点

上面代码中的 if … && …&& 一大串就是干这个事的,拿一行为例说明:

if (node.type == ‘CallExpression’ && //标注1
判断 node.type(当前节点类型)是否为 CallExpression(对应看上面的图),是不是马上就清楚了

执行代码看一下效果(加密字符串解密后)

function _0x556652(_0x4a2332, _0x2634dc) {
    var _0x94d946 = function () {
        if ('wxqXe' !== 'wxqXe') {
            if (fn) {
                var _0x33221e = fn['apply'](context, arguments);
                fn = null;
                return _0x33221e;
            }
        } else {
            var _0x43616c = !![];
            return function (_0x550c05, _0x13d9c2) {
                if ('NDYGh' !== 'bvJko') {
                    var _0x296878 = _0x43616c ? function () {   
                        if ('RzWPN' !== 'fJYYY') {
                            if (_0x13d9c2) {
                                if ('CONdw' === 'YqJRn') {      
                                    that['console'] = function (_0x1c2524) {
                                        var _0x2972cc = {};
                                        _0x2972cc['log'] = _0x1c2524;
                                        _0x2972cc['warn'] = _0x1c2524;
                                        _0x2972cc['debug'] = _0x1c2524;
                                        _0x2972cc['info'] = _0x1c2524;
                                        _0x2972cc['error'] = _0x1c2524;
                                        _0x2972cc['exception'] = _0x1c2524;
                                        _0x2972cc['table'] = _0x1c2524;
                                        _0x2972cc['trace'] = _0x1c2524;
                                        return _0x2972cc;
                                    }(func);
                                } else {
                                    var _0x34a69a = _0x13d9c2['apply'](_0x550c05, arguments);
                                    _0x13d9c2 = null;
                                    return _0x34a69a;
                                }
                            }
                        } else {
                            that = window;
                        }
                    } : function () {
                    };
                    _0x43616c = ![];
                    return _0x296878;
                } else {
                    var _0x2c3174 = {};
                    _0x2c3174['log'] = func;
                    _0x2c3174['warn'] = func;
                    _0x2c3174['debug'] = func;
                    _0x2c3174['info'] = func;
                    _0x2c3174['error'] = func;
                    _0x2c3174['exception'] = func;
                    _0x2c3174['table'] = func;
                    _0x2c3174['trace'] = func;
                    return _0x2c3174;
                }
            };
        }
    }();
    var _0x23c1f6 = _0x94d946(this, function () {
        var _0x6e83c1 = function () {
        };
        var _0x3f3777;
        try {
            if ('LbvcK' !== 'qYROQ') {
                var _0x584a01 = Function('return (function() ' + '{}.constructor("return this")( )' + ');');
                _0x3f3777 = _0x584a01();
            } else {
                var _0x30c130 = Function('return (function() ' + '{}.constructor("return this")( )' + ');');
                _0x3f3777 = _0x30c130();
            }
        } catch (_0x5365b4) {
            if ('BUJQE' !== 'qkHzB') {
                _0x3f3777 = window;
            } else {
                _0x3f3777['console']['log'] = _0x6e83c1;
                _0x3f3777['console']['warn'] = _0x6e83c1;
                _0x3f3777['console']['debug'] = _0x6e83c1;
                _0x3f3777['console']['info'] = _0x6e83c1;
                _0x3f3777['console']['error'] = _0x6e83c1;
                _0x3f3777['console']['exception'] = _0x6e83c1;
                _0x3f3777['console']['table'] = _0x6e83c1;
                _0x3f3777['console']['trace'] = _0x6e83c1;
            }
        }
        if (!_0x3f3777['console']) {
            if ('rlkwv' !== 'CXTGb') {
                _0x3f3777['console'] = function (_0x4bdab5) {
                    if ('aziuQ' === 'aziuQ') {
                        var _0x5f33bc = {};
                        _0x5f33bc['log'] = _0x4bdab5;
                        _0x5f33bc['warn'] = _0x4bdab5;
                        _0x5f33bc['debug'] = _0x4bdab5;
                        _0x5f33bc['info'] = _0x4bdab5;
                        _0x5f33bc['error'] = _0x4bdab5;
                        _0x5f33bc['exception'] = _0x4bdab5;
                        _0x5f33bc['table'] = _0x4bdab5;
                        _0x5f33bc['trace'] = _0x4bdab5;
                        return _0x5f33bc;
                    } else {
                        var _0x1b640b = firstCall ? function () {
                            if (fn) {
                                var _0x3dd5c2 = fn['apply'](context, arguments);
                                fn = null;
                                return _0x3dd5c2;
                            }
                        } : function () {
                        };
                        firstCall = ![];
                        return _0x1b640b;
                    }
                }(_0x6e83c1);
            } else {
                var _0x345d2c = fn['apply'](context, arguments);
                fn = null;
                return _0x345d2c;
            }
        } else {
            _0x3f3777['console']['log'] = _0x6e83c1;
            _0x3f3777['console']['warn'] = _0x6e83c1;
            _0x3f3777['console']['debug'] = _0x6e83c1;
            _0x3f3777['console']['info'] = _0x6e83c1;
            _0x3f3777['console']['error'] = _0x6e83c1;
            _0x3f3777['console']['exception'] = _0x6e83c1;
            _0x3f3777['console']['table'] = _0x6e83c1;
            _0x3f3777['console']['trace'] = _0x6e83c1;
        }
    });
    _0x23c1f6();
    var _0x245e10 = _0x4a2332 + _0x2634dc;
    var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    return _0x245e10 + _0x5d196d;
}
console['log'](_0x556652(10, 20));


可以看到,被混淆加密的字符串已经解密完成,一般情况下,这种程度的代码已经可以进行调试分析了,但我们的追求可以更高,各位同学可以翻到前面看看,测试加密混淆的原始JS才几行,虽然现在加密字符串解密了,但代码里依然存在大量的垃圾指令,还等什么,继续盘

分析一下第一步解密字符串完成后的代码,如下图
AST基础+混淆JS还原的逐步演示_第8张图片

代码里有很多 if (‘xxx’=‘xxx’) 、if (‘xxx’!‘xxx’)、if (‘xxx’ === ‘yyy’)、if (‘xxx’ !== 'yyy)

会点js的一眼就看出来了,这不就是垃圾代码吗,明明一样还搞个判断分支,所以,把所以这类的 if 处理掉,可以将代码量直接砍掉一半,动手

// 处理if('xx'==='xx')
ast = estraverse.replace(ast, {
    enter: function (node,parent) {
        if (node.type == 'IfStatement' &&
            node.test.type == 'BinaryExpression')
        {
            if(node.test.left.value == node.test.right.value) { //if('aaa'==='aaa'){}
                switch (node.test.operator) {
                    case '!==' :  //if('aaa'!=='aaa'){}
                        for (var idx = 0; idx < node.consequent.body.length; idx++) {
                            parent.body.splice(parent.body.indexOf(node), 0, node.consequent.body[idx]);
                        }
                        parent.body.splice(parent.body.indexOf(node), 1);
                        break
 
                    case '===' : //if('aaa'==='aaa'){}
                        for (var idx = 0; idx < node.alternate.body.length; idx++) {
                            parent.body.splice(parent.body.indexOf(node), 0, node.alternate.body[idx]);
                        }
                        parent.body.splice(parent.body.indexOf(node), 1);
                        break
 
                }
            } else {  //if('aaa'==='bbb'){}
                switch (node.test.operator) {
                    case '!==' : //if('aaa'!=='bbb'){}
                        for (var idx = 0; idx < node.consequent.body.length; idx++) {
                            parent.body.splice(parent.body.indexOf(node), 0, node.consequent.body[idx]);
                        }
                        parent.body.splice(parent.body.indexOf(node), 1);
                        break
 
                    case '===' : //if('aaa'==='bbb'){}
                        for (var idx = 0; idx < node.alternate.body.length; idx++) {
                            parent.body.splice(parent.body.indexOf(node), 0, node.alternate.body[idx]);
                        }
                        parent.body.splice(parent.body.indexOf(node), 1);
                        break
 
                }
            }
        }
    }
});

跟第一步字符串解密的代码相比差别不算很大,前面也是一大堆的类型判断,确定遍历到的节点就是我们需要找的if … 这类的垃圾节点,找到后,将正常分支的内容插入到父节点,然后删除当前节点,为什么是这样的操作,同样上图说明
AST基础+混淆JS还原的逐步演示_第9张图片
左侧是我们需要找的 if … 这样的节点,绿色部分代表会执行部分,黑色部分代表不会执行的部分
AST基础+混淆JS还原的逐步演示_第10张图片
而我们需要做的就是,将绿色部分会执行的代码 consequent 节点插入到它的父节点,也就是TryStatement节点下,然后再把整个的 IfStatement 节点删除,这样就完成了对这类垃圾if…语句的处理

看下效果(处理垃圾if语句结果)

function _0x556652(_0x4a2332, _0x2634dc) {
    var _0x94d946 = function () {
        if (fn) {
            var _0x33221e = fn['apply'](context, arguments);
            fn = null;
            return _0x33221e;
        }
    }();
    var _0x23c1f6 = _0x94d946(this, function () {
        var _0x6e83c1 = function () {
        };
        var _0x3f3777;
        try {
            var _0x584a01 = Function('return (function() ' + '{}.constructor("return this")( )' + ');');
            _0x3f3777 = _0x584a01();
        } catch (_0x5365b4) {
            _0x3f3777 = window;
        }
        if (!_0x3f3777['console']) {
            _0x3f3777['console'] = function (_0x4bdab5) {
                var _0x1b640b = firstCall ? function () {
                    if (fn) {
                        var _0x3dd5c2 = fn['apply'](context, arguments);
                        fn = null;
                        return _0x3dd5c2;
                    }
                } : function () {
                };
                firstCall = ![];
                return _0x1b640b;
            }(_0x6e83c1);
        } else {
            _0x3f3777['console']['log'] = _0x6e83c1;
            _0x3f3777['console']['warn'] = _0x6e83c1;
            _0x3f3777['console']['debug'] = _0x6e83c1;
            _0x3f3777['console']['info'] = _0x6e83c1;
            _0x3f3777['console']['error'] = _0x6e83c1;
            _0x3f3777['console']['exception'] = _0x6e83c1;
            _0x3f3777['console']['table'] = _0x6e83c1;
            _0x3f3777['console']['trace'] = _0x6e83c1;
        }
    });
    _0x23c1f6();
    var _0x245e10 = _0x4a2332 + _0x2634dc;
    var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    return _0x245e10 + _0x5d196d;
}
console['log'](_0x556652(10, 20));

是不是清爽多了,当然,还有可以优化的空间,无非就是制定添加规则,这就留给各位当课后作业吧。

最后附上这个混淆JS通过AST还原的最终结果:

function _0x556652(_0x4a2332, _0x2634dc) {
    var _0x245e10 = _0x4a2332 + _0x2634dc;
    var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    return _0x245e10 + _0x5d196d;
}
console['log'](_0x556652(10, 20));

对比一下测试加密源代码:

function MyFun(a,b){
        var c=a+b;
        var d=a*b+c;
        return c+d;
}
console.log(MyFun(10,20))

参考文章: https://www.52pojie.cn/thread-1473162-1-1.html
视频演示:https://www.bilibili.com/video/BV1E64y147Q6/

你可能感兴趣的:(javascript,开发语言,ecmascript)