Js Ast 一部曲:高完整度还原某V5 的加密
本文由 简悦
SimpRead 转码, 原文地址 bbs.nightteam.cnNanda
声明:本文内容仅供学习交流,严禁用于商业用途,请于24 小时内删除。 今天让我们玩一点有意思的东西,使用ast 解决某 v5 加密的js 代码。 作为一个爬虫爱好者,可能你并不会使用ast 处理混淆文件,但肯定是听过这个名字的,对吧?而且被传的神乎其神,又是编译原理,又是词法语法分析的。但实际上,底层的东西都已经有大佬封装好了,并不需要从头造轮子,而且ast 对逆向工作中是真的很有效果!所以,最近我也是研究学习了一下,本次我就使用babel 这一工具,为大家揭开ast 的神秘面纱。 既然是第一部曲,主要是让大家初步了解ast ,所以我们选择一个常规难度就好了,加密配置如下图:
先上前后代码对比吧,还原前:
/*
;var encode_version = ‘sojson.v5’
, jrkqk = ’’
, _0x3318 = [’eMO1woTDnRU=’, ‘6L6h5pmt5LuD5Lq85Lq857Gi5Ymtwqh55pCG5LyT44OQ’, ‘56us6ZaG5o2S6auk57ufDuKDp8KuwqzliYHlrpbig7NT5ZCXwrrigaJPw5bopp3lrb7ig4zCt+++heS/peWMheS+leeYlCB8w7bjga4=’, ‘Z3rCqsKQ’, ‘wq12EsOow7A=’, ‘wq3Di8Kpw63CisKZwr4=’, ‘RMOdwpc=’, ‘NTREw4FsCMKIQsKB’, ‘54qF5p+/5Y6E772jw5LDkeS8meWsjOafneW+uOepq++9pei9v+itk+aXt+aMh+aKgOS7rOealOW3seS8vw==’, ‘5Yis6Zia54ms5p225Y6H772nKMO85L2n5a+b5p+W5b2756iM’, ‘VMOBwofDtDscbsOBw7k=’, ’eAglw6V9’, ‘dFUCw7LCsA==’, ‘wqzCij0=’, ‘5YuX6Zip54ig5p6p5Y+J772cwqbDpOS+guWvgeadpOW+reermQ==’];
(function(_0x1cab0b, _0x9fc93e) {
var _0x50aee6 = function(_0x5f6b93) {
while (–_0x5f6b93) {
_0x1cab0b‘push’;
}
};
_0x50aee6(++_0x9fc93e);
}(_0x3318, 0x12d));
var _0x4d94 = function(_0x387cf0, _0x46d6a3) {
_0x387cf0 = _0x387cf0 - 0x0;
var _0x1c7cd3 = _0x3318[_0x387cf0];
if (_0x4d94[‘initialized’] === undefined) {
(function() {
var _0x9672bc = typeof window !== ‘undefined’ ? window : typeof process === ‘object’ && typeof require === ‘function’ && typeof global === ‘object’ ? global : this;
var _0x47acfd = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’;
_0x9672bc[‘atob’] || (_0x9672bc[‘atob’] = function(_0x4f114b) {
var _0xda79c1 = String(_0x4f114b)‘replace’;
for (var _0x2456af = 0x0, _0xc64c02, _0x5e6923, _0x48f516 = 0x0, _0x448f43 = ‘’; _0x5e6923 = _0xda79c1‘charAt’; ~_0x5e6923 && (_0xc64c02 = _0x2456af % 0x4 ? _0xc64c02 * 0x40 + _0x5e6923 : _0x5e6923,
_0x2456af++ % 0x4) ? _0x448f43 += String‘fromCharCode’ : 0x0) {
_0x5e6923 = _0x47acfd‘indexOf’;
}
return _0x448f43;
}
);
}());
var _0x15dca8 = function(_0xa971bc, _0x5e580d) {
var _0x2a7b0e = [], _0x18c27c = 0x0, _0x8cce7, _0x21fe1e = ‘’, _0x21ee82 =’’;
_0xa971bc = atob(_0xa971bc);
for (var _0x465e88 = 0x0, _0x1826b5 = _0xa971bc[’length’]; _0x465e88 < _0x1826b5; _0x465e88++) {
_0x21ee82 += ‘%’ + (‘00’ + _0xa971bc‘charCodeAt’’toString’)‘slice’;
}
_0xa971bc = decodeURIComponent(_0x21ee82);
for (var _0x559b8b = 0x0; _0x559b8b < 0x100; _0x559b8b++) {
_0x2a7b0e[_0x559b8b] = _0x559b8b;
}
for (_0x559b8b = 0x0; _0x559b8b < 0x100; _0x559b8b++) {
_0x18c27c = (_0x18c27c + _0x2a7b0e[_0x559b8b] + _0x5e580d‘charCodeAt’) % 0x100;
_0x8cce7 = _0x2a7b0e[_0x559b8b];
_0x2a7b0e[_0x559b8b] = _0x2a7b0e[_0x18c27c];
_0x2a7b0e[_0x18c27c] = _0x8cce7;
}
_0x559b8b = 0x0;
_0x18c27c = 0x0;
for (var _0x325076 = 0x0; _0x325076 < _0xa971bc[’length’]; _0x325076++) {
_0x559b8b = (_0x559b8b + 0x1) % 0x100;
_0x18c27c = (_0x18c27c + _0x2a7b0e[_0x559b8b]) % 0x100;
_0x8cce7 = _0x2a7b0e[_0x559b8b];
_0x2a7b0e[_0x559b8b] = _0x2a7b0e[_0x18c27c];
_0x2a7b0e[_0x18c27c] = _0x8cce7;
_0x21fe1e += String‘fromCharCode’;
}
return _0x21fe1e;
};
_0x4d94[‘rc4’] = _0x15dca8;
_0x4d94[‘data’] = {};
_0x4d94[‘initialized’] = !![];
}
var _0x281020 = _0x4d94[‘data’][_0x387cf0];
if (_0x281020 === undefined) {
if (_0x4d94[‘once’] === undefined) {
_0x4d94[‘once’] = !![];
}
_0x1c7cd3 = _0x4d94‘rc4’;
_0x4d94[‘data’][_0x387cf0] = _0x1c7cd3;
} else {
_0x1c7cd3 = _0x281020;
}
return _0x1c7cd3;
};
var a = {}
, b = {};
(function(_0x117d09, _0x110647) {
var _0x4845bf = {
‘XhuzS’: _0x4d94(‘0x0’, ‘W7bQ’),
‘iUTPo’: _0x4d94(‘0x1’, ‘]2T3’)
};
_0x117d09[_0x4d94(‘0x2’, ‘WIC5’)] = _0x4845bf[‘XhuzS’];
_0x110647[‘adinfo’] = _0x4845bf[_0x4d94(‘0x3’, ‘Wka7’)];
_0x110647[_0x4d94(‘0x4’, ‘D8d!’)] = ‘如果您的
}(a, b));
;(function(_0x182aaa, _0x4c3590, _0x55392e) {
var _0x4b40d0 = {
‘gTfic’: _0x4d94(‘0x5’, ‘oqkX’),
‘GXIQH’: function _0x38020c(_0x65ebe0, _0x2dc82b) {
return _0x65ebe0 === _0x2dc82b;
},
‘xyVxG’: _0x4d94(‘0x6’, ‘JsgM’),
‘qeHKL’: function _0x3e069c(_0x501f24, _0x41e6d1) {
return _0x501f24 + _0x41e6d1;
},
‘EkYag’: _0x4d94(‘0x7’, ‘^7hL’),
‘YZgLH’: _0x4d94(‘0x8’, ‘g@wB’)
};
_0x55392e = ‘al’;
try {
_0x55392e += _0x4b40d0[‘gTfic’];
_0x4c3590 = encode_version;
if (!(typeof _0x4c3590 !== _0x4d94(‘0x9’, ‘oqkX’) && _0x4b40d0_0x4d94(‘0xa’, ‘M*Wm’))) {
_0x182aaa_0x55392e](‘删除’, _0x4b40d0[‘EkYag’]));
}
} catch (_0x781e53) {
if (‘aYN’ === _0x4d94(‘0xc’, ‘^7hL’)) {
_0x182aaa_0x55392e);
} else {
_0x182aaa_0x55392e]);
}
}
}(window));
;encode_version = ‘sojson.v5’;
还原后:
*/
var a = {},
b = {};
(function () {
a[“info”] = “这是一个一系列
b[‘adinfo’] = “站长接高级
b[“warning”] = ‘如果您的
})();
;
(function () {
_0x55392e = ‘al’;
try {
_0x55392e += “ert”;
_0x4c3590 = encode_version;
if (!(typeof _0x4c3590 !== “undefined” && _0x4c3590 === “sojson.v5”)) {
window[_0x55392e](‘删除’ + “版本号,
}
} catch (_0x781e53) {
if (‘aYN’ === “aYN”) {
window[_0x55392e](“删除版本号,
} else {
window[_0x55392e](“删除版本号,
}
}
})();
;
encode_version = ‘sojson.v5’;
是不是感觉效果很显著,那就认真看看本文是怎么实现的吧,哈哈。
下面我来介绍下还原流程
0,你肯定是要了解
a>https://github.com/yacan8/blog/blob/master/posts/JavaScript%E6%8A%BD%E8%B1%A1%E8%AF%AD%E6%B3%95%E6%A0%91AST.md:这是基础,多翻几遍,最好背过,才能更得心应手的使用
总体,程序可分为两部分,上面是参数加密及转换的部分,在本例中,以
2,结构已经清楚了,那我们就先用
用到的依赖(
const parser = require("@babel/parser");
const template = require("@babel/template").default;
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
const path = require(‘path’);
const fs = require(‘fs’)
引入
const {decryptStr, decryptStrFnName} = require(’./module’);
读取原始
const ast = parser.parse(jsStr);
CallExpression:funToStr
})
function funToStr(path) {
var curNode = path.node;
if(curNode.callee.name === decryptStrFnName && curNode.arguments.length === 2)
{
var strC = decryptStr(curNode.arguments[0].value, curNode.arguments[1].value);
}
}
使用
console.log(code);
转后效果图:
嗯,已经清晰很多了,但是可以看到
4,观察可见,调用方式如
_0x4b40d0“qeHKL”
或
_0x4b40d0[‘gTfic’]
可以看到这次是
traverse(ast,{
VariableDeclarator:callToStr
})
function callToStr(path) {
var node = path.node;
if (!t.isObjectExpression(node.init))
return;
var objPropertiesList = node.init.properties;
if (objPropertiesList.length==0)
return;
var objName = node.id.name;
objPropertiesList.forEach(prop => {
var key = prop.key.value;
if(!t.isStringLiteral(prop.value))
{
var retStmt = prop.value.body.body[0];
var fnPath = path.getFunctionParent();
fnPath.traverse({
CallExpression: function (_path) {
if (!t.isMemberExpression(_path.node.callee))
return;
var _node = _path.node.callee;
if (!t.isIdentifier(_node.object) || _node.object.name !== objName)
return;
if (!t.isStringLiteral(_node.property) || _node.property.value != key)
return;
var args = _path.node.arguments;
if (t.isBinaryExpression(retStmt.argument) && args.length===2)
{
_path.replaceWith(t.binaryExpression(retStmt.argument.operator, args[0], args[1]));
}
else if(t.isLogicalExpression(retStmt.argument) && args.length==2)
{
_path.replaceWith(t.logicalExpression(retStmt.argument.operator, args[0], args[1]));
}
else if(t.isCallExpression(retStmt.argument) && t.isIdentifier(retStmt.argument.callee))
{
_path.replaceWith(t.callExpression(args[0], args.slice(1)))
}
}
})
}
else{
var retStmt = prop.value.value;
fnPath.traverse({
MemberExpression:function (_path) {
var _node = _path.node;
if (!t.isIdentifier(_node.object) || _node.object.name !== objName)
return;
if (!t.isStringLiteral(_node.property) || _node.property.value != key)
return;
_path.replaceWith(t.stringLiteral(retStmt))
}
})
}
});
}
ok,经过上面的处理,那些对象也被干掉了,让我们将
/*
var a = {},
b = {};
(function (_0x117d09, _0x110647) {
_0x117d09[“info”] = “这是一个一系列
_0x110647[‘adinfo’] = “站长接高级
_0x110647[“warning”] = ‘如果您的
})(a, b);
;
(function (_0x182aaa, _0x4c3590, _0x55392e) {
_0x55392e = ‘al’;
try {
_0x55392e += “ert”;
_0x4c3590 = encode_version;
if (!(typeof _0x4c3590 !== “undefined” && _0x4c3590 === “sojson.v5”)) {
_0x182aaa[_0x55392e](‘删除’ + “版本号,
}
} catch (_0x781e53) {
if (‘aYN’ === “aYN”) {
_0x182aaa[_0x55392e](“删除版本号,
} else {
_0x182aaa[_0x55392e](“删除版本号,
}
}
})(window);
;
encode_version = ‘sojson.v5’;
自执行函数的
traverse(ast,{
ExpressionStatement:convParam
})
function convParam(path) {
var node = path.node;
if (!t.isCallExpression(node.expression))
return;
if (node.expression.arguments == undefined || node.expression.callee.params == undefined || node.expression.arguments.length> node.expression.callee.params.length)
return;
var argumentList = node.expression.arguments;
var paramList = node.expression.callee.params;
for (var i = 0; i<argumentList.length; i++)
{
var argumentName = argumentList*.name;
var paramName = paramList*.name;
path.traverse({
MemberExpression:function (_path) {
var _node = _path.node;
if (!t.isIdentifier(_node.object) || _node.object.name !== paramName)
return;
_node.object.name = argumentName;
}
});
}
node.expression.callee.params = [];
}
都搞定了,就是最开始展示的那个结果了。让我们再来看一下:
b = {};
(function () {
a[“info”] = “这是一个一系列
b[‘adinfo’] = “站长接高级
b[“warning”] = ‘如果您的
})();
;
(function () {
_0x55392e = ‘al’;
try {
_0x55392e += “ert”;
_0x4c3590 = encode_version;
if (!(typeof _0x4c3590 !== “undefined” && _0x4c3590 === “sojson.v5”)) {
window[_0x55392e](‘删除’ + “版本号,
}
} catch (_0x781e53) {
if (‘aYN’ === “aYN”) {
window[_0x55392e](“删除版本号,
} else {
window[_0x55392e](“删除版本号,
}
}
})();
;
encode_version = ‘sojson.v5’;
简单明了,这要是再看不懂就说不过去了吧
结尾:至此,对该加密的还原就告一段落了,可以看到还原后的代码完全足够让我们愉快的
[ttreply]
https://github.com/chencchen/webcrawler/tree/master/%E4%BD%BF%E7%94%A8ast%E5%AF%B9%E6%9F%90v5%E5%8A%A0%E5%AF%86%E8%BF%9B%E8%A1%8C%E8%BF%98%E5%8E%9F
[/ttreply]
__