AST解混淆-笔记
This_is_Y Lv6

目标

http://sgnec.sgcc.com.cn/

无法打开F12,经查找知道是使用了https://github.com/theajack/disable-devtool

本来是想用hook的方式绕过,结果发现js被混淆了,大致如下,

image-20250509103536302

image-20250509103610758

所以需要临时来学习一下AST解混淆

基础语法

解混淆方式

字符串还原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* @Author: This_is_Y
* @Date: 2025-05-09 15:53:16
* @LastEditTime: 2025-05-09 16:07:36
* @FilePath: /国网/AST/ast_unConfusion.js
* @Description: 字符串还原
*
*
*/
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default
const types = require("@babel/types")

let code = "console['\u006c\u006f\u0067']('\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u006f\u0072\u006c\u0064\u0021')"
const ast = parser.parse(code)

const visitor = {
StringLiteral(path) {
// 以下方法均可
// path.node.extra.raw = path.node.rawValue
// path.node.extra.raw = '"' + path.node.value + '"'
// delete path.node.extra
delete path.node.extra.raw
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

表达式还原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
* @Author: This_is_Y
* @Date: 2025-05-09 16:06:53
* @LastEditTime: 2025-05-09 16:07:03
* @FilePath: /国网/AST/ast_unConfusion2.js
* @Description: 表达式还原
*
*/
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default
const types = require("@babel/types")

const code = `
const a = !![]+!![]+!![];
const b = Math.floor(12.34 * 2.12)
const c = 10 >> 3 << 1
const d = String(21.3 + 14 * 1.32)
const e = parseInt("1.893" + "45.9088")
const f = parseFloat("23.2334" + "21.89112")
const g = 20 < 18 ? '未成年' : '成年'
`
const ast = parser.parse(code)

const visitor = {
"BinaryExpression|CallExpression|ConditionalExpression"(path) {
const {confident, value} = path.evaluate()
if (confident){
path.replaceInline(types.valueToNode(value))
}
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

删除未使用变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
* @Author: This_is_Y
* @Date: 2025-05-09 16:07:51
* @LastEditTime: 2025-05-09 16:08:01
* @FilePath: /国网/AST/ast_unConfusion3.js
* @Description: 删除未使用变量
*
*/
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default

const code = `
const a = 1;
const b = a * 2;
const c = 2;
const d = b + 1;
const e = 3;
console.log(d)
`
const ast = parser.parse(code)

const visitor = {
VariableDeclarator(path){
const binding = path.scope.getBinding(path.node.id.name);

// 如标识符被修改过,则不能进行删除动作。
if (!binding || binding.constantViolations.length > 0) {
return;
}

// 未被引用
if (!binding.referenced) {
path.remove();
}

// 被引用次数为0
// if (binding.references === 0) {
// path.remove();
// }

// 长度为0,变量没有被引用过
// if (binding.referencePaths.length === 0) {
// path.remove();
// }
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

删除冗余逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
* @Author: This_is_Y
* @Date: 2025-05-09 16:08:30
* @LastEditTime: 2025-05-09 16:08:37
* @FilePath: /国网/AST/ast_unConfusion4.js
* @Description: 删除冗余逻辑代码

*
*/
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default
const types = require('@babel/types');

const code = `
const example = function () {
let a;
if (false) {
a = 1;
} else {
if (1) {
a = 2;
}
else {
a = 3;
}
}
return a;
};
`
const ast = parser.parse(code)

const visitor = {
enter(path) {
if (types.isBooleanLiteral(path.node.test) || types.isNumericLiteral(path.node.test)) {
if (path.node.test.value) {
path.replaceInline(path.node.consequent.body);
} else {
if (path.node.alternate) {
path.replaceInline(path.node.alternate.body);
} else {
path.remove()
}
}
}
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

switch-case 反控制流平坦化

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/*
* @Author: This_is_Y
* @Date: 2025-05-09 16:09:03
* @LastEditTime: 2025-05-09 16:09:16
* @FilePath: /国网/AST/ast_unConfusion5.js
* @Description: switch-case 反控制流平坦化 1
*
*/
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default
const types = require("@babel/types")
const fs = require("fs");

const code = `const _0x34e16a = '3,4,0,5,1,2'['split'](',');
let _0x2eff02 = 0x0;
while (!![]) {
switch (_0x34e16a[_0x2eff02++]) {
case'0':
let _0x38cb15 = _0x4588f1 + _0x470e97;
continue;
case'1':
let _0x1e0e5e = _0x37b9f3[_0x50cee0(0x2e0, 0x2e8, 0x2e1, 0x2e4)];
continue;
case'2':
let _0x35d732 = [_0x388d4b(-0x134, -0x134, -0x139, -0x138)](_0x38cb15 >> _0x4588f1);
continue;
case'3':
let _0x4588f1 = 0x1;
continue;
case'4':
let _0x470e97 = 0x2;
continue;
case'5':
let _0x37b9f3 = 0x5 || _0x38cb15;
continue;
}
break;
}
`
const ast = parser.parse(code)

const visitor = {
WhileStatement(path) {
// switch 节点
let switchNode = path.node.body.body[0];
// switch 语句内的控制流数组名,本例中是 _0x34e16a
let arrayName = switchNode.discriminant.object.name;
// 获得所有 while 前面的兄弟节点,本例中获取到的是声明两个变量的节点,即 const _0x34e16a 和 let _0x2eff02
let prevSiblings = path.getAllPrevSiblings();
// 定义缓存控制流数组
let array = []
// forEach 方法遍历所有节点
prevSiblings.forEach(pervNode => {
let {id, init} = pervNode.node.declarations[0];
// 如果节点 id.name 与 switch 语句内的控制流数组名相同
if (arrayName === id.name) {
// 获取节点整个表达式的参数、分割方法、分隔符
let object = init.callee.object.value;
let property = init.callee.property.value;
let argument = init.arguments[0].value;
// 模拟执行 '3,4,0,5,1,2'['split'](',') 语句
array = object[property](argument)
// 也可以直接取参数进行分割,方法不通用,比如分隔符换成 | 就不行了
// array = init.callee.object.value.split(',');
}
// 前面的兄弟节点就可以删除了
pervNode.remove();
});

// 储存正确顺序的控制流语句
let replace = [];
// 遍历控制流数组,按正确顺序取 case 内容
array.forEach(index => {
let consequent = switchNode.cases[index].consequent;
// 如果最后一个节点是 continue 语句,则删除 ContinueStatement 节点
if (types.isContinueStatement(consequent[consequent.length - 1])) {
consequent.pop();
}
// concat 方法拼接多个数组,即正确顺序的 case 内容
replace = replace.concat(consequent);
}
);
// 替换整个 while 节点,两种方法都可以
path.replaceWithMultiple(replace);
// path.replaceInline(replace);
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default
const types = require("@babel/types")
const fs = require("fs");


const code = `const _0x34e16a = '3,4,0,5,1,2'['split'](',');
let _0x2eff02 = 0x0;
while (!![]) {
switch (_0x34e16a[_0x2eff02++]) {
case'0':
let _0x38cb15 = _0x4588f1 + _0x470e97;
continue;
case'1':
let _0x1e0e5e = _0x37b9f3[_0x50cee0(0x2e0, 0x2e8, 0x2e1, 0x2e4)];
continue;
case'2':
let _0x35d732 = [_0x388d4b(-0x134, -0x134, -0x139, -0x138)](_0x38cb15 >> _0x4588f1);
continue;
case'3':
let _0x4588f1 = 0x1;
continue;
case'4':
let _0x470e97 = 0x2;
continue;
case'5':
let _0x37b9f3 = 0x5 || _0x38cb15;
continue;
}
break;
}
`
const ast = parser.parse(code)

const visitor = {
WhileStatement(path) {
// switch 节点
let switchNode = path.node.body.body[0];
// switch 语句内的控制流数组名,本例中是 _0x34e16a
let arrayName = switchNode.discriminant.object.name;
// 获取控制流数组绑定的节点
let bindingArray = path.scope.getBinding(arrayName);
// 获取节点整个表达式的参数、分割方法、分隔符
let init = bindingArray.path.node.init;
let object = init.callee.object.value;
let property = init.callee.property.value;
let argument = init.arguments[0].value;
// 模拟执行 '3,4,0,5,1,2'['split'](',') 语句
let array = object[property](argument)
// 也可以直接取参数进行分割,方法不通用,比如分隔符换成 | 就不行了
// let array = init.callee.object.value.split(',');

// switch 语句内的控制流自增变量名,本例中是 _0x2eff02
let autoIncrementName = switchNode.discriminant.property.argument.name;
// 获取控制流自增变量名绑定的节点
let bindingAutoIncrement = path.scope.getBinding(autoIncrementName);
// 可选择的操作:删除控制流数组绑定的节点、自增变量名绑定的节点
bindingArray.path.remove();
bindingAutoIncrement.path.remove();

// 储存正确顺序的控制流语句
let replace = [];
// 遍历控制流数组,按正确顺序取 case 内容
array.forEach(index => {
let consequent = switchNode.cases[index].consequent;
// 如果最后一个节点是 continue 语句,则删除 ContinueStatement 节点
if (types.isContinueStatement(consequent[consequent.length - 1])) {
consequent.pop();
}
// concat 方法拼接多个数组,即正确顺序的 case 内容
replace = replace.concat(consequent);
}
);
// 替换整个 while 节点,两种方法都可以
path.replaceWithMultiple(replace);
// path.replaceInline(replace);
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

参考

Ast - 反混淆(基础篇)

逆向进阶,利用 AST 技术还原 JavaScript 混淆代码

ast解析器

非官方文档

disable-devtool

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
访客数 访问量