极验4 一键解混淆

提示!本文章仅供学习交流,严禁用于任何商业和非法用途,未经许可禁止转载,禁止任何修改后二次传播,如有侵权,可联系本文作者删除!

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.node
    if (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;

    // 获取上一个节点,也就是VariableDeclaration
    var 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下所有的case
    var case_list = body[0].cases;
    var resultBody = [];

    // 遍历全部的case
    for (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(); // 删除break
                    targetBody.pop(); // 删除break节点的上一个节点
                }
                //删除break
                else if (t.isBreakStatement(targetBody[targetBody.length - 1])) {
                    targetBody.pop();
                }
                resultBody = resultBody.concat(targetBody);
                break;
            } else {
                break;
            }
        }
    }
    //替换for节点,多个节点替换一个节点用replaceWithMultiple
    path.replaceWithMultiple(resultBody);

    //删除上一个节点
    PrevSibling.remove();
}

// 删除无关函数
function delete_express(path) {
    let {expression} = path.node
    if (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){
                // 获取全局 ySWRY
                global_obj_name = program_body[i].expression.left.object.name;
                // 为了获取对应名称 ySWRY.$_Ck
                funPropertyName.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 代码。

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
安装AST混淆的具体步骤如下: 1. 首先,下载并安装Java开发工具包(JDK)。AST混淆工具是基于Java的,所以您需要先安装JDK并将其配置到系统环境变量中。 2. 下载并安装AST混淆工具。您可以在AST混淆工具的官方网站或代码托管平台上找到安装包,并按照提供的指示进行安装。 3. 打开AST混淆工具的安装目录,并找到主程序的可执行文件。 4. 配置AST混淆工具。在主程序的安装目录中,找到配置文件并打开它。根据您的需求,配置文件中的选项包括输入文件路径、输出文件路径、混淆算法选择等。根据您的实际需求,修改这些选项。 5. 使用AST混淆工具进行混淆。在命令行或终端中导航到AST混淆工具的安装目录,并执行主程序的可执行文件。根据您在配置文件中设置的选项,输入需要混淆的文件路径,并选择适当的混淆算法。然后,执行混淆命令。 6. 等待混淆过程完成。根据您需要混淆的文件大小和复杂性,混淆过程可能需要一些时间。请耐心等待,直到混淆过程完成。 7. 检查混淆结果。混淆工具通常会生成一个混淆后的文件,并将其保存到您在配置文件中指定的输出路径中。您可以打开该文件以查看混淆结果,并验证其有效性。 总之,安装AST混淆工具的过程大致分为下载安装、配置选项、执行混淆命令和检查结果等步骤。请按照以上步骤操作,以完成安装和使用AST混淆工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值