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

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

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

相关文章

如何让CANoe或Wireshark自动解析应用层协议

当我们使用CANoe软件或Wireshark工具抓取以太网总线上的报文时,网卡首先会把以太网总线上的模拟信号解析成以太网帧数据。数据链路层根据二层头部中的Type字段值确定上层的协议。 如果以太网使用的是TCP/IP协议栈,那么Type值要么是0x0800(IPv4),要么是0x0806(ARP),要么是0x…

偏微分方程算法之椭圆型方程差分格式编程示例

目录 一、示例1-五点菱形格式 1.1 C代码 1.2 计算结果 二、示例2-九点紧差分格式 2.1 C代码 2.2 计算结果 三、示例3-二阶混合边值 3.1 C代码 3.2 计算结果 本专栏对椭圆型偏微分方程的三种主要差分方法进行了介绍&#xff0c;并给出相应格式的理论推导过程。为加深对…

Flutter实战记录-协作开发遇到的问题

一.前言 Android项目使用了混合架构&#xff0c;部分模块使用Flutter进行开发。在电脑A上开发的项目提交到git仓库&#xff0c;电脑B拉取后进行操作&#xff0c;遇到两个问题&#xff0c;特此做一下记录&#xff1b; 二.问题A Settings file ‘D:\xxx\settings.gradle’ line…

[微信小程序] 入门笔记2-自定义一个显示组件

[微信小程序] 入门笔记2-自定义一个显示组件 0. 准备工程 新建一个工程,删除清空app的内容和其余文件夹.然后自己新建pages和components创建1个空组件和1个空页面. 设定 view 组件的默认样式,使其自动居中靠上,符合习惯.在app.wxss内定义,作用做个工程. /**app.wxss**/ /* 所…

renren-fast开源快速开发代码生成器

简介 renrenfast框架介绍 renren-fast是一个轻量级的Spring Boot快速开发平台&#xff0c;能快速开发项目并交付.完善的XSS防范及脚本过滤&#xff0c;彻底杜绝XSS攻击实现前后端分离&#xff0c;通过token进行数据交互 使用流程 项目地址 https://gitee.com/renrenio/ren…

MQTT 5.0 报文解析 03:SUBSCRIBE 与 UNSUBSCRIBE

欢迎阅读 MQTT 5.0 报文系列 的第三篇文章。在上一篇中&#xff0c;我们介绍了 MQTT 5.0 的 PUBLISH 及其响应报文。现在&#xff0c;我们将介绍用于订阅和取消订阅的控制报文。 在 MQTT 中&#xff0c;SUBSCRIBE 报文用于发起订阅请求&#xff0c;SUBACK 报文用于返回订阅结果…

浅谈Session和Cookie

各位大佬光临寒舍&#xff0c;希望各位能赏脸给个三连&#xff0c;谢谢各位大佬了&#xff01;&#xff01;&#xff01; 目录 1.Cookie 2.Sesssion&#xff08;会话&#xff09; 3.Session和Cookie的联系 4.总结 1.Cookie Cookie是客户端存储数据的机制&#xff0c;一般是…

OpenHarmony 实战开发—— refreshlayout 组件开发学习指南~

1. RefreshLayout_harmonyos 功能介绍 1.1. 组件介绍&#xff1a; RefreshLayout_harmonyos 是一款下拉刷新组件 1.2. 手机模拟器上运行效果&#xff1a; 2. RefreshLayout_harmonyos 使用方法 2.1 在目录 build.gradle 下 implementation project(":refreshlayout_ha…

信号产生的五种方式

文章目录 正文前的知识准备kill 命令查看信号man手册查看信号信号的处理方法 认识信号产生的5种方式1. 工具2. 键盘3. 系统调用kill 向任意进程发送任意信号raise 给调用方发送任意信号abort 给调用方发送SIGABRT信号 4. 软件条件5. 异常 正文前的知识准备 kill 命令查看信号 …

PM说|还有不会DISC的项目经理?

DISC行为模型是一种常用于职场中的人际交往工具&#xff0c;它通过对个体的行为特点进行分类和分析&#xff0c;帮助人们更好地理解自己和他人的行为方式&#xff0c;从而更加高效地进行沟通和合作。在项目管理过程中&#xff0c;多方沟通是必备工作技能&#xff0c;如何利用DI…

Day 63:单调栈 LeedCode 84.柱状图中最大的矩形

84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 解释&a…

HarmonyOS开发案例:【电子相册】

介绍 如何实现一个简单的电子相册应用的开发&#xff0c;主要功能包括&#xff1a; 实现首页顶部的轮播效果。 实现页面跳转时共享元素的转场动画效果。 实现通过手势控制图片的放大、缩小、左右滑动查看细节等效果。 相关概念 [Swiper]&#xff1a;滑块视图容器&#x…

20230507,LIST容器

学了又忘学了又忘&#xff0c;明知道会忘又不想复习又还得学 LIST容器 1.1 基本概念 链表是一种物理存储单元上非连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接实现的&#xff1b;链表由一系列结点组成 结点&#xff1a;一个是存储数据元素的数据域&a…

Ubuntu18.04设置SSH密钥登录

我们一般使用 VSCode 、MobaXterm、PuTTY等 SSH 客户端来远程管理 Linux 服务器。但是&#xff0c;一般的密码方式登录&#xff0c;容易有密码被暴力破解的问题。所以&#xff0c;一般我们会将 SSH 的端口设置为默认的 22 以外的端口&#xff0c;或者禁用 root 账户登录。但是即…

值得收藏!修复Windows 10/11中找不到输出或输入设备的五种方法

序言 这篇文章主要关注处理声音输出/输入设备未发现的问题。它提供了许多可行的方法,帮助了许多Windows用户。阅读以下内容以找到你的解决方案。 最近,我将Windows 10更新到21H2,发现我的音频无法工作。当我把鼠标放在任务栏上的声音图标(上面有一个十字图标)上时,它会…

六、文件查找

一、文件查找 1.查找文件内容 ​ 命令&#xff1a;grep keywords /dir_path/filename 2.查找系统命令 ​ 命令&#xff1a;which command 3.查找命令及配置文件位置 ​ 命令&#xff1a;whereis command 4.find查找 ​ find $find_path -name|-type|-perm|-size|-atime…

休斯《公共管理导论》第5版/考研真题解析/章节题库

第一部分 考研真题精选 一、概念题二、简答题三、论述题四、案例分析题第二部分 章节题库 第1章 一个变革的时代第2章 政府的角色第3章 传统的公共行政模式第4章 公共管理第5章 公共政策第6章 治 理第7章 问 责第8章 利害关系人和外部环境第9章 管制、外包和公共企…

Redis(持久化)

文章目录 1.RDB1.介绍2.RDB执行流程3.持久化配置1.Redis持久化的文件是dbfilename指定的文件2.配置基本介绍1.进入redis配置文件2.搜索dbfilename&#xff0c;此时的dump.rdb就是redis持久化的文件3.搜索dir&#xff0c;每次持久化文件&#xff0c;都会在启动redis的当前目录下…

【数据库表的约束】

文章目录 一、NULL vs &#xff08;空字符串&#xff09;二、not null 和default三、列描述字段comment四、zerofill五、primary key 主键总结 一、NULL vs ‘’&#xff08;空字符串&#xff09; NULL和空字符串’’ NULL代表什么都没有。 空字符串’代表有&#xff0c;但串…

致远M3 Session 敏感信息泄露漏洞复现

0x01 产品简介 M3移动办公是致远互联打造的一站式智能工作平台,提供全方位的企业移动业务管理,致力于构建以人为中心的智能化移动应用场景,促进人员工作积极性和创造力,提升企业效率和效能,是为企业量身定制的移动智慧协同平台。 0x02 漏洞概述 致远M3 server多个日志文…