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

ast处理之前的js源码:链接:https://pan.baidu.com/s/1Duf7G8i2W3GQ1vt46NnxWg?pwd=x9rd
提取码:x9rd
文章是学习明妃ast的笔记,加了学习时的一些注释,对照学习看不懂的地方进行了debugger方式查看运行结果,内容不多,慢慢敲一遍。
平时主要是自学,不系统,getbinding和scope这些玩意儿拿捏不住,不过,两层for循环我还是能看得懂滴!

const parser = require("@babel/parser");
const generator = require("@babel/generator").default;
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const fs = require("fs");

const js_code = fs.readFileSync(
    "./boos_01_源码.js", {encoding: "utf-8"}
);

const ast = parser.parse(js_code, {sourceType: "module"});

function character_decoding_array(path) {
    let node = path.node;
    if (!t.isIdentifier(node.object.object)) {
        return
    }
    if (t.isStringLiteral(node.property)) {
        // node.property.extra.raw = node.property.extra.rawValue
        delete node.property.extra.raw
    }
    if (t.isStringLiteral(node.object.property)) {
        // node.object.property.extra.raw = node.object.property.extra.rawValue
        delete node.object.property.extra.raw
    }
}

function character_decoding_string(path) {
    let node = path.node;
    if (!t.isIdentifier(node.object)) {
        return
    }
    if (t.isStringLiteral(node.property)) {
        // node.property.extra.raw = node.property.extra.rawValue
        delete node.property.extra.raw
    }
}

function case_handle_one(path) {
    // 控制流平坦化  主要针对在case中通过+=运算符修改流程的控制流
    path.traverse({
        // 只处理分发器时单个声明的
        VariableDeclaration(p) {
            // 第一步:通过特征定位分发器 var J3I = 11;
            let declarations = p.node.declarations;
            if (!(declarations.length === 1 && t.isNumericLiteral(declarations[0].init))) {
                return
            }
            // // key: 有兄弟节点时,为当前节点,在容器中的索引,没有时,是 当前节点属性名
            let whilePath = p.getSibling(p.key + 1);
            if (!t.isWhileStatement(whilePath)) {
                // 声明语句后面不是while节点就退出
                return;
            }
            // 第二步:获取控制器的名称和值
            let shufferVar = {name: declarations[0].id.name, value: declarations[0].init.value}
            console.log("分发器初始值:", shufferVar);
            // 第三步:映射case,为了方便获取case代码块
            let _case = {};
            whilePath.node.body.body[0].cases.map((v) => {
                // v就是一个一个的 SwitchCase 节点,v.consequent时case里的代码块 数组结构
                let switchCase = v.consequent;
                // v.test.value 这个获取的是case后面的数值  case 1:  里面的1
                _case[v.test.value] = switchCase
            });
            // console.log(_case);
            // 第四步:构造分支流程  构造出前后分支的代码
            let shufferFlow = [], // 分支流程数组
                flag = false;   // 没有 break 标识
            whilePath.node.body.body[0].cases.map((v) => {
                // 获取最后一个代码块  大多数是 break ,不是 break 的话,该 cases 执行完就 跳到下一个 case
                let n = v.consequent.slice(-1)[0];
                // console.log(n);
                if (t.isBreakStatement(n)) {
                    let caseNum = v.test.value;
                    console.log("遍历分发器的值:", caseNum);
                    let nextNode = v.consequent.slice(-2)[0];
                    if (nextNode.expression.left.name === shufferVar.name) {
                        // 变量名 与 分发器起始值一样 更新
                        let nextNum = caseNum;   // 当前流程  eval计算下依次流程
                        eval(`nextNum
                        ${nextNode.expression.operator}
                        ${nextNode.expression.right.value}`);
                        flag && (nextNum -= 1) && (flag = false);
                        shufferFlow.push([caseNum, nextNum])
                    }
                } else {
                    if (!t.isReturnStatement(n)) {
                        // 不是 return 执行完直接到 下一个 cases,但 下一个 case 需要单独处理
                        let caseNum = v.test.value;
                        let nextNode = caseNum + 1;
                        flag = true;
                        shufferFlow.push([caseNum, nextNode])
                    }
                }
            });
            console.log("前后分支关系:", JSON.stringify(shufferFlow), shufferFlow.length)
            // 通过 节点起始  流程还原
            let shufferFlow_ = [];
            shufferFlow.map((v1, i1) => {
                if (shufferVar.value === v1[0]) {
                    shufferFlow_.push(v1);  // 起始值 和 下一个值
                    for (let i = 0; i < shufferFlow.length; i++) {
                        for (let j = 0; j < shufferFlow.length; j++) {
                            if (shufferFlow_[i][1] === shufferFlow[j][0]) {
                                shufferFlow_.push(shufferFlow[j]);
                                break
                            }
                        }
                    }
                }
            });
            console.log("正常分支流程:", JSON.stringify(shufferFlow_));
            // 构造分发器
            let shufferArr = [];
            shufferFlow_.reduce((k1, k2) => {
                k1 && shufferArr.push(...k1);
                k2[1] > 0 && shufferArr.push(k2[1])
            });
            console.log("分发器数组:", JSON.stringify(shufferArr), shufferArr.length)
            // 获取完整 分发器后,只需要把 每个 case 里面的代码,按照分发器 提取,添加到 父节点的 body 里面就好,
            // 这个过程使用判断 删除一点代码就好了
            p.remove();  // 删除分发器初始值
            whilePath.remove();  // 删除 while 节点
            let parentPath = whilePath.parent;  // while循环的父节点
            shufferArr.map((v) => {
                if (t.isBreakStatement(_case[v].slice(-1)[0])) {
                    _case[v].pop(); // 删除 break 节点
                    _case[v].pop() // 删除 更新流程 节点
                }
                parentPath.body.push(..._case[v])   添加到 父path 节点
            });
            console.log("新生父节点 body 数量:", parentPath.body.length);
        }
    })
}

function case_handle_two(path) {
    // 控制流平坦化  主要针对i++类型的流程
    path.traverse({
        VariableDeclaration(p) {
            // 第一步:通过特征 定位分发器位置 var Hr2 = 0
            //             , sAW = [6, 5, 19, 37, 38, 35, 20, 30, 17, 27, 12, 42, 15];
            // 在搞这个之前,要先知道Array.prototype 属性表示 Array 构造函数的原型,并允许您向所有Array对象添加新的属性和方法
            // 字符解码后Array["prototype"]["p"] = Array["prototype"]["push"]; 所有array对象都有一个p属性,p有是继承原先的push属性
            // var Rfa = ZhV.p;  ==> var Rfa = ZhV.push
            // Rfa.apply(ZhV, l5s)  表示在原有分发器的基础上push了一组array类型的分发器,明白了这些就可以继续往下看了
            let declarations = p.node.declarations;
            if (declarations.length != 2) {  // 判断长度
                return
            }
            if (!t.isNumericLiteral(declarations[0].init, {value: 0})) {
                // 条件是初始值为0的赋值是否是数值类型的判断
                return;
            }
            if (!t.isArrayExpression(declarations[1].init)) {
                // 第二个是数组类型的判断
                return;
            }
            // 获取while 循环 p的下一个兄弟节点
            let whilePath = p.getSibling(p.key + 1);
            if (!t.isWhileStatement(whilePath)) {
                return;  //
            }
            // 第二步:获取分发器初始数组
            // 先通过 定位 while 节点,获取前兄弟节点,判断是否是 初始分发器数组
            let shufferVAr = declarations[1].id.name; // 分发器名称
            let shufferArr = {value: declarations[1].init.elements};
            console.log("初始 分发器:", {name: shufferVAr, lenght: shufferArr.value.length});
            // 第三步:映射 case 代码块,并 记录 更新分发器变量名
            // 映射为了 方面获取 case 代码块,映射过程,可以通过 apply 的特征,记录其他分发器数组的变量名
            // return;
            let _case = {};
            let shufferVArName = {arrName: {}};
            whilePath.node.body.body[0].cases.map((v) => {  // v 是每一个 case
                let switchCase = v.consequent;  // case里面的代码块, 数组结构
                if (t.isExpressionStatement(switchCase.slice(-2)[0])) {
                    let n = switchCase.slice(-2)[0];  // h = M4l;

                    if (n.expression.callee // Rfa.apply(ZhV, j10);     //  push 新的分发器
                        && t.isMemberExpression(n.expression.callee)   // CBf()  过滤直接调用函数的形式
                        && n.expression.callee.property.name === "apply"
                        && n.expression.arguments[0].name === shufferVAr
                    ) {
                        // // 记录 另一部分 分发器变量名
                        shufferVArName.arrName[n.expression.arguments[1].name] = true;
                        switchCase.splice(-2, 1)  // 删除 apply 语句行
                    }
                }
                // _case[6] = 对应的语句块
                _case[v.test.value] = switchCase
            });
            // 第四步:组件完整分发器
            // 每次取从 tmpShufferArr 删除头部值并获得,来进行过一遍流程分支,
            // 过的过程中动态添加 tmpShufferArr 值,来获得完整的分发器
            let tmpShufferArr = shufferArr.value.concat();  // 深复制
            shufferArr = [];  // 分发器置空
            let a = 0;
            while (tmpShufferArr.length !== 0) { // 这样写 每次更新临时分发器时,能增加循环
                // shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值
                let index = tmpShufferArr.shift().value; // 每次从 临时分发器 头部获取 流程值

                let consequent = _case[index];  // 从_case中获取对应的语句块
                shufferArr.push(index);   // 从 临时分发器 获得的流程值 才是真实的

                let n = consequent.slice(-1)[0];  // 获取最后一个代码块
                // console.log(a++);
                if (!t.isBreakStatement(n) && !t.isReturnStatement(n)) {
                    // 为了处理:结尾 不是 break 和 return 的 case,下一次直接进入下一个 case 代码块
                    // unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
                    tmpShufferArr.unshift({value: index + 1})  // 更新临时分发器
                }
                // var l5s = 获取倒数第二个语句块,判断是否是分发器
                let b = consequent.slice(-2)[0];
                if (t.isVariableDeclaration(b) && b.declarations[0]
                    && shufferVArName.arrName[b.declarations[0].id.name]) {
                    // 更新临时分发器
                    tmpShufferArr.push(...b.declarations[0].init.elements);
                    _case[index].splice(-2, 1)
                }
                if (t.isVariableDeclaration(b) && b.declarations[0]
                    && b.declarations[0].init && b.declarations[0].init.object
                    && b.declarations[0].init.object.name === shufferVAr  // 形式 var Rfa = ZhV.p;
                    && b.declarations[0].init.property.name === "p") {
                    _case[index].splice(-2, 1)  // 删除 push 声明
                }
            }
            console.log("完整分发器:", JSON.stringify(shufferArr))
            // 第五步:还原 遍历分发器,获取case代码块,添加到父节点的body里,并删除break
            p.remove(); // 删除分发器
            whilePath.remove();  // 删除 while 节点
            let parentPath = whilePath.parent;
            shufferArr.map((v) => {
                if (t.isBreakStatement(_case[v].slice(-1)[0])) {
                    _case[v].pop() // 删除分发器
                }
                // _case[v] 是一个数组 ...解构之后一次性push到body
                parentPath.body.push(..._case[v])
            })

        }
    })
}

let visitor = {
    MemberExpression: {
        enter: [character_decoding_array, character_decoding_string]
    },
    "FunctionDeclaration|FunctionExpression": {
        enter: [
            case_handle_one,
            case_handle_two
        ]
    }
};
// 平坦流处理

// charCodeAt() 返回在指定的位置的字符的 Unicode 编码
// charAt() 	返回在指定位置的字符
traverse(ast, visitor);

let {code} = generator(ast, opts = {jsescOption: {"minimal": true}});
// 文件保存
fs.writeFile('./boos_01_new.js', code, (err) => {
});
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值