Ast实战:反混淆解析低级难度
ob混淆网站
https://obfuscator.io/
一、混淆demo生成
二、混淆demo说明
三、混淆demo整理
demo.js
//TODO 这里对混淆demo进行了手动调整顺序操作,为了便于阅读,进行了格式化处理操作
function _0x49fa() {
var _0x516a2d = ['{}.constructor(\x22return\x20this\x22)(\x20)', 'apply', 'log', 'constructor', '2SqklKs', 'bind', '16884qOndvr', '1165464jGkqOf', 'Hello\x20World!', 'trace', '(((.+)+)+)+$', 'table', '1335024wPJxRa', '1222485Bckiuw', '__proto__', '199500lvEvzx', 'error', 'info', '7220SPjIao', 'console', 'length', '5607vTyVEp', '181888FaBBpo', 'toString', 'return\x20(function()\x20', 'prototype', 'search', '2056AnReBT', 'exception'];
_0x49fa = function () {
return _0x516a2d;
};
return _0x49fa();
}
(function (_0x125bfe, _0x500fb6) {
var _0x1329da = _0x4518, _0x3a893b = _0x125bfe();
while (!![]) {
try {
var _0x5a9bd0 = parseInt(_0x1329da(0xf0)) / 0x1 * (-parseInt(_0x1329da(0xfb)) / 0x2) + parseInt(_0x1329da(0xe9)) / 0x3 + -parseInt(_0x1329da(0xe6)) / 0x4 + -parseInt(_0x1329da(0xe7)) / 0x5 + -parseInt(_0x1329da(0xfe)) / 0x6 + -parseInt(_0x1329da(0xef)) / 0x7 * (parseInt(_0x1329da(0xf5)) / 0x8) + -parseInt(_0x1329da(0xfd)) / 0x9 * (-parseInt(_0x1329da(0xec)) / 0xa);
if (_0x5a9bd0 === _0x500fb6) break; else _0x3a893b['push'](_0x3a893b['shift']());
} catch (_0x462d56) {
_0x3a893b['push'](_0x3a893b['shift']());
}
}
}(_0x49fa, 0x3fa7a));
function _0x4518(_0x4980dd, _0x49bf25) {
var _0x41ef62 = _0x49fa();
return _0x4518 = function (_0x2e0a85, _0xe588d) {
_0x2e0a85 = _0x2e0a85 - 0xe6;
var _0x46a5ea = _0x41ef62[_0x2e0a85];
return _0x46a5ea;
}, _0x4518(_0x4980dd, _0x49bf25);
}
function hi() {
var _0x40b3ee = _0x4518, _0x42eed9 = function () {
var _0x14d6ea = !![];
return function (_0x4d0fda, _0xb74864) {
var _0x5cf757 = _0x14d6ea ? function () {
var _0x3e3424 = _0x4518;
if (_0xb74864) {
var _0x17acd4 = _0xb74864[_0x3e3424(0xf8)](_0x4d0fda, arguments);
return _0xb74864 = null, _0x17acd4;
}
} : function () {
};
return _0x14d6ea = ![], _0x5cf757;
};
}(), _0x29f72a = _0x42eed9(this, function () {
var _0x332c5c = _0x4518;
return _0x29f72a[_0x332c5c(0xf1)]()[_0x332c5c(0xf4)](_0x332c5c(0x101))[_0x332c5c(0xf1)]()[_0x332c5c(0xfa)](_0x29f72a)[_0x332c5c(0xf4)](_0x332c5c(0x101));
});
_0x29f72a();
var _0x4a8060 = function () {
var _0x2d3452 = !![];
return function (_0x576843, _0x17ac8b) {
var _0x7723ab = _0x2d3452 ? function () {
if (_0x17ac8b) {
var _0x12504d = _0x17ac8b['apply'](_0x576843, arguments);
return _0x17ac8b = null, _0x12504d;
}
} : function () {
};
return _0x2d3452 = ![], _0x7723ab;
};
}(), _0x19a1a3 = _0x4a8060(this, function () {
var _0x1118aa = _0x4518, _0x1812be;
try {
var _0x5b1f89 = Function(_0x1118aa(0xf2) + _0x1118aa(0xf7) + ');');
_0x1812be = _0x5b1f89();
} catch (_0x5bb1f8) {
_0x1812be = window;
}
var _0x591858 = _0x1812be[_0x1118aa(0xed)] = _0x1812be[_0x1118aa(0xed)] || {},
_0x5e6a8b = [_0x1118aa(0xf9), 'warn', _0x1118aa(0xeb), _0x1118aa(0xea), _0x1118aa(0xf6), _0x1118aa(0x102), _0x1118aa(0x100)];
for (var _0x1170fb = 0x0; _0x1170fb < _0x5e6a8b[_0x1118aa(0xee)]; _0x1170fb++) {
var _0x504458 = _0x4a8060[_0x1118aa(0xfa)][_0x1118aa(0xf3)][_0x1118aa(0xfc)](_0x4a8060),
_0x2f50a0 = _0x5e6a8b[_0x1170fb], _0x116675 = _0x591858[_0x2f50a0] || _0x504458;
_0x504458[_0x1118aa(0xe8)] = _0x4a8060[_0x1118aa(0xfc)](_0x4a8060), _0x504458['toString'] = _0x116675[_0x1118aa(0xf1)][_0x1118aa(0xfc)](_0x116675), _0x591858[_0x2f50a0] = _0x504458;
}
});
_0x19a1a3(), console[_0x40b3ee(0xf9)](_0x40b3ee(0xff));
}
hi();
四、存在难点
难点解决:
AST反混淆进阶-标识符重复赋值
https://jia666666.blog.csdn.net/article/details/120352005
难点解决:
AST反混淆进阶-大数组解密
https://jia666666.blog.csdn.net/article/details/120304802
注意:由于这里demo不同于大数组解密样例,需要进行部分改写,才能使用大数组解密模块
这一节是:
基于AST反混淆实战-默认难度解析源码
https://jia666666.blog.csdn.net/article/details/120368597
利用AST反混淆实战-默认难度解析源码解析后,如图,可以明显看到console禁用输入功能
难点解决
AST反混淆进阶-禁用console输出功能删减
https://jia666666.blog.csdn.net/article/details/120354257
五、解混淆
在AST反混淆实战-默认难度解析源码中增加禁用console输出功能删减即可
dec_main.js
const fs = require("fs");//文件读写
const parse = require("@babel/parser"); //解析为ast
const traverse = require('@babel/traverse').default;//遍历节点
const t = require('@babel/types');//类型
const generator = require('@babel/generator').default;//ast解析为代码
//读取js文件
const jscode = fs.readFileSync(
'./read.js', {
encoding: 'utf-8'
}
);
let ast = parse.parse(jscode);//js转ast
try {
//TODO 1 标识符-重复赋值
traverse(ast, {VariableDeclarator: {exit: [ReIdent]},});
console.log('第一步:标识符-重复赋值已完成')
//TODO 2 大数组解密
ast = decrypt_arr(ast)//大数组还原
console.log('第二步:大数组解密已完成')
//TODO 3 禁用console删减
ast = parse.parse(generator(ast).code)//刷新ast
traverse(ast, {VariableDeclarator: {exit: [DelConsole_one]},});
ast = parse.parse(generator(ast).code)//刷新ast
traverse(ast, {VariableDeclarator: {exit: [DelConsole_two]},});
console.log('第三步:禁用console删减已完成')
//TODO 拆分对象合并
//TODO 对象表达式字符串合并
//TODO 花指令函数处理
//TODO 反控制流平坦化
//TODO 自执行实参替换形参
//TODO 替换空参数的自执行方法为顺序语句-慎用!可能涉及到作用域的问题。
//TODO 删减定时器
//TODO return函数简化
//TODO 数组函数简化
//TODO 常量计算
//TODO 未修改常量替换
//TODO 删除if语句块中假的部分-依赖常量计算
//TODO 正则检测替换
//TODO 删减未引用的标识符
//TODO 内存爆破检测
} catch (e) {
console.log(e);
} finally {
//TODO Finally ast还原js
code = generator(ast, opts = {jsescOption: {"minimal": true}}).code// 处理中文Unicode
//文件保存
fs.writeFile('./demoNew.js', code, (err) => {
});
}
function decrypt_arr(ast) {
//TODO 1 解密三部分的代码执行
let end = 3;//切片需要处理的代码块
let newAst = parse.parse('');//新建ast
let decrypt_code = ast.program.body.slice(0, end);//切片
newAst.program.body = decrypt_code// 将前3个节点替换进新建ast
let stringDecryptFunc = generator(newAst, {compact: true},).code;//转为js,由于存在格式化检测,需要指定选项,来压缩代码// 自动转义
eval(stringDecryptFunc);//执行三部分的代码
//TODO 2 准备工作及对解密三部分节点删除
let stringDecryptFuncAst = ast.program.body[end - 1];// 拿到解密函数所在的节点
let DecryptFuncName = stringDecryptFuncAst.id.name;//拿到解密函数的名字
var rest_code = ast.program.body.slice(end); // 剩下的节点
ast.program.body = rest_code;//剩下的节点替换
//TODO 3 加密数组还原
traverse(ast, {
CallExpression(path) {//回调表达式匹配--替换加密数组为对应的值
if (t.isIdentifier(path.node.callee, {name: DecryptFuncName})) { //当变量名与解密函数名相同时,就执行相应操作
path.replaceWith(t.valueToNode(eval(path.toString()))); // 值替换节点
}
},
});
return ast;
}
function ReIdent(path) {
// 标识符简化
let node = path.node;//获取路径节点
if (!t.isIdentifier(node.id) || !t.isIdentifier(node.init)) return;
let leftName = node.id.name;//函数名称
let rightName = node.init.name;//函数名称
let scope = path.scope;//获取路径的作用域
let binding = scope.getBinding(leftName);//获取绑定
if (!binding || binding.constantViolations.length > 0) {//检查该变量的值是否被修改--一致性检测
return;
}
let paths = binding.referencePaths;//绑定引用的路径
let paths_sums = 0;
paths.map(function (refer_path) {
refer_path.node.name = rightName;//标识符重命名
paths_sums += 1;//路径+1
});
if (paths_sums == paths.length) {//若绑定的每个路径都已处理 ,则移除当前路径
path.remove();//删除路径
}
}
function DelConsole_one(path) {
// 删除console
let node = path.node;//获取路径节点
if (!t.isCallExpression(node.init)) return;//不是回调表达式,退出
if (node.init.arguments.length !== 2) return;//形参不等于2个
if (!t.isThisExpression(node.init.arguments[0])) return;//this表达式
let thisname = node.id.name;//节点名称
let scope = path.scope;//获取路径的作用域
let binding = scope.getBinding(thisname);//获取绑定
if (!binding || binding.constantViolations.length > 0) {//检查该变量的值是否被修改--一致性检测
return;
}
let paths = binding.referencePaths;//绑定引用的路径
paths.map(function (refer_path) {
let bindpath = refer_path.parentPath;//父路径
if (!t.isCallExpression(bindpath)) return;//回调表达式判断
if (!t.isIdentifier(bindpath.node.callee)) return;//标识符判定
bindpath.remove();//删除路径
});
path.remove();//删除路径
}
function DelConsole_two(path) {
// 删除console遗留下列未使用的定义变量
let node = path.node;//获取路径节点
if (!t.isCallExpression(node.init)) return;//不是回调表达式,退出
if (node.init.arguments.length !== 0) return;//形参不等于0个
if (!t.isFunctionExpression(node.init.callee)) return;//this表达式
let thisname = node.id.name;//节点名称
let scope = path.scope;//获取路径的作用域
let binding = scope.getBinding(thisname);//获取绑定
if (!binding || binding.constantViolations.length > 0) {//检查该变量的值是否被修改--一致性检测
return;
}
let paths = binding.referencePaths;//绑定引用的路径
if (paths.length !== 0) return;
path.remove();//删除路径
}
六、解混淆完成
demoNew.js
function hi() {
console["log"]("Hello World!");
}
hi();