本文章是个人学习AST反混淆的笔记记录,发出来供大家参考指正,希望可以多多交流以提高个人技术!!!(注:本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!)
常见的常量混淆
常量混淆一直是js中最喜欢使用的一种方式之一,其形式包括计算,常量代替,ASCII码等等,有很多种类型
下面举例几种我最近遇到的比较多的情况:
- var a = 1 + 2;
- var b = ‘\x41’
- var c = ‘\u0041’
- var d = !![]
- var e = ‘hello, world’.length
- var f = String.fromCharCode(65)
- var g = ‘Win’;
g += ‘dow’
解决方案
情况1-6
这种常量混淆比较小儿科,就是把一个固定的数以其他人类不易读的形式展现出来。
虽然人类不易读,但是程序是可以直接计算的。那我们的思路就是,直接匹配到这段被混淆的代码,然后eval执行它,然后把执行后获得的值替换即可
脚本如下:
const x = {
VariableDeclarator(path) {
try {
path.node.init = types.valueToNode(eval(generator(path.node.init).code))
} catch (e) {
path.toString()
}
}
}
traverse(ast, x)
转换后代码如下:
情况七
这种是将一个变量拆开,然后用加法的方式组合形成最终的变量,这种就可能会麻烦不少
有很多种可能,比如这个加法的操作不是紧跟在声明变量操作的后面,而是隔了一段距离,比如
var a = 'win',
b,
c = 0;
a += 'dow'
c += 1
或者是与前面情况一至情况六这种融合一下,比如
var a = 1;
a += 'asd'.length
var b = 'A';
b += String.fromCharCode(65)
还有一种最最恶心的,就是不同作用域下的变量,变量名可能是一致的,比如
!function(){
var a = 1
var b = function(e){
var a = 2;
a += 3;
a += e;
return a
}(4)
a += 2
console.log(a + b)
}()
对于前两种可能,我们可以考虑创建一个字典,将匹配到的var变量存入字典中,然后再次遍历+=的操作,如果+=操作的变量存在于字典中,就可以直接将右边的值与字典中已经存入的值进行相加,最后再替换掉原本的var变量即可
脚本如下:
var var_dict = {}
const g = {
VariableDeclarator(path){
let {id, init} = path.node
if (init){
try{
var_dict[id.name] = eval(generator(init).code)
}catch (e){
console.log(path.toString())
}
}
}
}
traverse(ast, g)
const g2 = {
AssignmentExpression(path){
let {left, operator, right} = path.node
if (Object.keys(var_dict).includes(left.name) && operator === "+=" && types.isLiteral(right)){
try{
var_dict[left.name] += eval(generator(right).code)
path.remove()
}catch (e) {
console.log(path.toString())
}
}
}
}
traverse(ast, g2)
const g3 = {
VariableDeclarator(path){
let {id, init} = path.node
if (Object.keys(var_dict).includes(id.name) && init){
path.node.init = types.valueToNode(var_dict[id.name])
}
}
}
traverse(ast, g3)
转换后代码如下:
但是上面的做法遇到第三种情况就不好用了,因为作用域的不同,第三种情况下的两个a变量,其实指向的是不同的变量,如果仍旧使用上述的做法应对,就会导致出现以下情况
上图可以很明显的看出来最终的转换结果是不对的
所以面对作用域不同的情况,我个人的初步考虑是首先遍历整个代码,将所有的var变量放到一个dict中并记录其出现的数量,出现2次以上的就可以标记为可能作用域不同的变量(因为可能在同一个作用域重复声明,不过这种情况遇到的很少,重复声明会导致之前做的所有操作全部作废,一般很少有程序员会这么编写代码吧,所以这种情况以后遇到了再讨论吧)
然后,第二遍遍历就针对于这些会出现2次以上的变量进行操作,直接遍历每个函数(每个函数代表的作用域不一致),确定该函数下不会有新函数出现,此时作用域唯一,然后修改重复变量的变量名
等到所有的变量名都唯一时,就可以运行上面那套脚本了
var var_dict_2 = {}
const y = {
VariableDeclarator(path){
let {id, init} = path.node
if (!var_dict_2[id.name] && init){
var_dict_2[id.name] = 1
}else if (var_dict_2[id.name] && init){
var_dict_2[id.name] += 1
}
}
}
traverse(ast, y)
// console.log(var_dict_2)
const y2 = {
FunctionExpression(path){
let isLast = true
path.traverse({
FunctionExpression(path_2) {
if (path_2){
isLast = false
path_2.stop()
}
}
})
let change_id = []
if (isLast){
path.traverse({
Identifier(id_path){
let id = id_path.node.name
if (Object.keys(var_dict_2).includes(id) && var_dict_2[id] > 1){
id_path.node.name = id + var_dict_2[id]
if (!change_id.includes(id)){
change_id.push(id)
}
}
}
})
if (change_id.length !== 0){
for (let id of change_id){
var_dict_2[id] -= 1
}
}
}
}
}
traverse(ast, y2)
转换后代码如下: