1.前言
学习一下语法分析,感觉很有用,于是决定使用JavaScript。因为javascript ATS感觉好用,最主要是复习一下javascript语法。使用babel依赖包,定下一个小任务:浅浅的反混淆一个东西。
2.正文
我首先选中的就是阿里云AWF的js文件,因为它混淆的足够猛,网上公开的反混淆工具似乎都没有办法。js链接(https://aeu.alicdn.com/waf/jquery_220224.min.js),随后就是(AST explorer)
参考资料(Appendix A. Syntax Tree Format — Esprima master documentation)
2.1进行观察
我们首先对代码进行格式化,首先印入眼帘的就是一个_0x512b数组,随后就是一个_0x1885变量函数,我们再仔细观察一下下面的方法,无一不是使用上面两个就是进行混淆的。
数组没有什么好说的,看一下_0x1885变量函数,可以进行简化。
var _0x1885 = function(_0x512bf4, _0x18850b) {
_0x512bf4 = _0x512bf4 - 0x0;
var _0x44b9d3 = _0x512b[_0x512bf4];
return _0x44b9d3;
};
//简化后
var _0x1885 = function (_0x512bf4, _0x18850b) {
return _0x512b[_0x512bf4];
};
简化后,第二个参数是没有用,那么我们就可以进行第一个反混淆优化了,那就是替换_0x1885函数我们再观察一下调用_0x1885函数的地方。
var _0x1fb9d1 = function(_0x53210e, _0x3aa6a6, _0x53e0c4, _0x217c5b, _0x297cf0) {
return _0x1885(_0x53e0c4 - 0x19d, _0x3aa6a6);
};
已经知道第二个参数没有用,那么我们只需要记下第一个参数,然后将其改造成如下就可以了。
var _0x1fb9d1 = function(_0x53210e, _0x3aa6a6, _0x53e0c4, _0x217c5b, _0x297cf0) {
return _0x512b[_0x53e0c4 - 0x19d];
};
首先这是一个CallExpression,其次Callee是_0x1885,我们需要将这个CallExpression替换成一个MemberExpression。我们写出如下代码
import types from '@babel/types'
import parser from '@babel/parser';
import traverse from '@babel/traverse';
import generator from '@babel/generator';
import fs from 'fs';
let sourceCode = fs.readFileSync('./jquery_220224.min.js', {encoding: 'utf-8'});
let ast = parser.parse(sourceCode);
/*去掉_0x1885函数的调用*/
traverse.default(ast, {
CallExpression(path) {
const node = path.node;
if (node.callee.type === 'Identifier' && node.callee.name === '_0x1885') {
path.replaceWith(
types.memberExpression(types.identifier("_0x512b"), node.arguments[0], true))
}
}
})
//一定会报错,需要添加err回调函数,有没有更加优雅的方式呢?
fs.writeFile('./new_jquery_220224.min.js', generator.default(ast).code, (err) => {
});
于是就会变成如下这样子
那么自然而言的就有了下一步,把这些函数也给它优化掉。老规矩,首先观察一下是怎么调用。
'LGpeF': _0x1fb9d1(0x24b, 0x20b, 0x19d, 0x21f, 0x156),
传入的是全部都是常量,但是并无啥用,只有一个参数能用到,每个函数都是不一样,所以我们需要记录下参数的index值。第二个就是传入的参数的offset量,下面还有不少的这个值都是不同的,为了方便我就不放在一起。其次就是我们可以看的出来,这些都是函数,并且都是ReturnStatement,然后的话就是上面MemberExpression,MemberExpression的argment.object.name是_0x512b。里面的话就是没有好说的了。
const paramIndexData = new Map();
const variableNumberData = new Map();
traverse.default(ast, {
ReturnStatement(path) {
const node = path.node;
if (types.isMemberExpression(node.argument)
&& types.isIdentifier(node.argument.object)
&& node.argument.object.name === "_0x512b") {
const property = node.argument.property;
//判断是否是二进制表达式,方便记录offset和param index
if (types.isBinaryExpression(property)) {
const paramName = property.left.name;
if (types.isBlockStatement(path.parent)) {
path.findParent(parent => {
if (!types.isFunctionExpression(parent.parent)) {
return
}
const params = parent.parent.params
for (let i = 0; i < params.length; i++) {
if (types.isIdentifier(params[i]) && params[i].name === paramName) {
parent.findParent(it => {
if (types.isVariableDeclarator(it)) {
paramIndexData.set(it.node.id.name, i);
variableNumberData.set(it.node.id.name, property.right.value)
}
})
}
}
})
}
}
}
}
})
我们记录下之后就进行替换。又是一个CallExpression,首先判断是否在hashmap中存在,存在的话才会去替换。为了方便,我直接把_0x512b拷贝进来了。
traverse.default(ast, {
CallExpression(path) {
const node = path.node;
//判断是否在hashMap中存在
if (types.isIdentifier(node.callee) && paramIndexData.has(node.callee.name)) {
let param = node.arguments;
const index = paramIndexData.get(node.callee.name);
console.log(node.callee.name)
console.log(node.loc.start)
console.log(node.loc.end)
//根据index取的传入的常量
if (types.isNumericLiteral(param[index])) {
//常量减去offset
const value = _0x512b[param[index].value - variableNumberData.get(node.callee.name)]
if (value !== undefined) {
path.replaceWith(types.stringLiteral(value))
}
}
}
}
})
其中获取到的值可能是undefined?不知道为啥,可能是我转成十进制?也有可能是因为这里根本不走?不知道,反正是学习,能用就用吧,看下结果。
上面以oBlnb为例,肯定就是又是一个BinaryExpression表达式,直接就进行替换就可以了,只不过我懒得写了,可以参考第一个。只不过我接下来写了一下低级版的控制流平坦化。
因为在替换出常量后出现了不少的这种if语句,于是我决定写一个试下水。
代码如下
/*控制流平坦化*/
traverse.default(ast, {
IfStatement(path) {
const node = path.node;
if (types.isBinaryExpression(node.test)) {
let expression = node.test;
if (types.isStringLiteral(expression.left)
&& types.isStringLiteral(expression.right)) {
/* 判断一下操作符,有没有什么好办法可以直接获取其运算结果呢?*/
if (expression.operator === "===") {
path.replaceWith(expression.left.value === expression.right.value ? node.consequent : node.alternate)
} else if (expression.operator === "!==") {
path.replaceWith(expression.left.value !== expression.right.value ? node.consequent : node.alternate)
}
}
}
}
})
还有好几种可以进行判断。比如,当然,方案都是大同小异的。
if (_0x3521ae["kNAeh"] !== _0x3521ae["lkRrr"]) {
****
}
if ("oBQxD" === _0x3521ae["hCctw"]) {
****
}
最后希望能够请教一下大家,有没有什么什么更加优雅的方式来实现,一起学习。今天的笔记就到这里吧。