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/ 第二次替换有问题, "wxqXe" !== "wxqXe" 走的是else分支 你提取了if的分支 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 字符串编码转换 谢谢分享,涨姿势了 谢谢分享! 我認識了一種新的數據類型。
還是有收穫的!
挺好,
不像看別的技術帖子,
似懂非懂的。 我只能说厉害厉害。。。 膜拜大佬 有几个包楼主没有说明是干什么用的
const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
const iconv = require("iconv-lite"); 膜拜膜拜,希望大佬早日上岸