流程平坦化
它使用了
两种平坦流
,这里还原第一种
分析结构
每次
执行 case
时,才更新控制器的值
,这种平坦流
还是挺常见的,但 该 Js 做了一点处理
var sCS = 16; // 分发器初始值 while (!![]) { switch (sCS) { case 1: ... sCS += 22; break; case 2: ... sCS += 3; // 该 分支没有 break ,直接进入下一个 case case 3: ... sCS += 12; break; case 4: ... sCS += 2; break; case 5: ... sCS += 12; break; ... }
还原思路
1、通过
特征
判断,确定平坦流
代码2、获取 分发器初始值(
shufferVar
)3、遍历
while-case
映射出case 值 和 case 代码块
({'case值': 代码块})
4、再次遍历
while-case
,构造分支流程(自己说的词),表示 流程的前后分支关系
,当然没有 break
的需要特殊处理一下
,形如:[[2, 3],[1, 2],[3, 4]]5、通过
分支流程
的前后关系
,构造出分发器数组
6、遍历
分发器数组
(shufferArr
),根据数组 元素 顺序
获取映射的代码块
,同时判断 break
,并删除 break
和更新流程节点
,添加到父节点的 body
里面,添加前删除 分发器节点 和 while 节点
1、判断特征
平坦流都在函数节点里面,
声明节点
+whilw 节点
traverse(ast, { 'FunctionDeclaration|FunctionExpression'(path) { path.traverse({ // 只处理分发器是单个声明的 VariableDeclaration(p) { // 通过 特征 定位 定位分发器位置 var rbD = 0; let declarations = p.node.declarations; if (!(declarations.length === 1 && type.isNumericLiteral(declarations[0].init)) ) return; let whilePath = p.getSibling(p.key + 1) if (!type.isWhileStatement(whilePath)) return; // 声明语句后不是 while 节点 } }) } })
2、获取分发器
判断完成,获取分发器
变量名
和初始值
,变量名 用来更新 分发器
值时判断
// 获取 分发器 let shufferVar = {name: declarations[0].id.name, value: declarations[0].init.value} // 分发器起始值 console.info('分发器其起始值:', shufferVar)
3、映射 case
映射为了 方面获取 case 代码块
// 映射 case 和 代码块 let _case = {} whilePath.node.body.body[0].cases.map((v) => { let switchCase = v.consequent; // 获取 cases 里的代码块 数组 _case[v.test.value] = switchCase })
4、构造分支流程
构造分支流程的思路是,既然
进入
到该 case
,那么分发器
现在的值就是该 case 的值
,那么下一步的流程更新代码
,就在该case代码块里面
,也就能构造
出前后分支的关系数组
了
// 构造 分支 流程 let shufferFlow = [], // 分支流程 数组 flag = false; // 没有 break 标识 whilePath.node.body.body[0].cases.map((v) => { let n = v.consequent.slice(-1)[0] // 获取最后一个代码块 大多数是 break ,不是 break 的话,该 cases 执行完就 跳到下一个 case if (type.isBreakStatement(n)) { let caseNum = v.test.value let nextNode = v.consequent.slice(-2)[0] if (nextNode.expression.left.name === shufferVar.name) { // 变量名 与 分发器起始值一样 更新 let nextNum = caseNum; eval(`nextNum // 下一次 流程 ${nextNode.expression.operator} ${nextNode.expression.right.value}`) flag && (nextNum -= 1) && (flag = false) // 特殊处理就是 下一次节点 -1 shufferFlow.push([caseNum, nextNum]) } } else { if (!type.isReturnStatement(n)) { // 不是 return 执行完直接到 下一个 cases,但 下一个 case 需要单独处理 let caseNum = v.test.value let nextNode = caseNum + 1 flag = true // 标识 记录 shufferFlow.push([caseNum, nextNode]) } } })
5、还原 分发器
上一步构造出来的
前后分支关系
,顺序
是乱的
,没有还原
出代码的前后执行关系
,所以在还原分发器之前
,还需要 通过分发器初始值
,还原出代码的前后执行关系
。得到代码的前后执行流程后,还原出分发器时,这里取巧
的使用了函数,它原本是用来数组聚合的
// 构造 前后分支关系 let shufferFlow = [], // 前后分支关系 flag = false; // 没有 break 标识 whilePath.node.body.body[0].cases.map((v) => { let n = v.consequent.slice(-1)[0] // 获取最后一个代码块 大多数是 break ,不是 break 的话,该 cases 执行完就 跳到下一个 case if (type.isBreakStatement(n)) { let caseNum = v.test.value let nextNode = v.consequent.slice(-2)[0] if (nextNode.expression.left.name === shufferVar.name) { // 变量名 与 分发器起始值一样 更新 let nextNum = caseNum; eval(`nextNum // 下一次 流程 ${nextNode.expression.operator} ${nextNode.expression.right.value}`) flag && (nextNum -= 1) && (flag = false) // 特殊处理就是 下一次节点 -1 shufferFlow.push([caseNum, nextNum]) } } else { if (!type.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)
6、整体还原
获取完整
分发器
后,只需要把每个 case 里面
的代码
,按照分发器 提取
,添加到父节点的 body
里面就好,这个过程使用判断 删除一点代码
就好了
p.remove() // 删除分发器初始值 whilePath.remove() // 删除 while 节点 let parentPath = whilePath.parent shufferArr.map((v) => { if (type.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)
验证
可以看到已经还原了
版权声明:《 【AST 还原实战】某招聘 zp_stoken 平坦流 还原(一) 》为明妃原创文章,转载请注明出处!
最后编辑:2022-4-20 13:04:11