AST逆向还原(一)字符串递归解密
相关链接
AST详解与运用 - 知乎 (zhihu.com)
Babel 是什么? · Babel 中文文档 | Babel中文网 (babeljs.cn)
本文以及后续推文如无特殊说明,默认都是在babel的环境编译的,本文仅限相关从业爱好者学习使用。
话不多说,直接还原逆向实践,原站点:
aHR0cHM6Ly93d3cuc3VwZXJiZWQuY24v
拿其中一段代码分析:
;function changeUserState(_0x5d5847) {
var _0x46c769 = _0x551a
, _0x43508c = {
'FPUIO': function(_0x296d3e, _0x32d69f) {
return _0x296d3e(_0x32d69f);
},
'mzIBk': _0x46c769(0x22a, 'XRe['),
'RysRT': function(_0x3eb3cd, _0x3b1ace) {
return _0x3eb3cd(_0x3b1ace);
},
'YBOFo': 'layui-hide',
'cBrCl': _0x46c769(0x429, '7awU'),
'FQNWn': _0x46c769(0x42d, 'Q)Me')
};
_0x43508c[_0x46c769(0x1ef, '7awU')]($, _0x43508c[_0x46c769(0x1c9, 'z0@N')])['removeClass'](_0x46c769(0x124, 'hZwl')),
_0x43508c[_0x46c769(0x21c, 'z0@N')]($, _0x46c769(0x3b1, '1jc1'))['addClass'](_0x43508c[_0x46c769(0x1cc, 'fK(%')]),
_0x43508c[_0x46c769(0x433, 'hZwl')]($, _0x43508c[_0x46c769(0x35c, 'bQ0(')])[_0x46c769(0xfc, 'u8pZ')](_0x5d5847[_0x46c769(0x388, 'dSI1')] || _0x5d5847['nickName']),
_0x5d5847[_0x46c769(0x108, 'dSI1')] && _0x43508c[_0x46c769(0x487, '2Ku!')]($, _0x43508c[_0x46c769(0x398, 'u8pZ')])['removeClass'](_0x43508c[_0x46c769(0x59a, '@iK5')]);
}
通过AST explorer可以发现,只要我们把拿到的stringLiteral替换callExpression即可
还原的ast代码
const fs = require('fs');
const parser = require('@babel/parser');
const {default: traverse} = require('@babel/traverse');
const types = require("@babel/types");
const {default: generator} = require("@babel/generator");
let jsCode = fs.readFileSync('decodeDemo.js', {
encoding: 'utf-8'
})
let ast = parser.parse(jsCode);
// 加密数组
let bigArr = [
...
]
// 解密函数
function _0x551a(_0x2e7fdc, _0x351414) {
...
}
function decryptStr(path) {
// 获取binding
let bindings = path.scope.getBinding(path.node.id.name);
// 获取引用的path
let refPaths = bindings && bindings.referencePaths;
if (refPaths) {
// 遍历
for (let i = 0; i < refPaths.length; i++) {
// 获取父级Path
let parentPath = refPaths[i].parentPath;
if (parentPath.isCallExpression()) {
let argu = parentPath.node.arguments;
if (argu && argu.length == 2 && types.isNumericLiteral(argu[0]) && types.isStringLiteral(argu[1])) {
// 用stringLiteral替换callExpression调用
let codeStr = _0x551a(argu[0].value, argu[1].value);
parentPath.replaceWith(types.stringLiteral(codeStr));
}
} else {
// 二次或多次赋值递归
decryptStr(parentPath)
}
}
}
}
traverse(ast, {
// 捕获VariableDeclarator,通过替换把具体字符串替换_0x46c769(0x1ef, '7awU')的方式解密
VariableDeclarator(path) {
if (types.isIdentifier(path.node.init, {name: '_0x551a'})) {
decryptStr(path);
}
}
})
还原的效果如下:
function changeUserState(_0x5d5847) {
var _0x46c769 = _0x551a,
_0x43508c = {
'FPUIO': function (_0x296d3e, _0x32d69f) {
return _0x296d3e(_0x32d69f);
},
'mzIBk': ".logout",
'RysRT': function (_0x3eb3cd, _0x3b1ace) {
return _0x3eb3cd(_0x3b1ace);
},
'YBOFo': 'layui-hide',
'cBrCl': ".username",
'FQNWn': ".user-admin"
};
_0x43508c["FPUIO"]($, _0x43508c["mzIBk"])['removeClass']("layui-hide"), _0x43508c["RysRT"]($, ".login")['addClass'](_0x43508c["YBOFo"]), _0x43508c["RysRT"]($, _0x43508c["cBrCl"])["text"](_0x5d5847["username"] || _0x5d5847['nickName']), _0x5d5847["admin"] && _0x43508c["FPUIO"]($, _0x43508c["FQNWn"])['removeClass'](_0x43508c["YBOFo"]);
}