提示!本文章仅供学习交流,严禁用于任何商业和非法用途,未经许可禁止转载,禁止任何修改后二次传播,如有侵权,可联系本文作者删除!
AST简介
AST(Abstract Syntax Tree),中文抽象语法树,简称语法树(Syntax Tree),是源代码的抽象语法结构的树状表现形式,树上的每个节点都表示源代码中的一种结构。语法树不是某一种编程语言独有的,JavaScript、Python、Java、Golang 等几乎所有编程语言都有语法树。
在做逆向解混淆中,主要用到了 Babel 的以下几个功能包:
@babel/core:Babel 编译器本身,提供了 babel 的编译 API
@babel/parser:将 JavaScript 代码解析成 AST 语法树
@babel/traverse:遍历、修改 AST 语法树的各个节点
@babel/generator:将 AST 还原成 JavaScript 代码
@babel/types:判断、验证节点的类型、构建新 AST 节点等
常用API
常用节点
常见混淆还原
AST 有一个在线解析网站:https://astexplorer.net/ ,常用选择
极验4 gcaptcha.js 解混淆
字符还原
将混淆代码 和 解混淆后的代码放到ast网站对比结构
观察下来 将 extra 的row 替换成value 即可,这里直接删除 extra 节点 也可以
替换的对象名称
通过观察 C B I R 对应就是 y S W R Y . _CBIR 对应就是ySWRY. CBIR对应就是ySWRY._Ck 方法,多个地方流程一致, 思路:
1、初始化 方法,自执行
2、找到所有ySWRY.$_Ck 对应的方法名称
3、执行出结果 ,替换节点
还原 控制流平坦化
解混淆思路
1、 遍历ForStatement 节点找到固定格式代码块
2、 拿到ForStatement 的上一个节点,获取控制流的初始值
3、按照流程计算 switch 的初始值
4、遍历 SwitchCase 节点, 计算case 值, switch值 和case值一致时 向下计算
删除无关函数
js 头部方法已经不需要,直接节点遍历 找到对应值 remove 掉
完整代码如下
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
const fs = require("fs");// js混淆代码
process.argv.length > 2 ? encode_file = process.argv[2] : encode_file = "./input/jy_0422.js";
process.argv.length > 3 ? decode_file = process.argv[3] : decode_file = "./output/jy_0422_decode.js";// #######################################
// AST解析函数
// #######################################// 简化字符, unicode 字符串 和 表达式 1e3
const simplifyLiteral = {"NumericLiteral|StringLiteral"(path){var node = path.node;if (node.extra === undefined)return;delete node.extra;}
};/*
var t = 5 * Math["random"](2),s = t - 1,n = [];-------------var t = 5 * Math["random"](2);
var s = t - 1;
var n = [];*/
// 简单逗号表达式处理
function deal_VariableDeclarator(path) {if (t.isForStatement(path.parent)) {return;}var node = path.node;let body = [];if (node.declarations && node.declarations.length > 1) {node.declarations.forEach(dec => {body.push(t.variableDeclaration("var", [dec]));});path.replaceInline(body);}
}// 定义一个全局变量,存放待替换变量名
let name_array = [];// 获取待替换的名称
function get_name_array(path) {let {kind, declarations} = path.node;if (kind !== 'var'|| declarations.length !== 3|| declarations[0].init === null|| declarations[0].init.property === undefined)return;if (declarations[0].init.property.name !== funPropertyName[2])return;// 获取待替换节点变量名let name1 = declarations[0].id.name;// 获取待输出变量名let name2 = declarations[2].id.name;// 将变量名存入数组name_array.push(name1, name2);// 删除下一个节点path.getNextSibling().remove();// 删除下一个节点path.getNextSibling().remove();// 删除path节点path.remove()
}// 替换节点
function replace_name_array(path) {let {callee, arguments} = path.nodeif (callee === undefined || callee.name === undefined)return;// 不在name_array中的节点不做替换操作if (name_array.indexOf(callee.name) === -1)return;// 调用ySWRY.$_Ck函数获取结果let value = global_obj_name_fun[funPropertyName[2]](arguments[0].value);// 创建节点并替换结果let string_node = t.stringLiteral(value);path.replaceWith(string_node)
}// 控制流平坦化
function replace_ForStatement(path) {var node = path.node;// 获取上一个节点,也就是VariableDeclarationvar PrevSibling = path.getPrevSibling();// 判断上个节点的各个属性,防止报错if (PrevSibling.type === undefined|| PrevSibling.container === undefined|| PrevSibling.container[0].declarations === undefined|| PrevSibling.container[0].declarations[0].init === null|| PrevSibling.container[0].declarations[0].init.object === undefined|| PrevSibling.container[0].declarations[0].init.object.object === undefined)return;if (PrevSibling.container[0].declarations[0].init.object.object.callee.property.name !== funPropertyName[3])return;// if (PrevSibling.node.declarations[0].init.object.object.callee.property.name !== funPropertyName[3])// return;// SwitchStatement节点var body = node.body.body;// 判断当前节点的body[0]属性和body[0].discriminant是否存在if (!t.isSwitchStatement(body[0]))return;if (!t.isIdentifier(body[0].discriminant))return;// 获取控制流的初始值var argNode = PrevSibling.container[0].declarations[0].init;var init_arg_f = argNode.object.property.value;var init_arg_s = argNode.property.value;var init_arg = global_obj_name_fun[funPropertyName[3]]()[init_arg_f][init_arg_s];// 提取for节点中的if判断参数的value作为判断参数, 退出循环判断var break_arg_f = node.test.right.object.property.value;var break_arg_s = node.test.right.property.value;var break_arg = global_obj_name_fun[funPropertyName[3]]()[break_arg_f][break_arg_s];// 提取switch下所有的casevar case_list = body[0].cases;var resultBody = [];// 遍历全部的casefor (var i = 0; i < case_list.length; i++) {for (; init_arg != break_arg;) {// 提取并计算case后的条件判断的值var case_arg_f = case_list[i].test.object.property.value;var case_arg_s = case_list[i].test.property.value;var case_init = global_obj_name_fun[funPropertyName[3]]()[case_arg_f][case_arg_s];if (init_arg == case_init) {//当前case下的所有节点var targetBody = case_list[i].consequent;// 删除break节点,和break节点的上一个节点的一些无用代码if (t.isBreakStatement(targetBody[targetBody.length - 1])&& t.isExpressionStatement(targetBody[targetBody.length - 2])&& targetBody[targetBody.length - 2].expression.right.object.object.callee.object.name == global_obj_name) {// 提取break节点的上一个节点AJgjJ.EMf()后面的两个索引值var change_arg_f = targetBody[targetBody.length - 2].expression.right.object.property.value;var change_arg_s = targetBody[targetBody.length - 2].expression.right.property.value;// 修改控制流的初始值init_arg = global_obj_name_fun[funPropertyName[3]]()[change_arg_f][change_arg_s];// global_obj_name.funPropertyName[3][change_arg_f][change_arg_s];targetBody.pop(); // 删除breaktargetBody.pop(); // 删除break节点的上一个节点}//删除breakelse if (t.isBreakStatement(targetBody[targetBody.length - 1])) {targetBody.pop();}resultBody = resultBody.concat(targetBody);break;} else {break;}}}//替换for节点,多个节点替换一个节点用replaceWithMultiplepath.replaceWithMultiple(resultBody);//删除上一个节点PrevSibling.remove();
}// 删除无关函数
function delete_express(path) {let {expression} = path.nodeif (expression === undefined|| expression.left === undefined|| expression.left.property === undefined)return;if (expression.left.property.name === funPropertyName[0]|| expression.left.property.name === funPropertyName[1]|| expression.left.property.name === funPropertyName[2]|| expression.left.property.name === funPropertyName[3]) {path.remove()}
}// 获取全局函数
function getGlobalFunc(){let program_body= ast.program.body;if(program_body && program_body.length === 6){for (var i=0; i<program_body.length-1; i++){// 拿到body 前5个节点自执行globalCode += generator(program_body[i]).code + "\n";if(i<4){// 获取全局 ySWRYglobal_obj_name = program_body[i].expression.left.object.name;// 为了获取对应名称 ySWRY.$_CkfunPropertyName.push(program_body[i].expression.left.property.name);}}return globalCode}
}// 删除无用方法
function delete_func(path) {let {id} = path.node;if(id.name == global_obj_name){path.remove()}
}// #######################################// #######################################
// AST还原流程
// #######################################// 读取需要解码的js文件, 注意文件编码为utf-8格式
let jscode = fs.readFileSync(encode_file, {encoding: "utf-8"});// 将js代码修转成AST语法树
let ast = parser.parse(jscode);console.time("处理完毕,耗时");var global_obj_name = ''; // ySWRY
var globalCode = '';
var funPropertyName = [];// 获取自执行方法
globalCode = getGlobalFunc();
eval(globalCode);
// console.log('funPropertyName--', funPropertyName); // [ '$_AB', '$_Bx', '$_Ck', '$_DK' ]// 全局方法 ySWRY
global_obj_name_fun = eval("(" + global_obj_name + ")");// 简化字符串
traverse(ast, simplifyLiteral);// AST结构修改逻辑
const visitor = {VariableDeclaration: {enter: [get_name_array]},CallExpression: {enter: [replace_name_array]},ForStatement: {enter: [replace_ForStatement]},ExpressionStatement: {enter: [delete_express]},FunctionDeclaration: {enter: [delete_func]}
};// 遍历语法树节点,调用修改函数
traverse(ast, visitor);resolveSequence_t = {VariableDeclaration: {exit: [deal_VariableDeclarator]},
};// 简单逗号表达式 还原
traverse(ast, resolveSequence_t);console.timeEnd("处理完毕,耗时");// 将ast转成js代码,{jsescOption: {"minimal": true}} unicode -> 中文
let {code} = generator(ast, opts = {jsescOption: {"minimal": true}});
// 将js代码保存到文件
fs.writeFile(decode_file, code, (err) => {
});console.log("end");
最终效果 解混淆部分不需要任何修改,只修改下待还原 的文件名 和 输出文件名, 支持极验4 代任意版本的混淆JS 代码。