16回答

9收藏

AST操作实战:全自动解密经obfuscator混淆的加密字符串

信息分享 信息分享 9370 人阅读 | 16 人回复 | 2020-06-02


我们可以在网上的很多地方看到经obfuscator在线网站:



https://obfuscator.io/



混淆的代码,仔细观察其实可以发现,经过obfuscator混淆的前面一段代码结构都是固定的: 大数组+自执行函数+解密字符串函数。




既然代码结构是固定的,那编写解析的AST代码也可以通用。




文本目的:以下面的混淆代码为例,来编写一个部分通用型的插件。
var _0x2075 = ['wrw3EMKc', 'BBdBHWk=', 'wplgd8O5dHbDtFfDucK9CsOS', 'f8KvAcKewoDClg==', 'XcKowo9uOyfChw==', 'XcKowpRzOzDCgMKuw5vCtH8=', 'HmQkw5vDt8OIBDbCpMKdw6Aaw7HDmcKb', 'wpxzdMO4', 'R8KHF1k1w5A=', 'w4LDgcOowrjDhg==', 'w6RKw6PCmVDDpw==', 'w6DDgsKrCsK5wqAwKsOMTkPDilwgB241RVBIw6rCvwpWw5fCo8OSw59pBcK7UlrCucOZHy7DgsO5wpx5J8K5wqbCtMOMwqvCsiUFw5s4JGfDmwQPw7Fawq3CgXlkJyE=', 'VcObYsOHKcKpwpI=', 'KkZfcE52w77ChsKgUQ==', 'CmQsw57DvA==', 'YV7CscOYZg==', 'w5jDt8OUwr46w5c6LsKEPsO0', 'F8OUMQhRw78Q', 'YMKzeTvCpMKzHcKKGSjCj2dJwq3Cj3/ChsKSFVpMw4sZwrg9H8OLw4/DqUlhYlpaa8KYJsO5AcK2wqnCmGhEwqkbdMKKLsO/wpBFMcKlC8OvKUkXZ8KpBsOxw4XDk8K5w4Y6w7VZO8K/wojCqcO2wqQow5Z+w6dew7I3TMObw6Ykw7I=', 'Mk8Bw6QawqU=', 'wo5zw4vCkxvDuSBqwoENw7rCrF3DksKewoPDqMKHNSzCgcK2fcKxPMKbGcKwCW5GZWRpw6fDmgHCjXrCnXE3w4zDqlt3w64lw7JiworDi8Knw5YoW1LDlUbDpkEtGQPDnw==', 'w6lvdMKW', 'w7JFdsOhwrBqwrlMYcKVJRjCuMKQwpLDtMONwprCsMORw4BtRV0oeEQPCgAmMgx2'];
(function(_0xf486e7, _0x2075d7) {
    var _0x5c3a18 = function(_0x5b65b1) {
        while (--_0x5b65b1) {
            _0xf486e7['push'](_0xf486e7['shift']());
        }
    };
    _0x5c3a18(++_0x2075d7);
}(_0x2075, 0xa4));
var _0x5c3a = function(_0xf486e7, _0x2075d7) {
    _0xf486e7 = _0xf486e7 - 0x0;
    var _0x5c3a18 = _0x2075[_0xf486e7];
    if (_0x5c3a['vEVEZj'] === undefined) {
        (function() {
            var _0x2e1ca4;
            try {
                var _0x28e173 = Function('return\x20(function()\x20' + '{}.constructor(\x22return\x20this\x22)(\x20)' + ');');
                _0x2e1ca4 = _0x28e173();
            } catch (_0x16acc9) {
                _0x2e1ca4 = window;
            }
            var _0x16f958 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
            _0x2e1ca4['atob'] || (_0x2e1ca4['atob'] = function(_0x5a7812) {
                var _0x3c7e74 = String(_0x5a7812)['replace'](/=+$/, '');
                var _0x5e030c = '';
                for (var _0x4eaee2 = 0x0, _0x5954ef, _0x29200e, _0x5a128b = 0x0; _0x29200e = _0x3c7e74['charAt'](_0x5a128b++); ~_0x29200e && (_0x5954ef = _0x4eaee2 % 0x4 ? _0x5954ef * 0x40 + _0x29200e : _0x29200e,
                _0x4eaee2++ % 0x4) ? _0x5e030c += String['fromCharCode'](0xff & _0x5954ef >> (-0x2 * _0x4eaee2 & 0x6)) : 0x0) {
                    _0x29200e = _0x16f958['indexOf'](_0x29200e);
                }
                return _0x5e030c;
            }
            );
        }());
        var _0x3acf89 = function(_0x593a19, _0xfee22e) {
            var _0x1b5349 = [], _0x4ddb21 = 0x0, _0x28ed27, _0x4b4996 = '', _0xbdd0c6 = '';
            _0x593a19 = atob(_0x593a19);
            for (var _0x1d6343 = 0x0, _0x3f947e = _0x593a19['length']; _0x1d6343 < _0x3f947e; _0x1d6343++) {
                _0xbdd0c6 += '%' + ('00' + _0x593a19['charCodeAt'](_0x1d6343)['toString'](0x10))['slice'](-0x2);
            }
            _0x593a19 = decodeURIComponent(_0xbdd0c6);
            var _0x1a120c;
            for (_0x1a120c = 0x0; _0x1a120c < 0x100; _0x1a120c++) {
                _0x1b5349[_0x1a120c] = _0x1a120c;
            }
            for (_0x1a120c = 0x0; _0x1a120c < 0x100; _0x1a120c++) {
                _0x4ddb21 = (_0x4ddb21 + _0x1b5349[_0x1a120c] + _0xfee22e['charCodeAt'](_0x1a120c % _0xfee22e['length'])) % 0x100;
                _0x28ed27 = _0x1b5349[_0x1a120c];
                _0x1b5349[_0x1a120c] = _0x1b5349[_0x4ddb21];
                _0x1b5349[_0x4ddb21] = _0x28ed27;
            }
            _0x1a120c = 0x0;
            _0x4ddb21 = 0x0;
            for (var _0x585b7f = 0x0; _0x585b7f < _0x593a19['length']; _0x585b7f++) {
                _0x1a120c = (_0x1a120c + 0x1) % 0x100;
                _0x4ddb21 = (_0x4ddb21 + _0x1b5349[_0x1a120c]) % 0x100;
                _0x28ed27 = _0x1b5349[_0x1a120c];
                _0x1b5349[_0x1a120c] = _0x1b5349[_0x4ddb21];
                _0x1b5349[_0x4ddb21] = _0x28ed27;
                _0x4b4996 += String['fromCharCode'](_0x593a19['charCodeAt'](_0x585b7f) ^ _0x1b5349[(_0x1b5349[_0x1a120c] + _0x1b5349[_0x4ddb21]) % 0x100]);
            }
            return _0x4b4996;
        };
        _0x5c3a['HKkhxp'] = _0x3acf89;
        _0x5c3a['eabUGz'] = {};
        _0x5c3a['vEVEZj'] = !![];
    }
    var _0x5b65b1 = _0x5c3a['eabUGz'][_0xf486e7];
    if (_0x5b65b1 === undefined) {
        if (_0x5c3a['vszZjY'] === undefined) {
            _0x5c3a['vszZjY'] = !![];
        }
        _0x5c3a18 = _0x5c3a['HKkhxp'](_0x5c3a18, _0x2075d7);
        _0x5c3a['eabUGz'][_0xf486e7] = _0x5c3a18;
    } else {
        _0x5c3a18 = _0x5b65b1;
    }
    return _0x5c3a18;
};
var _0x2e1ca4 = function() {
    var _0x564fd8 = !![];
    return function(_0x157886, _0x3f8543) {
        var _0x3aa335 = _0x564fd8 ? function() {
            if (_0x3f8543) {
                var _0x35f411 = _0x3f8543[_0x5c3a('0x15', 'qqhd')](_0x157886, arguments);
                _0x3f8543 = null;
                return _0x35f411;
            }
        }
        : function() {}
        ;
        _0x564fd8 = ![];
        return _0x3aa335;
    }
    ;
}();
setInterval(function() {
    _0x3acf89();
}, 0xfa0);
(function() {
    _0x2e1ca4(this, function() {
        var _0x13f533 = new RegExp('function\x20*\x5c(\x20*\x5c)');
        var _0x28f488 = new RegExp(_0x5c3a('0x13', 'l02m'),'i');
        var _0x5783e7 = _0x3acf89('init');
        if (!_0x13f533['test'](_0x5783e7 + _0x5c3a('0xb', 'mvpW')) || !_0x28f488['test'](_0x5783e7 + _0x5c3a('0x6', 'S&fJ'))) {
            _0x5783e7('0');
        } else {
            _0x3acf89();
        }
    })();
}());
window = {};
window['atob'] = function(_0x44004e) {
    e = _0x5c3a('0x8', 'CwZq');
    var _0x2761c0 = String(_0x44004e)[_0x5c3a('0x9', 'F%XZ')](/=+$/, '');
    if (_0x2761c0[_0x5c3a('0x7', 'KMc0')] % 0x4 == 0x1)
        throw new t('\x27atob\x27\x20failed:\x20The\x20string\x20to\x20be\x20decoded\x20is\x20not\x20correctly\x20encoded.');
    for (var _0x3568b6, _0x228da4, _0x1076e1 = 0x0, _0x242bbc = 0x0, _0x5766d9 = ''; _0x228da4 = _0x2761c0['charAt'](_0x242bbc++); ~_0x228da4 && (_0x3568b6 = _0x1076e1 % 0x4 ? 0x40 * _0x3568b6 + _0x228da4 : _0x228da4,
    _0x1076e1++ % 0x4) ? _0x5766d9 += String[_0x5c3a('0x16', '%Fh)')](0xff & _0x3568b6 >> (-0x2 * _0x1076e1 & 0x6)) : 0x0)
        _0x228da4 = e[_0x5c3a('0xe', 'ivHf')](_0x228da4);
    return _0x5766d9;
}
;
window['btoa'] = function(_0x140387) {
    e = _0x5c3a('0x11', '1t8u');
    for (var _0x5a7683, _0x5c4afc, _0x414c71 = String(_0x140387), _0x3a865d = 0x0, _0x388744 = e, _0x171f9b = ''; _0x414c71[_0x5c3a('0x10', 'G%UZ')](0x0 | _0x3a865d) || (_0x388744 = '=',
    _0x3a865d % 0x1); _0x171f9b += _0x388744[_0x5c3a('0x5', '#%vS')](0x3f & _0x5a7683 >> 0x8 - _0x3a865d % 0x1 * 0x8)) {
        if (_0x5c4afc = _0x414c71[_0x5c3a('0xa', '(eE#')](_0x3a865d += 0.75),
        _0x5c4afc > 0xff)
            throw new t(_0x5c3a('0xf', '!zyq'));
        _0x5a7683 = _0x5a7683 << 0x8 | _0x5c4afc;
    }
    return _0x171f9b;
}
;
function _0x3acf89(_0x1a61bd) {
    function _0x50b4d2(_0x5c1045) {
        if (typeof _0x5c1045 === 'string') {
            return function(_0xaf1ee8) {}
            ['constructor'](_0x5c3a('0x3', 'mvpW'))[_0x5c3a('0xc', 'dtRw')](_0x5c3a('0x1', 'g1Ep'));
        } else {
            if (('' + _0x5c1045 / _0x5c1045)['length'] !== 0x1 || _0x5c1045 % 0x14 === 0x0) {
                (function() {
                    return !![];
                }
                ['constructor']('debu' + 'gger')[_0x5c3a('0x4', '%Fh)')](_0x5c3a('0x0', 'zu[n')));
            } else {
                (function() {
                    return ![];
                }
                [_0x5c3a('0x2', 'g1Ep')](_0x5c3a('0x12', 'LPae') + _0x5c3a('0x14', 'N5*X'))['apply'](_0x5c3a('0xd', 'qOO9')));
            }
        }
        _0x50b4d2(++_0x5c1045);
    }
    try {
        if (_0x1a61bd) {
            return _0x50b4d2;
        } else {
            _0x50b4d2(0x0);
        }
    } catch (_0x524e63) {}
}



分析这段代码结构:





它前面的部分是由 大数组(_0x2075) + 自执行函数(function(_0xf486e7, _0x2075d7) ) + 解密函数(_0x5c3a) 组成.




而在下面的代码中 很多地方调用了 _0x5c3a 这个函数,如:



_0x5c3a('0x15', 'qqhd')



观察分析,函数名是_0x5c3a,参数均是 StringLiteral 类型的节点,(当然也遇到过参数不是字符串的)。




通过操作AST把类似 _0x5c3a('0x15', 'qqhd') 这样的函数调用换成它对应的字符串:apply。




第一步:把前面提到的固定框架(大数组+自执行+解密函数),添加到本地环境中,并确保它能正常运行。




第二步:处理CallExpression 节点。

1.遍历 CallExpression 节点,如果遍历的节点函数名不是 _0x5c3a,则不用理会;

如果是 _0x5c3a,则调用该函数及两个实参,计算出函数值,然后将 值 替换 这个 CallExpression 节点。




2.计算值的时候如果无法计算,则会抛出异常,这时捕获异常就行了。




第三步:如果第二步没有任何异常,则表示 第一步的固定框架完成了它的使命,可以进行删除操作,否则不予删除。







来看第一步,先判断这个固定的框架能否直接使用,本例中是可以直接使用的,即运行不会报错。




把所有的混淆代码,全部拷贝至 在线解析网站,观察固定框架的结构:










可以看到,这三个节点是在 'Program' 的 'body'下面,因此我们遍历 'Program'节点:/



const visitor = {
    "rogram"(path) {
        replace_call_to_string(path)
    },
}



下面进行 replace_call_to_string 函数的编写:




一:拿到固定框架的源码:




首先,获取数组节点,它是body数组的第一个索引



let arr_path   = path.get('body.0');



将其转换成源码:
let code  = arr_path.toString();



获取自执行函数节点,它是body数组的第二个索引



let shift_path = path.get('body.1');



获取第二个节点的源代码,这里注意了,在经过AST转换以后,自执行函数外面的()没有了。



code  += '!' + shift_path.toString();



获取解密函数节点,它是body数组的第三个索引



let call_path  = path.get('body.2');



这里还需要获取函数名,因为遍历的时候需要判断



let call_name  = call_path.get('declarations.0.id').toString();



获取第三个节点的源代码:
code  += call_path.toString();



就这种,我们拿到了固定框架的源码。




二:将源码添加到本地环境,直接eval即可:
eval(code);



此时,我们还需要一个flag,来判断前面三个节点能否进行删除。

默认可以删除,如果抛出异常,则不能进行删除



let can_be_delete = true;



三:遍历 CallExpression 表达式,因此此时的path表示的是 Program,直接遍历它就行,可以遍历到每个 CallExpression节点。



path.traverse({
      CallExpression: function(_path) 
      {
      },
  });



再就是编写 遍历 CallExpression 节点 的插件了。

我们把 _0x5c3a('0x15', 'qqhd') 这个表达式复制到在线解析网站,看看节点的属性:










也就一个 callee 节点 和 arguments 节点.




在这里,直接拿到函数名与 call_name 进行比较,不是则返回:



let callee = _path.get('callee');
if(callee.toString() !== call_name)
  return;



再就是计算函数的值,这里我直接拿到 CallExpression 节点的源代码,然后进行 eval,就拿到了结果,因为有可能会异常,因此需要 try语句:
try {
    let value = eval(_path.toString());
    _path.replaceWith(t.valueToNode(value));

catch (e) {
    can_be_delete = false;
}



相信聪明的你一定可以看得懂。




四:判断是否有异常抛出,如果没有,则将前面三个节点删除:



for (let i = 0; can_be_delete && i < 3; i++) {
    path.get('body.0').remove();
}



所以,代码合并起来是这样的:











这样就完成了一个半通用型插件的编写,为什么说是半通用型,因为obfuscator 还有另外一个形式,就是直接将前三个节点的代码运行,会内存溢出,导致出错,其实也就是在上面的代码多加几行判断罢了。




怎么样,是不是非常的简单?大佬们口中的AST,真没有那么神秘,只要有一定的编程基础,再了解一些babel库相关的方法,相信你也可以写出很优雅功能强大的代码。





本文首发地址:https://mp.weixin.qq.com/s/L4FOxc7fwKB7bq0eQb2r9g









本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
分享到:
回复

使用道具 举报

回答|共 16 个

daisixuan

发表于 2020-6-2 21:31:18 | 显示全部楼层

芜湖 tql老板
回复

使用道具 举报

北落师门

发表于 2020-6-2 21:41:52 | 显示全部楼层

围观
回复

使用道具 举报

Thor

发表于 2020-6-2 21:42:19 | 显示全部楼层

老板6
回复

使用道具 举报

yuanbug

发表于 2020-7-25 23:09:29 | 显示全部楼层

之前尝试用java做这个事情,编辑ast的json来修改,结果难写得不行。看来还是要用js来打败js啊。
回复

使用道具 举报

悦来客栈的老板

发表于 2020-8-1 08:43:16 | 显示全部楼层

是的,大佬。AST用来解混淆非常的棒!我使用的babel库也是非常的强大。
回复

使用道具 举报

西木

发表于 2020-8-27 14:53:54 | 显示全部楼层

大佬
回复

使用道具 举报

白白嫩嫩

发表于 2020-8-27 14:54:31 | 显示全部楼层

nice
回复

使用道具 举报

白白嫩嫩

发表于 2020-8-27 14:54:38 | 显示全部楼层

太棒了
回复

使用道具 举报

白白嫩嫩

发表于 2020-8-27 14:54:51 | 显示全部楼层

在我心里,大佬一直是大鸡鸡
回复

使用道具 举报