# AST 思考
本篇重在介绍关于 ast 还原代码的一些逻辑。
首先,我们知道,以目前常见的混淆来讲,大部分其实是通过不断地调用一个类似生成函数的地方,来调用。
我们在分析时,往往想看到其直接调用的位置,那么,我们就需要将所谓的生成入口写入内存中,然后直接调用进而达到直接将调用的结果展示出来。
通常,普通的混淆生成入口有如下四种:
- 数组型
- 函数形
- 对象型
- 组合型
# 数组型
所谓数组型,就是类似以数组的形式,通过数组下标访问。例如:
var arrlist = [1,2,3]; | |
var b = arrlist[1]; |
对于该种情况,我们抽象为如下四步:
- 确定要遍历的对象,这里是 VariableDeclarator。
- 接下来是判断 init 部分的类型,因为可能还有许多声明其他变量的部分,我们希望的是只遍历右边是数组类型的。
- 然后是将其写入内存,直接采用 eval 的方式 (最好将其写入全局变量中)。
- 最后是访问数组的作用域,找到其引用,然后直接替换节点即可。
# 函数形
所谓函数形,即声明的类型是函数,通过函数访问。
// 类型 1 | |
var add1 = function(a,b){ | |
return a + b; | |
}; | |
// 类型 2 | |
function add2(a,b){ | |
return a + b; | |
}; | |
var c = add1(1,2) + add2(3,4); |
该类情况与数组大致相同,只不过在遍历时候还需考虑第二种情况:
- 确定要遍历的对象,这里是 FunctionExpression,针对情况 1,可以直接通过 path.parent.id.name 获取函数昵称,针对情况 2,可以通过 path.node.id.name 获取函数昵称。
- 接下来是对函数体结构的判断,必须要有该函数的引用,其次,该函数可以通过传入的值,直接计算出结果。
- 然后是将其写入内存,直接采用 eval 的方式 (最好将其写入全局变量中)。
- 最后是访问函数的作用域,找到其引用,然后直接替换节点即可。
# 对象型
所谓对象型,就是声明了一个大对象,通过调用的方式访问。
var obj = { | |
'a':123, | |
'b':456, | |
}; | |
var c = obj['a']; |
这个的本质与数组可以说是一模一样,在此不做赘述。
# 组合型
所谓的组合型,就是通过对象、数组、函数的组合方式,使得其变得复杂化,进而加大一键脱混淆的难度,进而使得我们需要针对型的修改插件,才能使得混淆得以脱掉。
补充:其实上述也可以通过访问调用然后遍历其作用域,进而找到其声明部分替换,但是假如引用作用域中包含了对原对象的修改,那么在遍历时,可能会造成一定的难度。
目前来讲个人的解混淆思路倾向于写入式:
针对不同的情况,若是一个对象、函数、变量,经常被调用,那么我会将其写入内存,然后直接通过访问内存中的属性来修改节点。
