null119 发表于 2021-7-9 13:46

AST - 并没有想象中那么神秘

本帖最后由 null119 于 2021-7-9 16:19 编辑

0x0、前言部分


PS:本来准备发到编程区,不过编程区没有JavaScript 版块,另外本文最主的内容是如何通过AST处理还原加密混淆JS代码,应该也属于逆向破解的范畴,所以就发在这个版块,如有不妥,麻烦管理安排一下。

前几天在论坛看到一篇由 beattortoise 写的关于AST还原混淆JS的文章,回想起曾经折腾AST走过的那么多弯路,真是一把鼻涕一把泪,所以趁这个机会,给那些想学习AST却苦于无处入门的朋友们做个入门引导,其实AST并不神秘,也并没有想象中的那么的高大上!
文章就两个部分,AST基础+混淆JS还原的逐步演示。师傅领进门、修行还得靠个人! 共勉!!!


0x1、基础部分

AST:Abstract Syntax Tree(抽象语法树),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
我知道,看完定义的你八成是一脸懵逼,什么叫做源代码语法结构的抽象表示?不用害怕,我在这里简单使用一句话为例来帮助大家理解:

今天你喝水了吗?

大家都知道这句话的意思就是问今天你喝水了吗,但你知道这句话是由哪些成分组成的吗?



以前上学的时候语文课肯定都学过,句子里有主语,谓语,宾语,定语等等,句子中的词又分为名词、动词、介词....各种词,当这些词按照某种规律形式组合以后,就形成了上面的那句话,
而现在正在看文章的你肯定知道并理解上面那句话的意思,但是,你不一定知道这句话中每个词的词性,不知道哪个是主语,哪个是谓语,这就跟你知道 JavaScript,却不了解AST是一回事。
所以:不会AST这件事本身不会对你学习使用JavaScript有任何影响,但如果你理解并掌握了AST,那么JavaScript 可能在你眼里就有些不太一样了,从某种程度上来说,了解掌握AST可以帮助你真正吃透 JavaScript 的语言精髓。
既然如此,那怎样才能快速的了解AST?

答案很简单,随写写句JS拿去AST分析一下,然后对着看看,相信聪明的你很快就能有所了解。
在线 AST语法结构解析网站:https://astexplorer.net/
下面来看看最最简单的JS

var a=1;
代码对应的AST语法树结构:




从上往下看,有Program、declarations、VariableDeclarator、Identifier、Literal,这些都AST的结构类型,除了这些还有很多,列表如下:



知道了这些有什么用呢?除了上面说的可以加深对JS的深层理解外,最大的用处就是对 JavaScript 代码进行混淆以及还原了。

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


0x2、混淆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;
    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;
            }
            for (_0x94001b = 0x0; _0x94001b < 0x100; _0x94001b++) {
                _0x434903 = (_0x434903 + _0x3e9282 + _0x1f613d['charCodeAt'](_0x94001b % _0x1f613d['length'])) % 0x100;
                _0x33fc11 = _0x3e9282;
                _0x3e9282 = _0x3e9282;
                _0x3e9282 = _0x33fc11;
            }
            _0x94001b = 0x0;
            _0x434903 = 0x0;
            for (var _0x27a73e = 0x0; _0x27a73e < _0x257551['length']; _0x27a73e++) {
                _0x94001b = (_0x94001b + 0x1) % 0x100;
                _0x434903 = (_0x434903 + _0x3e9282) % 0x100;
                _0x33fc11 = _0x3e9282;
                _0x3e9282 = _0x3e9282;
                _0x3e9282 = _0x33fc11;
                _0x249601 += String['fromCharCode'](_0x257551['charCodeAt'](_0x27a73e) ^ _0x3e9282[(_0x3e9282 + _0x3e9282) % 0x100]);
            }
            return _0x249601;
      };
      _0x7969['tMJLOX'] = _0x570ba5;
      _0x7969['vDRtQv'] = {};
      _0x7969['UkyMFX'] = !![];
    }
    var _0x4b72d1 = _0x7969['vDRtQv'];
    if (_0x4b72d1 === undefined) {
      if (_0x7969['SqRviR'] === undefined) {
            _0x7969['SqRviR'] = !![];
      }
      _0x796962 = _0x7969['tMJLOX'](_0x796962, _0x305a4c);
      _0x7969['vDRtQv'] = _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(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 = function(_0x1c2524) {
                                        var _0x2972cc = {};
                                        _0x2972cc = _0x1c2524;
                                        _0x2972cc = _0x1c2524;
                                        _0x2972cc = _0x1c2524;
                                        _0x2972cc = _0x1c2524;
                                        _0x2972cc = _0x1c2524;
                                        _0x2972cc = _0x1c2524;
                                        _0x2972cc = _0x1c2524;
                                        _0x2972cc = _0x1c2524;
                                        return _0x2972cc;
                                    } (func);
                              } else {
                                    var _0x34a69a = _0x13d9c2(_0x550c05, arguments);
                                    _0x13d9c2 = null;
                                    return _0x34a69a;
                              }
                            }
                        } else {
                            that = window;
                        }
                  }: function() {};
                  _0x43616c = ![];
                  return _0x296878;
                } else {
                  var _0x2c3174 = {};
                  _0x2c3174 = func;
                  _0x2c3174 = func;
                  _0x2c3174 = func;
                  _0x2c3174 = func;
                  _0x2c3174 = func;
                  _0x2c3174 = func;
                  _0x2c3174 = func;
                  _0x2c3174 = 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 = _0x6e83c1;
                _0x3f3777 = _0x6e83c1;
                _0x3f3777 = _0x6e83c1;
                _0x3f3777zyO')] = _0x6e83c1;
                _0x3f3777 = _0x6e83c1;
                _0x3f3777 = _0x6e83c1;
                _0x3f3777 = _0x6e83c1;
                _0x3f3777 = _0x6e83c1;
            }
      }
      if (!_0x3f3777) {
            if (_0x7969('0x4b', 'NGkH') !== _0x7969('0x3c', 'O(f6')) {
                _0x3f3777 = function(_0x4bdab5) {
                  if (_0x7969('0x29', 'zEsY') === _0x7969('0x46', 'wdO$')) {
                        var _0x5f33bc = {};
                        _0x5f33bc = _0x4bdab5;
                        _0x5f33bc = _0x4bdab5;
                        _0x5f33bc = _0x4bdab5;
                        _0x5f33bc = _0x4bdab5;
                        _0x5f33bc = _0x4bdab5;
                        _0x5f33bc = _0x4bdab5;
                        _0x5f33bc = _0x4bdab5;
                        _0x5f33bc = _0x4bdab5;
                        return _0x5f33bc;
                  } else {
                        var _0x1b640b = firstCall ?
                        function() {
                            if (fn) {
                              var _0x3dd5c2 = fn(context, arguments);
                              fn = null;
                              return _0x3dd5c2;
                            }
                        }: function() {};
                        firstCall = ![];
                        return _0x1b640b;
                  }
                } (_0x6e83c1);
            } else {
                var _0x345d2c = fn(context, arguments);
                fn = null;
                return _0x345d2c;
            }
      } else {
            _0x3f3777 = _0x6e83c1;
            _0x3f3777 = _0x6e83c1;
            _0x3f3777 = _0x6e83c1;
            _0x3f3777 = _0x6e83c1;
            _0x3f3777 = _0x6e83c1;
            _0x3f3777 = _0x6e83c1;
            _0x3f3777 = _0x6e83c1;
            _0x3f3777 = _0x6e83c1;
      }
    });
    _0x23c1f6();
    var _0x245e10 = _0x4a2332 + _0x2634dc;
    var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    return _0x245e10 + _0x5d196d;
}
console(_0x556652(0xa, 0x14));

检查混淆代码是否可以正常执行:



混淆后的代码可以正常执行,

对混淆后的代码进行初步分析



上图中可以很清晰的看到,标注红色框的函数 _0x7969 在整个被混淆的JS中大量出现,并且,都是以 _0x7969['xxx'] 或 _0x7969('xxx','xxx') 的形式出现,其中的xxx则全部都是乱七八糟的字符串,由此可以得出结论:

_0x7969 这个函数就是这个混淆JS 加密字符串的解密函数
使用支持JS格式的文本编辑器,对混淆后的代码进行收缩,如下



我给标注出了解密函数跟执行函数(或者叫原始功能函数,叫啥无所谓,明白意思就行)

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

我们先将混沌JS中的解密函数复制出来保存到de.js,将执行函数部分复制出来保存为en.js,再新建一个obTest.js,用于编写AST还原代码


de.js 需要添加一行,导出函数,名称就是上面的 _0x7969,如下:



打开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.type == 'Literal' && //标注3
            node.arguments.type == 'Literal')//标注4
      {
            var val = de._0x7969(node.arguments.value,node.arguments.value);//标注5
            return {
                type: esprima.Syntax.Literal,
                value: val,
                raw: val
            }
      }
    }
});

code = escodegen.generate(ast)//将AST转换为JS
console.log(code)

上面代码看不懂?没关系,一张图搞定



左边为混淆代码,右边为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才几行,虽然现在加密字符串解密了,但代码里依然存在大量的垃圾指令,还等什么,继续盘

分析一下第一步解密字符串完成后的代码,如下图



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

会点js的一眼就看出来了,这不就是垃圾代码吗,明明一样还搞个判断分支,所以,把所以这类的 if 处理掉,可以将代码量直接砍掉一半,动手
// 处理if('xx'==='xx')
var ast = esprima.parse(code);
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);
                        }
                        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);
                        }
                        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);
                        }
                        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);
                        }
                        parent.body.splice(parent.body.indexOf(node), 1);
                        break

                }
            }
      }
    }
});

跟第一步字符串解密的代码相比差别不算很大,前面也是一大堆的类型判断,确定遍历到的节点就是我们需要找的if .... 这类的垃圾节点,找到后,将正常分支的内容插入到父节点,然后删除当前节点,为什么是这样的操作,同样上图说明



左侧是我们需要找的 if .... 这样的节点,绿色部分代表会执行部分,黑色部分代表不会执行的部分



而我们需要做的就是,将绿色部分会执行的代码 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.bilibili.com/video/BV1E64y147Q6/

moxueque 发表于 2022-3-1 11:41

第二次替换有问题, "wxqXe" !== "wxqXe" 走的是else分支 你提取了if的分支

null119 发表于 2021-7-9 16:17

bhbhxy 发表于 2021-7-9 16:06
有几个包楼主没有说明是干什么用的
const esprima = require('esprima');
const estraverse = require('e ...

百度一下不是什么难事吧。。。。
Esprima ECMAScript(JavaScript) 解析架构,主要用于多用途分析。
estraverse 语法树遍历辅助库(提供了两个静态方法,estraverse.traverse 和 estraverse.replace。前者单纯遍历 AST 的节点,通过返回值控制是否继续遍历到叶子节点;而 replace 方法则可以在遍历的过程中直接修改 AST,实现代码重构功能。)
escodegen AST的 ECMAScript (也称为JavaScript)代码生成器
iconv 字符串编码转换

smartfind 发表于 2021-7-9 13:53

谢谢分享,涨姿势了

柒呀柒 发表于 2021-7-9 14:10

谢谢分享!

列明 发表于 2021-7-9 14:15

我認識了一種新的數據類型。
還是有收穫的!
挺好,
不像看別的技術帖子,
似懂非懂的。

袁煜914 发表于 2021-7-9 15:02

cacuts 发表于 2021-7-9 15:27

我只能说厉害厉害。。。

Cay丶 发表于 2021-7-9 15:47

膜拜大佬

bhbhxy 发表于 2021-7-9 16:06

有几个包楼主没有说明是干什么用的
const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
const iconv = require("iconv-lite");

key_user 发表于 2021-7-9 16:29

膜拜膜拜,希望大佬早日上岸
页: [1] 2 3 4
查看完整版本: AST - 并没有想象中那么神秘