Ast - 反混淆(基础篇)
1. 什么是Ast-了解
- AST是抽象语法树的缩写。
- AST是一种用于表示程序代码结构的树状数据结构。
- 在编译器和解释器中,AST被用于解析和表示源代码的语法结构
- AST可以看作是源代码的一种抽象表示形式,它去除了源代码中的具体细节,只保留了语法结构和逻辑关系。
- AST中每个节点(Node) 表示源代码的一个语法元素,例如:变量声明,函数定义,循环语句,等,而节点之间的关系表示了语法结构的层次和关联关系
- 作用:
通过构建AST,编译器和解析器可以对源代码进行分析和处理
编译器可以在AST上进行语法检查、类型检查和优化等操作
解析器可以根据AST生成中间代码或目标代码
AST还能用于代码重构,代码生成等领域
以下是将正常代码通过在线AST在线网站进行转换为AST语法树
AST Explorer
2. 什么是反混淆-了解
什么是混淆?
- 混淆可以理解为是一种代码加密技术,主要用于隐藏代码的真实功能,以防止代码被逆向工程师分析和修改。通过混淆,让代码变得复杂和难以理解,使得逆向工程师在调试工程中消耗大量的时间或者放弃,从而达到一种保护
- 混淆总的来说就是一种代码保护方案,将原始代码转换为可读性较差或者没有可读性的代码
什么是反混淆?
- 反混淆就是将混淆后的代码,还原成具有可读性代码,方便逆向工程师进行调试。
3. 了解Ast结构
AST在线转换为抽象语法树的网站:AST Explorer
- 这个网站在反混淆中经常用到,因为我们要将被混淆的代码放到上面,然后转换为抽象语法树,然后对抽象语法树的结构进行分析。从而编写还原混淆的代码
在线解析工具使用:
-
打开网站后,将parser settings(解析器设置),设置成 @babel-parser,因为我们编写的反混淆代码使用的是 @babel 库,所以选择@babel/parser。在代码中@babel/parser的用处也就是将javascript代码转换为ast语法树结构
-
左侧的空白区域我们可以输入一些javascript代码(没混淆的或者混淆后的代码都可以),然后在右侧会展示转换好的ast抽象语法树
-
在左侧输入: console.log("学习AST架构")
以树的形式展示
以json的形式展示
** 节点解释 **
- type:表示当前节点类型
- start 表示当前节点的起始位置,图片中的start是0,起始位置是从c前面开始起算
- end 表示当前节点的末尾
- loc:表示当前节点所在的行列位置
loc中的start,表示节点所在起始的行列位置
loc中的end,表示节点所在末尾的行列位置
- body中的节点
-
ExpressionStatement表示为当前的代码是一个console.log()
-
ExpressionStatement中会有一个expression的子节点,expression节点的类型是 CallExpression
-
expression节点中又包含了callee和arguments两个节点,callee可以理解为函数名,而arguments这是它的参数
-
callee节点包含了object,property和computed节点
object表示对象,当前对象为 console
property表示属性,当前属性为 log
computed表示其方式
在后续的ast反混淆编写中,就是将原有的节点进行增删改查。然后得到一个新的代码
4. 什么是babel
- 在ast中,babel是一个广泛使用的JavaScript编译器工具。
- 它可以将高级的JavaScript代码转换为向后兼容的版本,以便在更旧的浏览器或环境中运行
- Babel使用AST(抽象语法树)来分析和修改代码。
- 通过在各个阶段插入插件,Babel可以执行各种转换操作,如转换ES6代码为ES5代码、添加Polyfills和补丁、优化代码等。
- Babel的主要目标是提供一个灵活且可扩展的工具,以适应不同的项目需求,并帮助开发者更好地利用新的JavaScript语言特性。
5. 安装babel
安装命令和解释:
npm install --save-dev @babel/core
以下已包含
@babel/types 判断节点类型,构建新的AST节点等
@babel/parser 将Javascript代码解析成AST语法树
@babel/traverse 遍历,修改AST语法树的各个节点
@babel/generator 将AST还原成Javascript代码
6. ast反混淆代码基本结构
以下是使用babel库来反混淆代码的模板
// fs模块 用于操作文件的读写
const fs = require("fs");
// @babel/parser 用于将JavaScript代码转换为ast树
const parser = require("@babel/parser");
// @babel/traverse 用于遍历各个节点的函数
const traverse = require("@babel/traverse").default;
// @babel/types 节点的类型判断及构造等操作
const types = require("@babel/types");
// @babel/generator 将处理完毕的AST转换成JavaScript源代码
const generator = require("@babel/generator").default;
// 混淆的js代码文件
const encode_file = "./encode.js"
// 反混淆的js代码文件
const decode_file = "./decode.js"
// 读取混淆的js文件
let jsCode = fs.readFileSync(encode_file, {encoding: "utf-8"});
// 将javascript代码转换为ast树(json结构)
let ast = parser.parse(jsCode)
// todo 编写ast插件
// 将处理后的ast转换为js代码(反混淆后的代码)
let {code} = generator(ast);
// 保存代码
fs.writeFile('decode.js', code, (err)=>{});
7. babel中的组件
7.1parser与generator 组件
- parser和generator这两个组件的作用是相反的。
- parser用于将js代码转换成ast,generator用于将ast转换成js代码
- parser将代码转换为ast:
let ast = parser.parse(参数一,参数二)
参数一:混淆的js代码
参数二:配置参数
sourceType: 默认是script,当解析的js代码中,含有 import,export 等关键字的时候需要指定sourceType为module,不然会报错 - generator将ast转换为代码:
let code = generator(参数一,参数二)
参数一:ast语法树
参数二:配置参数
retainLines:表示是否使用与源代码相同的行号,默认为false,也就是输出的是格式化后的代码
comments:表示是否保留注释,默认为true
compact:表示是否压缩代码,与其相同作用的选项还有minified,concise只不过压缩的程度不一样,minified压缩的最多,concise压缩的最少。
7.2 traverse 组件 与visitor
- traverse 用于遍历和转换抽象语法树(AST)的工具,转换语法树需要配置visitor使用
- visitor 是一个对象,里面可以定义一些方法,用来过滤节点
- visitor 示例代码:
const visitor={
expressionStatement(path){
console.log(path.toString())
}
}
traverse(ast,visitor)
1. 在代码中首先声明了visitor对象,对象的名字可以任意取。
2. 在visitor对象中定义一个名为ExpressionStatement的方法,这个方法的名字是需要遍历的是节点类型。
3. traverse会遍历所有节点,当节点类型为 ExpressionStatement时,调用visitor中对应的方法。
4. 如果想要处理其他节点类型,那么可以继续在visitor中继续定义对应的方法
5. visitor 对象中的方法接收一个参数,traverse在遍历的时候会把当前节点的Path对象传给它,传进去的Path对象不是节点node
6. 最后把visitor作为第二个参数传入traverse中,传给traverse的第一个参数是整个ast。
traverse(ast,visitor) 的意思是,从头开始遍历ast中的所有节点,过滤出ExpressionStatement节点,执行相应的方法。在ast中如果有多个ExpressStatement就会输出对应的次数
7.3 enter 组件 与exit
- 在遍历节点的过程中,实际上有两次机会来访问节点,enter表示进入节点时,exit表示退出节点时。可以在代码中编写遍历时进入要做的操作和退出时要做的操作。
const visitor={
expressionStatement:{
enter(path, state){
console.log('学习AST')
},
exit(path, state){
console.log('不学习AST');
}
}
}
traverse(ast,visitor)
8 示例
- 需要解码的代码, 放在 input.js里
// 前三个函数不需要动
function _0x4100() {
var _0x22aa53 = ['251985mUxIln', 'XFRJX', 'xXYDn', 'hexXor', 'CIXbb', 'aOBKg', '4480548xpuxYb', 'SqLaf', 'TPiWH', 'toString', '108cLfjrQ', '1357210Tvclfs', 'prototype', 'ssXnX', 'RBmGQ', 'XJjad', '8Pfqkyc', '344687iOhhUu', 'IRvRA', 'join', '4|1|0|3|2', '6934FoLeJQ', 'Jcwhc', '118APWSuk', '9prvDWJ', 'xFvlh', 'jPsXT', 'cBFWA', '264042HAeiXC', 'GDHNw', 'OPusJ', 'OWIJz', 'Gzpri', '3000176000856006061501533003690027800375', 'SSCjT', 'kmafh', 'hNmGu', 'PycNR', '1|0|5|3|4|2', 'EEeRc', '411631GrQOxo', 'split', 'LPvaq', 'length', '12NhGoWs', 'weDXa'];
_0x4100 = function() {
return _0x22aa53;
};
return _0x4100();
}
(function(_0x34f4dc, _0x30120e) {
var _0x332b09 = _0x48b1,
_0x20d748 = _0x34f4dc();
while (!![]) {
try {
var _0x2b177e = -parseInt(_0x332b09(0x1b5)) / 0x1 * (parseInt(_0x332b09(0x1b3)) / 0x2) + -parseInt(_0x332b09(0x1ba)) / 0x3 * (parseInt(_0x332b09(0x19c)) / 0x4) + -parseInt(_0x332b09(0x19e)) / 0x5 + parseInt(_0x332b09(0x1a4)) / 0x6 + parseInt(_0x332b09(0x1af)) / 0x7 * (-parseInt(_0x332b09(0x1ae)) / 0x8) + parseInt(_0x332b09(0x1b6)) / 0x9 * (parseInt(_0x332b09(0x1a9)) / 0xa) + -parseInt(_0x332b09(0x1c6)) / 0xb * (-parseInt(_0x332b09(0x1a8)) / 0xc);
if (_0x2b177e === _0x30120e) break;
else _0x20d748['push'](_0x20d748['shift']());
} catch (_0x24cf3c) {
_0x20d748['push'](_0x20d748['shift']());
}
}
}(_0x4100, 0x6d012));
// 解密函数
function _0x48b1(_0x4a0410, _0x1abe96) {
var _0x410075 = _0x4100();
return _0x48b1 = function(_0x48b11a, _0x2360e1) {
_0x48b11a = _0x48b11a - 0x19a;
var _0x501051 = _0x410075[_0x48b11a];
return _0x501051;
}, _0x48b1(_0x4a0410, _0x1abe96);
}
console.log(_0x4100());
function getAcwCookie(_0x5007fe) {
var _0x1f7d45 = _0x48b1,
_0x554031 = {
'RsEfR': _0x1f7d45(0x1b2),
'jPsXT': _0x1f7d45(0x1a7),
'wtMow': function(_0x5c473b, _0x4a31a5, _0x59fd0d) {
return _0x5c473b(_0x4a31a5, _0x59fd0d);
},
'XJjad': 'slice',
'RBmGQ': function(_0x52bf77, _0x452942) {
return _0x52bf77 == _0x452942;
},
'XFRJX': _0x1f7d45(0x19b),
'IRvRA': function(_0x38eb98, _0x293b3d) {
return _0x38eb98 + _0x293b3d;
},
'ssXnX': function(_0x3eb44c, _0x2e5bb4, _0x1b1880) {
return _0x3eb44c(_0x2e5bb4, _0x1b1880);
},
'xXYDn': function(_0x2c87e3, _0x359e79) {
return _0x2c87e3 + _0x359e79;
},
'weDXa': function(_0x106ab0, _0x22d881) {
return _0x106ab0 < _0x22d881;
},
'kmafh': function(_0xe77b2c, _0x3f3992) {
return _0xe77b2c === _0x3f3992;
},
'GDHNw': _0x1f7d45(0x1a5),
'hNmGu': _0x1f7d45(0x1c5),
'Tokhb': function(_0x189f19, _0x17c6cc) {
return _0x189f19 + _0x17c6cc;
},
'CIXbb': function(_0x10d136, _0x11d4ee) {
return _0x10d136 + _0x11d4ee;
},
'OPusJ': function(_0x5dddb8, _0x4ba730) {
return _0x5dddb8 ^ _0x4ba730;
},
'RKUqC': function(_0x3c0057, _0x406d8b) {
return _0x3c0057 == _0x406d8b;
},
'Jcwhc': function(_0x4a4b0d, _0x4775ab) {
return _0x4a4b0d !== _0x4775ab;
},
'TPiWH': _0x1f7d45(0x1a3),
'JTZKP': _0x1f7d45(0x1c4),
'PycNR': function(_0x30c0f3, _0x522705) {
return _0x30c0f3 < _0x522705;
},
'Gzpri': function(_0x2e0551, _0x427360) {
return _0x2e0551 < _0x427360;
},
'OWIJz': function(_0x1a596a, _0x7b9293) {
return _0x1a596a == _0x7b9293;
},
'mdvuG': function(_0x39487e, _0x152088) {
return _0x39487e + _0x152088;
},
'cBFWA': _0x1f7d45(0x1b1),
'xFvlh': _0x1f7d45(0x1bf),
'SSCjT': _0x1f7d45(0x1aa),
'LPvaq': _0x1f7d45(0x1a1)
},
_0x53ae70 = _0x554031[_0x1f7d45(0x1b7)];
String[_0x554031[_0x1f7d45(0x1c0)]][_0x554031[_0x1f7d45(0x19a)]] = function(_0x320388) {
var _0x7ec3d8 = _0x1f7d45,
_0x4efa30 = '';
for (var _0x1e3a86 = 0x0; _0x554031[_0x7ec3d8(0x19d)](_0x1e3a86, this[_0x554031[_0x7ec3d8(0x19f)]]) && _0x554031[_0x7ec3d8(0x19d)](_0x1e3a86, _0x320388[_0x554031[_0x7ec3d8(0x19f)]]); _0x1e3a86 += 0x2) {
if (_0x554031[_0x7ec3d8(0x1c1)](_0x554031[_0x7ec3d8(0x1bb)], _0x554031[_0x7ec3d8(0x1c2)])) {
var _0x1a2446 = _0x554031['RsEfR'][_0x7ec3d8(0x1c7)]('|'),
_0x16a3db = 0x0;
while (!![]) {
switch (_0x1a2446[_0x16a3db++]) {
case '0':
var _0x872b52 = (_0x5ccd2a ^ _0x2d6dd4)[_0x554031[_0x7ec3d8(0x1b8)]](0x10);
continue;
case '1':
var _0x2d6dd4 = _0x554031['wtMow'](_0x36f87f, _0x1a0839[_0x554031[_0x7ec3d8(0x1ad)]](_0x59517c, _0x48ede5 + 0x2), 0x10);
continue;
case '2':
_0x5cc12a += _0x872b52;
continue;
case '3':
_0x554031[_0x7ec3d8(0x1ac)](_0x872b52[_0x554031['XFRJX']], 0x1) && (_0x872b52 = _0x554031[_0x7ec3d8(0x1b0)]('0', _0x872b52));
continue;
case '4':
var _0x5ccd2a = _0x554031[_0x7ec3d8(0x1ab)](_0x324c08, this[_0x554031[_0x7ec3d8(0x1ad)]](_0x574e85, _0x14f8e3 + 0x2), 0x10);
continue;
}
break;
}
} else {
var _0x206263 = _0x554031['ssXnX'](parseInt, this[_0x554031[_0x7ec3d8(0x1ad)]](_0x1e3a86, _0x554031['Tokhb'](_0x1e3a86, 0x2)), 0x10),
_0x15beb7 = _0x554031['ssXnX'](parseInt, _0x320388[_0x554031[_0x7ec3d8(0x1ad)]](_0x1e3a86, _0x554031[_0x7ec3d8(0x1a2)](_0x1e3a86, 0x2)), 0x10),
_0x3d0178 = _0x554031[_0x7ec3d8(0x1bc)](_0x206263, _0x15beb7)[_0x554031[_0x7ec3d8(0x1b8)]](0x10);
_0x554031['RKUqC'](_0x3d0178[_0x554031[_0x7ec3d8(0x19f)]], 0x1) && (_0x554031[_0x7ec3d8(0x1b4)](_0x554031[_0x7ec3d8(0x1a6)], _0x554031['TPiWH']) ? _0x15edd9 = _0x554031[_0x7ec3d8(0x1a0)]('0', _0x50de18) : _0x3d0178 = _0x554031['xXYDn']('0', _0x3d0178)), _0x4efa30 += _0x3d0178;
}
}
return _0x4efa30;
}, String[_0x554031[_0x1f7d45(0x1c0)]]['unsbox'] = function() {
var _0x5a71ba = _0x1f7d45,
_0x46c646 = _0x554031['JTZKP'][_0x5a71ba(0x1c7)]('|'),
_0x23c717 = 0x0;
while (!![]) {
switch (_0x46c646[_0x23c717++]) {
case '0':
var _0x1127be = [];
continue;
case '1':
var _0x27ba70 = [0xf, 0x23, 0x1d, 0x18, 0x21, 0x10, 0x1, 0x26, 0xa, 0x9, 0x13, 0x1f, 0x28, 0x1b, 0x16, 0x17, 0x19, 0xd, 0x6, 0xb, 0x27, 0x12, 0x14, 0x8, 0xe, 0x15, 0x20, 0x1a, 0x2, 0x1e, 0x7, 0x4, 0x11, 0x5, 0x3, 0x1c, 0x22, 0x25, 0xc, 0x24];
continue;
case '2':
return _0x2173ee;
case '3':
for (var _0x48380c = 0x0; _0x554031[_0x5a71ba(0x1c3)](_0x48380c, this[_0x554031[_0x5a71ba(0x19f)]]); _0x48380c++) {
var _0x500f43 = this[_0x48380c];
for (var _0x240bf4 = 0x0; _0x554031[_0x5a71ba(0x1be)](_0x240bf4, _0x27ba70[_0x554031['XFRJX']]); _0x240bf4++) {
_0x554031[_0x5a71ba(0x1bd)](_0x27ba70[_0x240bf4], _0x554031['mdvuG'](_0x48380c, 0x1)) && (_0x1127be[_0x240bf4] = _0x500f43);
}
}
continue;
case '4':
_0x2173ee = _0x1127be[_0x554031[_0x5a71ba(0x1b9)]]('');
continue;
case '5':
var _0x2173ee = '';
continue;
}
break;
}
};
var _0x45aba9 = _0x5007fe['unsbox']();
return arg2 = _0x45aba9[_0x554031[_0x1f7d45(0x19a)]](_0x53ae70), arg2;
}
- 前三个函数不需要动--放在主入口代码中(ast_demo.js)
const parse = require('@babel/parser').parse
const generator = require('@babel/generator').default;
const traverse = require('@babel/traverse').default;
const types = require('@babel/types')
const fs = require('fs');
const { exit } = require('process');
// 自行将后面讲的三个特征的代码放到这
function _0x4100() {
var _0x22aa53 = ['251985mUxIln', 'XFRJX', 'xXYDn', 'hexXor', 'CIXbb', 'aOBKg', '4480548xpuxYb', 'SqLaf', 'TPiWH', 'toString', '108cLfjrQ', '1357210Tvclfs', 'prototype', 'ssXnX', 'RBmGQ', 'XJjad', '8Pfqkyc', '344687iOhhUu', 'IRvRA', 'join', '4|1|0|3|2', '6934FoLeJQ', 'Jcwhc', '118APWSuk', '9prvDWJ', 'xFvlh', 'jPsXT', 'cBFWA', '264042HAeiXC', 'GDHNw', 'OPusJ', 'OWIJz', 'Gzpri', '3000176000856006061501533003690027800375', 'SSCjT', 'kmafh', 'hNmGu', 'PycNR', '1|0|5|3|4|2', 'EEeRc', '411631GrQOxo', 'split', 'LPvaq', 'length', '12NhGoWs', 'weDXa'];
_0x4100 = function() {
return _0x22aa53;
};
return _0x4100();
}
(function(_0x34f4dc, _0x30120e) {
var _0x332b09 = _0x48b1,
_0x20d748 = _0x34f4dc();
while (!![]) {
try {
var _0x2b177e = -parseInt(_0x332b09(0x1b5)) / 0x1 * (parseInt(_0x332b09(0x1b3)) / 0x2) + -parseInt(_0x332b09(0x1ba)) / 0x3 * (parseInt(_0x332b09(0x19c)) / 0x4) + -parseInt(_0x332b09(0x19e)) / 0x5 + parseInt(_0x332b09(0x1a4)) / 0x6 + parseInt(_0x332b09(0x1af)) / 0x7 * (-parseInt(_0x332b09(0x1ae)) / 0x8) + parseInt(_0x332b09(0x1b6)) / 0x9 * (parseInt(_0x332b09(0x1a9)) / 0xa) + -parseInt(_0x332b09(0x1c6)) / 0xb * (-parseInt(_0x332b09(0x1a8)) / 0xc);
if (_0x2b177e === _0x30120e) break;
else _0x20d748['push'](_0x20d748['shift']());
} catch (_0x24cf3c) {
_0x20d748['push'](_0x20d748['shift']());
}
}
}(_0x4100, 0x6d012));
// 解密函数
function _0x48b1(_0x4a0410, _0x1abe96) {
var _0x410075 = _0x4100();
return _0x48b1 = function(_0x48b11a, _0x2360e1) {
_0x48b11a = _0x48b11a - 0x19a;
var _0x501051 = _0x410075[_0x48b11a];
return _0x501051;
}, _0x48b1(_0x4a0410, _0x1abe96);
}
// console.log(_0x4100());
- 要解码的第四个函数,放在AST Explorer
traverse(ast, {
CallExpression: {
exit:function(path){
if(['_0x1f7d45',"_0x7ec3d8","_0x5a71ba"].includes(path.node.callee.name)){
//console.log(path.toString());
// 传参_0x48b1解密函数 执行结果 替换这行代码
path.replaceInline(types.stringLiteral(_0x48b1(path.node.arguments[0].value)))
// console.log(path.toString());
}
}
}
})