js代码反混淆之ast的使用
代码
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
let origin_code = `
function _0x506bbd(v){
const a= !![]
console.log(a);
return v.length
}
console.log(_0x506bbd('\x69\x6e\x64\u0435\x78\x4f\x66'));
`
let ast = parser.parse(origin_code);
pre_ast = ast.program.body.slice(0, 1)
const gen = generator(ast, {
// 禁止自动格式化(针对反调试)
compact: true
})
eval(gen.code)
function toSource(_ast) {
let {code} = generator(_ast, {
// 是否格式化
compact: false,
jsescOption: {
// 自动转义
minimal: true,
}
});
return code.replace(/!!\[\]/g, 'true').replace(/!\[\]/g, 'false')
}
function getvalue(path) {
const node = path.node
if (t.isStringLiteral(node)) {
if (node && node.exact) {
delete path.node.exact
}
} else if (t.isCallExpression(node)) {
const {callee} = path.node;
if (t.isMemberExpression(callee) &&
callee.object.name === "console" &&
callee.property.name === "log") {
node.arguments.map(item => {
if (t.isCallExpression(item)) {
let _value = item.arguments
let _key = item.callee.name
const func = eval(_key)
let new_value = func(..._value.map(v => v.value))
let value_type = Object.prototype.toString.call(new_value)
let new_node;
if (value_type === "[object Number]") {
new_node = t.NumericLiteral(new_value)
} else if (value_type === "[object String]") {
new_node = t.stringLiteral(new_value)
}
path.node.arguments[0] = new_node
}
})
}
}
}
function traverse_ast(ast, opts) {
traverse(ast, opts);
return ast
}
let step1_ast = traverse_ast(ast, {StringLiteral: getvalue})
let step2_ast = traverse_ast(step1_ast, {CallExpression: getvalue})
let new_code = toSource(step2_ast)
console.log("before: =============")
console.log(origin_code)
console.log("after: =============")
console.log(new_code)
目标结果:
true
7
true
before: =============
function _0x506bbd(v){
const a= !![]
console.log(a);
return v.length
}
console.log(_0x506bbd('indеxOf'));
after: =============
function _0x506bbd(v) {
const a = true;
console.log(a);
return v.length;
}
下面是一个完整的示例
//解密替换字符串 --> 解耦 object --> 去控制流。
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');
function replace_ugly_code(path) {
let arr_path = path.get('body.0');
let code = arr_path.toString();
let shift_path = path.get('body.1');
let callee_path = shift_path.get('expression.callee');
let second_arg_node = callee_path.get('params.1').node;
let first_body = callee_path.get('body.body.0');
let call_fun = first_body.node.declarations[0].id
var all_next_siblings = first_body.getAllNextSiblings();
all_next_siblings.forEach(next_sibling => {
next_sibling.remove();
});
first_body.insertBefore(t.ExpressionStatement(t.UpdateExpression("++", second_arg_node)));
first_body.insertAfter(t.ExpressionStatement(t.callExpression(call_fun, [second_arg_node])));
code += '!' + shift_path.toString();
let call_path = path.get('body.2');
let call_name = call_path.node.declarations[0].id.name;
call_path.traverse({
AssignmentExpression(_path) {
let left = _path.get('left');
let left_code = left.toString();
let right = _path.get('right');
let right_code = right.toString();
if (right_code.indexOf(call_name) === -1 ||
right_code.indexOf(left_code) === -1 )
{
return;
}
const if_parent_path = _path.findParent(p => {
return p.isIfStatement();
});
if_parent_path && if_parent_path.replaceWith(_path.node);
},
})
code += call_path.toString();
return {call_name,code};
}
const delete_extra =
{
StringLiteral:function(path)
{
delete path.node.extra;
},
}
function replace_simple_code(path) {
traverse(path.node,delete_extra)//防止所有字符串以十六进制形式展现导致查找失败。
let source_code = path.toString();
if(source_code.indexOf('removeCookie') !== -1)
{
var {call_name,code} = replace_ugly_code(path);
}
else
{
let arr_path = path.get('body.0');
var code = arr_path.toString();
let shift_path = path.get('body.1');
code += '!' + shift_path.toString();
let call_path = path.get('body.2');
var call_name = call_path.get('declarations.0.id').toString();
code += call_path.toString();
}
eval(code);
let can_be_delete = true;
path.traverse({
CallExpression: function(_path) {
let callee = _path.get('callee');
if(callee.toString() !== call_name)
return;
try
{
let value = eval(_path.toString());
//console.log(value);
value !== undefined && _path.replaceWith(t.valueToNode(value))
}
catch(e)
{
can_be_delete = false;
}
},
});
for (let i=0 ;can_be_delete && i<3; i++)
{
path.get('body.0').remove();
}
}
//解密字符串
const decode_str = {
"Program"(path)
{
replace_simple_code(path)
},
};
//还原object
const decode_object = {
VariableDeclarator(path)
{
const {id,init} = path.node;
if (!t.isObjectExpression(init) || init.properties.length == 0) return;
let name = id.name;
let scope = path.scope;
for (const property of init.properties)
{
let key = property.key.value;
if (key.length !== 5)
{
return;
}
let value = property.value;
if (t.isLiteral(value))
{
scope.traverse(scope.block,{
MemberExpression(_path)
{
let _node = _path.node;
if (!t.isIdentifier(_node.object,{name:name})) return;
if (!t.isLiteral(_node.property, {value:key})) return;
_path.replaceWith(value);
},
})
}
else if (t.isFunctionExpression(value))
{
let ret_state = value.body.body[0];
if(!t.isReturnStatement(ret_state)) continue;
scope.traverse(scope.block,{
CallExpression: function(_path) {
let {callee,arguments} = _path.node;
if (!t.isMemberExpression(callee)) return;
if (!t.isIdentifier(callee.object,{name:name})) return;
if (!t.isLiteral(callee.property, {value:key})) return;
if (t.isCallExpression(ret_state.argument) && arguments.length > 0) {
_path.replaceWith(t.CallExpression(arguments[0], arguments.slice(1)));
}
else if (t.isBinaryExpression(ret_state.argument) && arguments.length === 2)
{
let replace_node = t.BinaryExpression(ret_state.argument.operator, arguments[0], arguments[1]);
_path.replaceWith(replace_node);
}
else if (t.isLogicalExpression(ret_state.argument) && arguments.length === 2)
{
let replace_node = t.LogicalExpression(ret_state.argument.operator, arguments[0], arguments[1]);
_path.replaceWith(replace_node);
}
}
})
}
}
path.remove();//慎重
},
}
//去控制流
const decode_while = {
WhileStatement(path)
{
const {test,body} = path.node;
//特征语句判断,body.body[0] 必须是 SwitchStatement 节点,
//注意一定要先判断长度,避免index出错
if (!t.isUnaryExpression(test) || body.body.length === 0 || !t.isSwitchStatement(body.body[0])) return;
let switch_state = body.body[0];
//获取discriminant及cases节点
let {discriminant,cases} = switch_state;
//特征语句判断,经过此判断后,基本可以确定是需要还原的while节点了。
//如果出错了,可以继续增加判断,直到不出错即可
if (!t.isMemberExpression(discriminant) || !t.isUpdateExpression(discriminant.property)) return;
//获取数组名,用于查找该数组。
let arr_name = discriminant.object.name;
let arr = [];
//在这里再加一个特征语句的判断:WhileStatement 节点前面有一个节点
let all_pre_siblings = path.getAllPrevSiblings();
if (all_pre_siblings.length !== 1) return;
all_pre_siblings.forEach(pre_path =>
{//虽然知道是第0个节点,但这里还是做下判断取arr
const {declarations} = pre_path.node;
let {id,init} = declarations[0];
if (arr_name == id.name)
{//如果是定义arr的节点,拿到该arr的值
arr = init.callee.object.value.split('|');
}
//没啥用的语句可以直接删除
pre_path.remove();
})
//新建一个 数组变量,用于存放 case 节点
let ret_body = [];
arr.forEach(index =>
{//遍历数组,去case节点
let case_body = cases[index].consequent;
if (t.isContinueStatement(case_body[case_body.length-1]))
{//删除 continue语句
case_body.pop();
}
//存放于数组变量中
ret_body = ret_body.concat(case_body);
})
//替换
path.replaceInline(ret_body);
},
}
var jscode = fs.readFileSync("./encode_ob.js", {
encoding: "utf-8"
});
let ast = parser.parse(jscode);
traverse(ast, decode_str);
traverse(ast, decode_object);
traverse(ast, decode_while);
let {code} = generator(ast);
fs.writeFile('decode_ob.js', code, (err) => {});