说明
第二种平坦流相比于第一种 难度都差不多,只是分发器结构 不同
,难点也是在于 没有 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 节点 } }) } })
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 节点 })
验证
版权声明:《 【AST 还原实战】某招聘 zp_stoken 平坦流 还原(二) 》为明妃原创文章,转载请注明出处!
最后编辑:2022-4-21 10:04:44