【AST 还原实战】某招聘 zp_stoken 平坦流 还原(二) [ 编程杂谈 ]
大数据男孩 文章 正文
明妃
{{nature("2022-08-14 17:23:21")}}更新说明
第二种平坦流相比于第一种 难度都差不多,只是分发器结构 不同
,难点也是在于 没有 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 节点
})
验证
[]()
{{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}}