我的理解:AST相当于把js代码所有语法解析为抽象的树.用处大概就是逆向的时候把混淆的代码还原逻辑,方便看逻辑.,以下所有笔记都是抄自悦来客栈的老板的星球 jsvmp相关请看 JSVMP js加密
AST在线解析
用这个网站输入JS源码就可以看到AST解析出来的语法树了
来源:悦来客栈的老板的星球
//babel库及文件模块导入
const fs = require('fs');
//babel库相关,解析,转换,构建,生产
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const types = require("@babel/types");
const generator = require("@babel/generator").default;
//读取文件
let encode_file = "./encode.js",decode_file = "./decode_result.js";
if (process.argv.length > 2)
{
encode_file = process.argv[2];
}
if (process.argv.length > 3)
{
decode_file = process.argv[3];
}
let jscode = fs.readFileSync(encode_file, {encoding: "utf-8"});
//转换为ast树
let ast = parser.parse(jscode);
const visitor =
{
//TODO write your code here!
}
//some function code
//调用插件,处理源代码
traverse(ast,visitor);
//生成新的js code,并保存到文件中输出
let {code} = generator(ast);
fs.writeFile('decode.js', code, (err)=>{});
这个抄的好啊https://blog.csdn.net/weixin_33826609/article/details/93164633
复制的是官方文档,但是上面的搜索比较方便
这个介绍的挺好https://www.cnblogs.com/YikaJ/p/10073540.html
我的笔记:
解析
解析步骤接收代码并输出 AST。 这个步骤分为两个阶段: 词法分析 -> 语法分析。
然而我们看到的一般都是语法分析后的
转换(核心)
简单来说就是对节点的增删改查
生成
把AST还原成源码,深度优先遍历AST,然后构建转换后的代码字符串
建议每个实践都对比一下AST树,大概就可以理解了
从\x6c\x6f\x67 -> log
混淆代码
var _0x1201 = ['\x6c\x6f\x67', '\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21'];
(function(_0x2b91f5, _0x120157) {
var _0x2e36e7 = function(_0x40e9dc) {
while (--_0x40e9dc) {
_0x2b91f5['\x70\x75\x73\x68'](_0x2b91f5['\x73\x68\x69\x66\x74']());
}
};
_0x2e36e7(++_0x120157);
}(_0x1201, 0xa3));
var _0x2e36 = function(_0x2b91f5, _0x120157) {
_0x2b91f5 = _0x2b91f5 - 0x0;
var _0x2e36e7 = _0x1201[_0x2b91f5];
return _0x2e36e7;
};
function hi() {
var _0x379bb0 = _0x2e36;
console[_0x379bb0('\x30\x78\x31')](_0x379bb0('\x30\x78\x30'));
}
hi();
解混淆代码
输入到第二点的框架中运行
const transform_literal = {
NumericLiteral({node}) {
if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
node.extra = undefined;
}
},
StringLiteral({node})
{
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra = undefined;
}
},
}
解析:
适配是乱码的部分,删除掉extra节点(在AST中不是必须的),然而其value节点是可阅读的字符串,所以会显示解析完得字符串,具体去AST解析那儿看看就知道.
从 a.length -> a[‘length’]
源代码:
var a = b.length;
解混淆代码
const member_property_literals = {
MemberExpression:
{
exit({node})
{
const prop = node.property;
if (!node.computed && types.isIdentifier(prop))
{
node.property = types.StringLiteral(prop.name);
node.computed = true;
}
}
},
ObjectProperty:
{
exit({node})
{
const key = node.key;
if (!node.computed && types.isIdentifier(key))
{
node.key = types.StringLiteral(key.name);
}
}
},
}
解析:
通过把Identifier构建为StringLiteral实现
!(function()
{
a = b;
})();
->
a = b;
反混淆代码
const simplify_auto_exec = {
UnaryExpression(path) {
let { operator, argument } = path.node;
if (operator != "!" || !types.isCallExpression(argument)) return;//找到"!"和Call类型的
let { arguments, callee } = argument;
if (arguments.length != 0 || !types.isFunctionExpression(callee)) return;
let { id, params, body } = callee;
if (id != null || params.length != 0 || !types.isBlockStatement(body)) return;
path.replaceWithMultiple(body.body);
},
}
解析:
其实就是将函数体里面的内容替换整个表达式
1.因为是替换表达式,因此这里遍历UnaryExpression节点,这个可以在对照网站看的很清楚。
2.特征判断,UnaryExpression表达式的operator节点为符号"!";而argument节点是CallExpression类型
3.再次对argument节点进行特征判断,具体见代码。
4.满足条件后进行替换。
var a = parseInt(“12345”,16),b = Number(“123”),c = String(true),d = unescape(“hello%2CAST%21”);
eval(“a = 1”);
->
var a = 74565,b = 123,c = “true”,d = “hello,AST!”;
eval(“a = 1”);
反混淆代码
const evaluate_global_func =
{
"CallExpression"(path)
{
let {callee,arguments} = path.node;
if (!types.isIdentifier(callee) || callee.name == "eval") return; //跳过eval,限定替换Identifier
if (!arguments.every(arg=>types.isLiteral(arg))) return;//需要不是Literal
let func = global[callee.name];
if (typeof func !== "function") return;//获取是否自带的对象,不是的话跳过
let args = [];
arguments.forEach((ele,index) =>{args[index] = ele.value;});//有些值可能是数组
let value = func.apply(null,args);//主动调用.例如->Number('123')
if (typeof value == "function") return;//调用完如果是函数就过滤
path.replaceInline(types.valueToNode(value));
},
}
解析:
开始变得基操了…但是需要对节点的理解才能不误伤不想修改的节点…
Unicode转中文,代码压缩,删除注释,删除空行
删除空行和空语句(比较简单,放在一起)
删除辣鸡代码
删除屎代码
直接看文章吧,这部分属于善后工作
这个时候需要插入蔡老板的node,path入门教学
自己写的代码,应该可以适配更多类型
源码
let a = 111;
var all = function () {
var Xor = function (p, q) {
return q + p;
};
let b = 222;
let a = Xor (b,a);
};
var all2 = function () {
let b = 444;
};
反混淆源码
const call2Express = {
CallExpression(path) {
let { arguments, callee } = path.node;
let arg = [];
arguments.every(element => {//获取这个Call的所有变量
if (types.isLiteral(element)) {//常规类型
arg.push(element.value);
} else if (types.isIdentifier(element)) {//变量的话找一下有没有
let elementName = element.name;
path.findParent((pathTemp) => {
if (pathTemp.isVariableDeclaration()) {
// console.log(pathTemp.container);
let argTemp = '';
pathTemp.container.every(node1 => {//有的话存到数组中
node1.declarations.every(node2 => {
if (node2.id.name === elementName && types.isLiteral(node2.init)) {
argTemp = node2.init.value;
console.log(argTemp);
return false;
}
return true;
})
return true;
});
if (argTemp !== '') {
arg.push(argTemp);
return true;
}
}
});
} else {
return false;
};
return true;
})
if (arg.length === arguments.length) {
let functionName = callee.name;
path.findParent((pathTemp) => {//找到对应的方法
if (pathTemp.isVariableDeclaration()) {
pathTemp.container.forEach(node => {
if (node.declarations.length != 1) return;
if (node.declarations[0].id.name === functionName) {
let { code } = generator(node);
let argStr = '';
for (let index = 0; index < arg.length; index++) {
const element = arg[index];
argStr += (',' + element);
}
argStr = argStr.substring(1);
let res = eval(`${code} ${functionName}(${argStr});`);//运行结果
path.replaceInline(types.valueToNode(res));//替换路径
// console.log(res);
return;
}
});
return;
}
});
}
},
}
解析在注释,目前可以一直,实际还可以深入,例如把一些不是调用的常量相加."a=b+c,b=1,c=2"这种也加起来
全在蔡老板的星球和他公众号学的
//获取赋值语句左边的 a
let expression = body[1].node.expression;
let name = expression.left.name;
//根据Function函数进行构造
let code = path.toString() + "\nreturn " + name;
//构造并运行,即可得到for循环的结果
let func = new Function("",code);
let value = func();
构建节点的时候查询 -> https://babeljs.io/docs/en/babel-types
变量定义:var a = 123; 这里的 a 就拥有 binding。
函数定义 function test(a,b,c) {}; a,b,c 有binding