《封号码罗》python爬虫之AST在js逆向中switch-case反控制流平坦化(二十二)

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笔记

你可能感兴趣的:(经验分享,javascript,开发语言,js逆向,AST解混淆,爬虫)