const fs = require("fs");//文件读写
const parse = require("@babel/parser"); //解析为ast
const traverse = require('@babel/traverse').default;//遍历节点
const t = require('@babel/types');//类型
const generator = require('@babel/generator').default;//ast解析为代码
const code = `
const _0x34e16a = '3,4,0,5,1,2'['split'](',');
let _0x2eff02 = 0x0;
while (!![]) {
switch (_0x34e16a[_0x2eff02++]) {
case'0':
let _0x38cb15 = _0x4588f1 + _0x470e97;
continue;
case'1':
let _0x1e0e5e = _0x37b9f3[_0x50cee0(0x2e0, 0x2e8, 0x2e1, 0x2e4)];
continue;
case'2':
let _0x35d732 = [_0x388d4b(-0x134, -0x134, -0x139, -0x138)](_0x38cb15 >> _0x4588f1);
continue;
case'3':
let _0x4588f1 = 0x1;
continue;
case'4':
let _0x470e97 = 0x2;
continue;
case'5':
let _0x37b9f3 = 0x5 || _0x38cb15;
continue;
}
break;
}
`
// 默认为 script,当代码中含有 import 、export 等关键字时会报错,需要指定为 module
const ast = parse.parse(code, {sourceType: "module"})
// 思路A---正向思维(先实现目标)
// 1.获取控制流原始数组,将 '3,4,0,5,1,2'['split'](',') 之类的语句转化成 ['3','4','0','5','1','2'] 之类的数组,
// 得到该数组之后,也可以选择把 split 语句对应的节点删除掉,因为最终代码里这条语句就没用了;
//
// 2.遍历第一步得到的控制流数组,依次取出每个值所对应的 case 节点;
// 3.定义一个数组,储存每个 case 节点 consequent 数组里面的内容,并删除 continue 语句对应的节点;
// 4.遍历完成后,将第三步的数组替换掉整个 while 节点,也就是 WhileStatement。
// 总结:获取到 While 语句节点,然后使用 path.getAllPrevSiblings() 方法获取其前面的所有兄弟节点,
// 遍历每个兄弟节点,找到与 switch() 里面数组的变量名相同的节点,然后再取节点的值进行后续处理
function replace_switch_case_A(path) {
// switch节点
let switch_node = path.node.body.body[0]; // body[1]为break节点
// switch 语句内的控制流数组名称,本例是 _0x34e16a
let array_name = switch_node.discriminant.object.name;
// 获得所有 while 前面的兄弟节点,是一个类似列表的对象
// 本例中获取到的是声明两个变量的节点,即 const _0x34e16a 和 let _0x2eff02
let prevSiblings = path.getAllPrevSiblings();
// 定义一个缓存控制流数组
let array_new = []
// forEach 方法是遍历当前数组对象所有节点,当前是while前面的所有兄弟节点
prevSiblings.forEach(prev_node => {
let {id, init} = prev_node.node.declarations[0]
// 如果节点 id.name 与 switch 语句内的控制流数组名称相同
if (array_name === id.name) {
// 获取节点整个表达式的参数、分割方法、分隔符
let object = init.callee.object.value; // "3,4,0,5,1,2"
let property = init.callee.property.value; // "split"
let argument = init.arguments[0].value; // ','
// 模拟执行 '3,4,0,5,1,2'['split'](',') 语句
array_new = object[property](argument)
// prev_node.remove() // 删除当前这个赋值节点
}
prev_node.remove() // 删除前面的所有赋值语句,看情况删除
});
// 存储正确顺序的控制流语句
let replace = [];
// 边离控制流数组,按正确顺序取 case 内容,并存储到replace数组中
array_new.forEach(index => { // index在array_new是单个元素,表示到cases中就是下标index
let consequent = switch_node.cases[index].consequent;
// 如果consequent最后一个节点是 continue 语句,则删除 ContinueStatement 节点
if (t.isContinueStatement(consequent[consequent.length - 1])) {
consequent.pop();
}
// concat 方法拼接多个数组,即正确顺序的 case 内容
// [ '3', '4', '0', '5', '1', '2' ] 遍历数组时,拿的case就是先后正确顺序
replace = replace.concat(consequent)
});
// 替换整个 while 节点
path.replaceInline(replace)
}
// 思路B---逆向思维(进阶玩儿法)
// 总结:直接取 switch() 里面数组的变量名,然后使用 scope.getBinding() 方法获取到它绑定的节点,
// 然后再取这个节点的值进行后续处理。
function replace_switch_case_B(path) {
// switch 节点
let switch_node = path.node.body.body[0]
// switch 语句内的控制流数组名称,本例中是 _0x34e16a
let array_name = switch_node.discriminant.object.name;
// 获取控制流数组绑定的节点
let binding_array = path.scope.getBinding(array_name)
// 获取节点整个表达式的参数、分割方法、分隔符
let init = binding_array.path.node.init;
let object = init.callee.object.value;
let property = init.callee.property.value;
let argument = init.arguments[0].value;
// 模拟执行 '3,4,0,5,1,2'['split'](',') 语句
let array = object[property](argument)
// 也可以直接取参数进行分割,方法不通用,比如分隔符换成 | 就不行了
// let array = init.callee.object.value.split(',');
// switch 语句内的控制流自增变量名,本例中是 _0x2eff02
let auto_increment_name = switch_node.discriminant.property.argument.name;
// 获取控制流自增变量名绑定的节点
let binding_auto_increment = path.scope.getBinding(auto_increment_name);
// 可选择的操作:删除控制流数组绑定的节点、自增变量名绑定的节点
binding_array.path.remove()
binding_auto_increment.path.remove()
// 存储正确顺序的控制流语句
let replace = [];
// 遍历控制流数组,按正确顺序取 case 内容
array.forEach(index => {
let consequent = switch_node.cases[index].consequent;
// 如果最后一个节点是 continue 语句,则删除 ContinueStatement 节点
if (t.isContinueStatement(consequent[consequent.length - 1])) {
consequent.pop();
}
// concat 方法拼接多个数组,即正确顺序的 case 内容
replace = replace.concat(consequent);
});
// 替换整个while 节点
path.replaceInline(replace)
}
const visitor = {
WhileStatement: {
// enter: [replace_switch_case_A]
enter: [replace_switch_case_B]
}
}
traverse(ast, visitor)
const result = generator(ast, {jsescOption: {"minimal": true}}).code
console.log(result)
备注:学习K哥AST笔记