极验4 一键解混淆

news2024/11/24 19:39:53

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

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 代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1652771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

书名号怎么打?4个输入方法请记好!

“我在电脑上对文档进行编辑时&#xff0c;需要输入书名号&#xff0c;但不知道书名号怎么打。大家平常是怎么输入书名号的呀&#xff1f;可以分享一下吗&#xff1f;” 在撰写文档、编辑文章或发送信息时&#xff0c;我们经常需要用到书名号。书名号主要用于标明书名、篇名、报…

数据库系统概论(超详解!!!)第七节 关系数据库理论

1.问题的提出 关系数据库逻辑设计&#xff1a; 针对具体问题&#xff0c;如何构造一个适合于它的数据模式 针对一个具体问题&#xff0c;构造出一个适合于它的数据模式&#xff0c;即应该构造几个关系&#xff0c;每个关系由哪些属性组成等。 数据库逻辑设计的工具──关系…

Omnity 进展月报 | 2024.4.1-4.30

Omnity 大事摘要 1、Octopus 官宣升级为 Omnity。 2、Omnity 4月28号正式上线&#xff0c;实现BTC 和 ICP 之间跨链转账 Runes 资产。 3、为庆祝上线&#xff0c;以符文 HOPE•YOU•GET•RICH 为资产&#xff0c;发红包快速触达大量用户&#xff0c;体验跨链服务。 4、Omni…

kubebuilder(6)webhook

operator中的webhook也是很重要的一块功能。也是相对比较独立的模块&#xff0c;所以放在后面讲。 webhook是一个callback&#xff0c;注册到k8s的api-server上。当某个特定的时间发生时&#xff0c;api server就会查询注册的webhook&#xff0c;并根据一些逻辑确认转发消息给…

抖音小店是什么?它和直播带货有什么区别和联系?一篇详解!

大家好&#xff0c;我是电商糖果 在网上大家都说抖音的流量大&#xff0c;在抖音做电商比较赚钱。 可是有很多人对抖音电商并不了解。 甚至搞不懂抖音小店是什么&#xff1f;它和直播带货的区别和联系也不清楚。 下面&#xff0c;糖果就来给大家好好解答一下这个问题。 抖音…

Educational Codeforces Round 165 (Div. 2) A~E

A.Two Friends (思维) 题意&#xff1a; 小 A A A想开一个派对。他有 n n n个朋友&#xff0c;他希望至少有 2 2 2个朋友参加他的派对。 i i i 这个朋友最好的朋友是 p i p_i pi​ 。所有的 p i p_i pi​ 都是不同的&#xff0c;对于每一个 i ∈ [ 1 , n ] i \in [1, n] …

HTTP深度指南:协议结构、请求方法与状态码

详解HTTP HTTP教程HTTP消息结构HTTP状态码HTTP和HTTPS HTTP教程 HTTP&#xff08;超文本传输协议&#xff0c;HyperText Transfer Protocol&#xff09;是一种用于分布式、协作式、超媒体信息系统的应用层协议。* HTTP是一个基于TCP/IP通信协议来传递数据的&#xff08;HTML文…

【源码】[完美接单]亲测双端获取通讯录、相册、短信定位源码

源码介绍 无hb源码&#xff0c;需要反编译。 此款修复了逻辑&#xff0c;现在全部能获取。包括华为鸿蒙。 安卓可获取&#xff1a;短信定位相册通讯录 IOS可获取&#xff1a;定位相册通讯录 源码截图 CD&#xff1a;获取方式联系小编 微信&#xff1a;uucodes 公众号&…

7天精通Web APIs——-Bom操作(理论+实战)(第五天)

一、window对象 1.1 window对象和bom的关系 首先理解dom和bom之间的关系 显然bom的范围比较大 bom的全称为浏览器对象模型 window是bom的核心对象&#xff0c;window里面有很多属性和方法&#xff0c;用于实现浏览器与 JavaScript 代码之间的交互。作为 JavaScript 的全局对…

Vue中使用$t(‘xxx‘)实现中英文切换;

&#xff08;原文链接&#xff09; 介绍 {{$t(key)}} &#xff1a;是VueI18n插件提供的函数&#xff0c;主要用于根据当前语言环境返回对应的翻译文本&#xff0c;以便在页面上显示多语言内容。 key&#xff1a;作为参数传递给函数$t()的字符串&#xff0c;用于指定需要翻译的…

快速入门 Postman Mock Server 的使用

Postman 作为一个功能强大的 API 开发工具&#xff0c;凭借 Mock Servers 功能&#xff0c;使得开发者能够轻松而高效地模拟服务器环境&#xff0c;加快开发与测试进程。 启动 Mock Servers 服务 当你在 Postman 中创建一个新的项目时&#xff0c;系统默认并不会显示 Mock Se…

Python爬虫之selenium,有验证码模拟登录

一. 前言 在学习Selenium之前&#xff0c;通过request.get()或者.post(),很难获取网站所加载的动态数据&#xff0c;通过Selenium强大的自动化功能、多浏览器支持、跨平台支持等优点&#xff0c;让我轻松获取一些之前很难获取的数据&#xff0c;这次的案例也是结合之前的所学知…

Window server 2012搭建FTP

禁止废话&#xff0c;直接上图 &#xff01;&#xff01;&#xff01; 注意&#xff1a;如果点击角色添加保存&#xff1a; Server Manager Error - Server Manager is collecting inventory data. Wizard will be available after data collection finishes 解决方案&#x…

Baidu Comate——让软件研发更高效、更智能

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;给自己一个梦想&#xff0c;给世界一个惊喜。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章目录 一、Baidu Co…

JAVA链表相关习题2

1.反转一个单链表。 . - 力扣&#xff08;LeetCode&#xff09; //2在1前面 //1在3前面 //ListNode curhead.next //head.nextnull(翻转后头节点变为最后一个节点) // while(cur ! null) { //记录 当前需要翻转节点的下一个节点 ListNode curNext cu…

9、String类型和基本数据类型转换(Java)

String类型和基本数据类型转换 1、基本数据类型转String类型2、String类型转基本数据类型⭐ 1、基本数据类型转String类型 Java中String类型是字符串类型&#xff0c;是用 “ ” 双引号括起来的内容&#xff0c;所以基本数据类型转String类型直接&#xff0b;“ ”即可。&…

损失一件外套?

2024/05/07&#xff0c;晴 碎碎念一波&#xff01; 早上洗漱完要出门时&#xff0c;发现自己昨天穿的外套不见了&#xff01;&#xff01;&#xff01;外套上身效果很不错&#xff0c;买了1年多穿的频率非常高&#xff0c;现在丢了还真挺可惜。 衣服口袋有一个耳机&#xff0…

stm32单片机遇到的问题(持续更新)

flymcu下载问题一直显示连接&#xff0c;实际是连接不上 参考&#xff0c;软件一键下载电路等 使用flymcu下载程序过程中&#xff0c;检测两个地方**&#xff0c;第一&#xff0c;两个boot引脚在下载和硬件运行不同的连接方式** BOOT1x&#xff0c;BOOT00&#xff1a;最常用的模…

六西格玛备考攻略:无从下手?一文让你豁然开朗

当你决定备考六西格玛时&#xff0c;可能会感到有些无从下手。毕竟&#xff0c;这是一个涉及多个领域和方面的综合性考试&#xff0c;需要掌握的知识点和技能也非常广泛。但是&#xff0c;只要你有一个清晰的学习计划和一些有效的备考方法&#xff0c;就能够顺利地通过考试。以…

hadoop学习---基于Hive的教育平台数据仓库分析案例(三)

衔接第一部分&#xff0c;第一部分请点击&#xff1a;基于Hive的教育平台数据仓库分析案例&#xff08;一) 衔接第二部分&#xff0c;第二部分请点击&#xff1a;基于Hive的教育平台数据仓库分析案例&#xff08;二) 学生出勤模块&#xff08;全量分析&#xff09;&#xff1a…