【AST 还原实战】某招聘 zp_stoken 平坦流 还原(一) [ 编程杂谈 ]
大数据男孩 文章 正文
明妃
{{nature("2022-08-14 17:23:20")}}更新流程平坦化
它使用了
两种平坦流
,这里还原第一种
分析结构
每次
执行 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)
验证
可以看到已经还原了
[]()
[)]()
{{nature('2020-01-02 16:47:07')}} {{format('12641')}}人已阅读
{{nature('2019-12-11 20:43:10')}} {{format('9527')}}人已阅读
{{nature('2019-12-26 17:20:52')}} {{format('7573')}}人已阅读
{{nature('2019-12-26 16:03:55')}} {{format('5017')}}人已阅读
目录
标签云
一言
评论 0
{{userInfo.data?.nickname}}
{{userInfo.data?.email}}