【AST 还原实战】某招聘 zp_stoken 平坦流 还原(二)

说明

第二种平坦流相比于第一种 难度都差不多,只是分发器结构 不同,难点也是在于 没有 break 的 case 流程分支构造

分析结构

可以通过观察,发现类似于一下的结构

var i = 0, arr = [...];
while (!![]) {
    switch (syZ[i++]) {
        case 1:
            ...
            var mP = arr.push // 获取 push
            break;
        case 2:
            ...
            var r8V = [...] // 部分 分发器 
            break;
        case 3:
            ...
            mP.apply(arr, r8V); // 更新分发器
            break;
        case 4:
            ... // 没有 break
        case 5:
            ...
            break

还原思路

1、通过 特征 判断,确定平坦流 代码

2、获取 分发器初始数组(shufferVar)

3、遍历 while-case映射出 case 值 和 case 代码块 ({‘case值’: 代码块}),并获取出相应的变量名

4、通过初始分发器数组遍历映射的case, 组建完整的 分发器

5、还原

1、判断特征

平坦流都在函数节点里 初始分发器数组 + whilw

traverse(ast, {
    'FunctionDeclaration|FunctionExpression'(path) {
        // 第一裂平坦流都在 函数里
        path.traverse({
            VariableDeclaration(p) {
                // 通过 特征 定位 定位分发器位置  var rbD = 0, BFH = []
                let declarations = p.node.declarations;
                if (declarations.length !== 2
                    || !type.isNumericLiteral(declarations[0].init, {value: 0})
                    || !type.isArrayExpression(declarations[1].init)
                ) return;
                let whilePath = p.getSibling(p.key + 1)
                if (!type.isWhileStatement(whilePath)) return; // 声明语句后不是 while 节点
            }
        })
    }
})

mark

2、获取分发器初始数组

先通过 定位 while 节点,获取前兄弟节点,判断是否是 初始分发器数组

// 获取 同级的 初始分发器数组
let shufferVar = declarations[1].id.name;
let shufferArr = {value: declarations[1].init.elements}; // 初始分发器数组
console.info('初始 分发器:', {name: shufferVar, lenght: shufferArr.value.length})

3、映射

映射为了 方面获取 case 代码块,映射过程,可以通过 apply 的特征,记录其他分发器数组变量名

// 映射 case 代码块,并 记录 更新分发器变量名
let _case = {}, shufferVarName = {arrName: {}};
whilePath.node.body.body[0].cases.map((v) => { // 映射 case 和 代码块
    let switchCase = v.consequent; // 获取 cases 里的代码块 数组
    if (type.isExpressionStatement(switchCase.slice(-2)[0])) {
        let n = switchCase.slice(-2)[0]
        if (n.expression.callee // 形式:mP.apply(syZ, r8V);
            && 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[v.test.value] = switchCase
})

4、组建完整分发器

每次取从 tmpShufferArr 删除头部值 并获得,来进行 过一遍流程分支,过的过程中动态添加 tmpShufferArr 值,来获得完整的分发器

let tmpShufferArr = shufferArr.value.concat() // 深复制
shufferArr = [] // 置空
while (tmpShufferArr.length !== 0) { // 这样写 每次更新临时分发器时,能增加循环
    let index = tmpShufferArr.shift().value // 每次从 临时分发器 头部获取 流程值
    let consequent = _case[index];
    shufferArr.push(index) // 从 临时分发器 获得的流程值 才是真实的

    let n = consequent.slice(-1)[0] // 获取最后一个代码块
    if (!type.isBreakStatement(n) && !type.isReturnStatement(n)) {
        // 为了处理:结尾 不是 break 和 return 的 case,下一次直接进入下一个 case 代码块
        tmpShufferArr.unshift({value: index + 1}) // 更新临时分发器

    }
    let b = consequent.slice(-2)[0] // 再获取 倒数第二个,判断是否是 分发器
    if (type.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 (type.isVariableDeclaration(b) && b.declarations[0] && b.declarations[0].init && b.declarations[0].init.object
        && b.declarations[0].init.object.name === shufferVar  // 形式:var xoL = KDW.p;
        && b.declarations[0].init.property.name === 'p') {
        _case[index].splice(-2, 1) // 删除 push 声明
    }
}
console.info('完整分发器:', JSON.stringify(shufferArr))

还原

遍历 分发器,获取 case 代码块,添加到 父节点的body里,并删除 break

p.remove() // 删除分发器
whilePath.remove() // 删除 while 节点
let parentPath = whilePath.parent
shufferArr.map((v) => {
    if (type.isBreakStatement(_case[v].slice(-1)[0])) {
        _case[v].pop() // 删除 break
    }
    parentPath.body.push(..._case[v]) // 添加到 path 节点
})

验证

mark

发表评论 / Comment

用心评论~