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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Redis7】10大数据类型之Zset类型

文章目录 1.Zset类型2.常用命令3.示例3.1 ZADD,ZRANGE和ZREVRANGE3.2 ZSCORE,ZCARD和ZREM3.3 ZRANGEBYSCORE和ZCOUNT3.4 ZRANK和ZREVRANK3.5 Redis7新命令ZMPOP 1.Zset类型 Redis的Zset&#xff08;Sorted Set&#xff0c;有序集合&#xff09;是一种特殊的数据结构&#xff0…

【教学类-18-03】20240508《蒙德里安“红黄蓝黑格子画”-A4横版》(大小格子)

作品展示 背景需求&#xff1a; 2年前制作了蒙德里安随机线条 【教学类-18-02】20221124《蒙德里安“红黄蓝黑格子画”-A4竖版》&#xff08;大班)_蒙德里安模版-CSDN博客文章浏览阅读1k次。【教学类-18-02】20221124《蒙德里安“红黄蓝黑格子画”-A4竖版》&#xff08;大班)…

ChIP-seq or CUTTag,谁能hold住蛋白质与DNA互作主战场?

DNA与蛋白质的相互作用作为表观遗传学中的一个重要领域&#xff0c;对理解基因表达调控、DNA复制与修复、表观遗传修饰&#xff08;组蛋白修饰&#xff09;及染色质结构等基本生命过程至关重要。 自1983年James Broach首次公布染色质免疫共沉淀&#xff08;ChIP&#xff09;技…

Docker-Compose 容器集群的快速编排

Docker-compose 简介 Docker-Compose项目是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Docker-Compose将所管理的容器分为三层&#xff0c;分别是 工程&#xff08;project&#xff09;&#xff0c;服务&#xff08;service&#xff09;以及容器&…

LLM 安全 | 大语言模型应用安全入门

一、背景 2023年以来&#xff0c;LLM 变成了相当炙手可热的话题&#xff0c;以 ChatGPT 为代表的 LLM 的出现&#xff0c;让人们看到了无限的可能性。ChatGPT能写作&#xff0c;能翻译&#xff0c;能创作诗歌和故事&#xff0c;甚至能一定程度上做一些高度专业化的工作&#x…

滤除纹波的方法:

空载时看起来纹波比较小但是一加负载的时候纹波一下子上来是因为空载时候电流比较小MOS大部分时间处于关断状态&#xff0c;而加上负载后电流变大MOS打开关闭更为频繁&#xff0c;因而纹波更大。 下图中的尖峰可能是因为电路的寄生电感导致的&#xff0c;理论上只能削弱不能避…

LINUX 入门 8

LINUX 入门 8 day10 20240507 耗时&#xff1a;90min 有点到倦怠期了 课程链接地址 第8章 TCP服务器 1 TCP服务器的介绍 开始讲服务器端&#xff0c;之前是客户端DNShttps请求 基础&#xff1a;网络编程并发服务器&#xff1a;多客户端 一请求&#xff0c;一线程 veryold…

推荐网站(6)33台词,通过台词找电影、电视剧、纪录片等素材

今天推荐一个网站33台词&#xff0c;你可以根据电影、电视剧、纪录片等某一段台词&#xff0c;来找到来源&#xff0c;帮你精确到多少分多少秒出现的&#xff0c;非常的好用&#xff0c;尤其是对那种只记得一些经典台词&#xff0c;不知道是哪个电影的人来说&#xff0c;帮助巨…

揭秘全网热门话题:抖音快速涨粉方法,巨量千川投流助你日增10000粉

在当今社交媒体的时代( 千川投流&#xff1a;hzzxar&#xff09;抖音成为了年轻人分享自己才华和生活的平台。然而&#xff0c;要在抖音上快速获得关注和粉丝&#xff0c;却不是一件容易的事情。今天&#xff0c;我们将揭秘全网都在搜索的抖音快速涨1000粉的秘籍&#xff0c;带…

网络文件共享

存储类型分三类 直连式存储&#xff1a;DAS存储区域网络&#xff1a;SAN网络附加存储&#xff1a;NAS 三种存储架构的应用场景 DAS虽然比较古老了&#xff0c;但是还是很适用于那些数据量不大&#xff0c;对磁盘访问速度要求较高的中小企业SAN多适用于文件服务器&#xff0c…

pyfilesystem2,一个超级实用的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超级实用的 Python 库 - pyfilesystem2。 Github地址&#xff1a;https://github.com/pyfilesystem/pyfilesystem2 PyFilesystem2是一个强大的Python库&#xff0c;用于抽象化文件系统操作。它…

静态分析-RIPS-源码解析记录-02

这部分主要分析scanner.php的逻辑&#xff0c;在token流重构完成后&#xff0c;此时ini_get是否包含auto_prepend_file或者auto_append_file 取出的文件路径将和tokens数组结合&#xff0c;每一个文件都为一个包含require文件名的token数组 接着回到main.php中&#xff0c;此时…

【GUI软件】调用YouTube的API接口,采集关键词搜索结果,并封装成界面工具!

文章目录 一、背景介绍1.1 爬取目标1.2 演示视频1.3 软件说明 二、代码讲解2.1 调用API-搜索接口2.2 调用API-详情接口2.3 API_KEY说明2.4 软件界面模块2.5 日志模块 三、获取源码及软件 一、背景介绍 1.1 爬取目标 您好&#xff01;我是马哥python说&#xff0c;一名10年程序…

动态规划day.2

62.不同路径 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#…

能否直接上手 Qt ?——看完 C++ 课本后怎么做?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Qt的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;如果你已经阅读了 C 课本&#xff0c;但仍然感到…

TMS320F28335学习笔记-时钟系统

第一次使用38225使用了普中的clocksystem例程进行编译&#xff0c;总是编译失败。 问题一&#xff1a;提示找不到文件 因为工程的头文件路径没有包含&#xff0c;下图的路径需要添加自己电脑的路径。 问题二 找不到库文件 例程种的header文件夹和common文件夹不知道从何而来…

7.删除有序数组中的重复项(快慢指针)

文章目录 题目简介题目解答解法一&#xff1a;暴力解法复杂度分析&#xff1a; 解法二&#xff1a;双指针(快慢指针)代码&#xff1a;复杂度分析&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 相关的讲解&#xff01;&#x1f600; 题目简介 题目解…

【C语言 | 字符串处理】sscanf 详细介绍、使用说明以及使用例子源码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a;2024-05-08 1…

【Python爬虫】使用request和xpath爬取高清美女图片

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 &#x1f388; urlib.request &#x1f525;具体的方法 ✈ lxml &#x1f525;xpath的基本语法 1. 基本路径 2. 选择节点 3. 谓语&#xff08;Predicates&#xff09; 4. 通配符 5. 选择多个路径 6. 函数 …

OV证书——企业网站的第一选择

据官方数据统计&#xff0c;从2024年开始OV证书的签发量远远超过DV证书的签发量&#xff0c;越来越多的企业网站摒弃了基础的DV证书&#xff0c;选择更高级别的OV证书。 但是其价格相对于DV证书来说要高几百甚至上千元&#xff0c;这里推荐性价比很高的JoySSL&#xff0c;他们…
最新文章