当前位置: 首页 > news >正文

[逆向知识] AST抽象语法树:混淆与反混淆的逻辑互换(二)

博客配套代码发布于github:半自动化cookie更新(欢迎顺手Star一下⭐)

相关逆向知识:

[逆向知识] AST抽象语法树:混淆与反混淆的逻辑互换(一)-CSDN博客

相关爬虫专栏:JS逆向爬虫实战  爬虫知识点合集  爬虫实战案例 逆向知识点合集


前言:

上篇文章我们详细对AST做了理解与剖析,但主要还是停留在纸面上。这期我们会真正深入到AST内部,通过真正的代码拆解转换来感受它的使用方式。

一、理解AST调试网站

AST explorer我们首先要用到这个网页:AST explorer 

这是个非常重要的网站,在解析混淆代码时我们常会用到它。先把这个网页结构大致浏览:

上层: 一般保持该默认设置即可

AST Explorer工具名称,即「抽象语法树浏览器」。
Snippet当前编辑区内容被标记为「代码片段」,你可以在这里输入任意 JS 代码。
JavaScript当前解析的语言是 JavaScript
@babel右侧下拉框选中的解析器是 Babel(默认用 @babel/parser 来生成 AST)。
default表示没有加载额外插件或自定义配置,用的是默认解析规则。

右侧:

Tree 是当前语法树结构,会表现出当前代码的各种参数。其中最重要的就是body,我们之后几乎所有ast操作都是针对body的。

 Tree的JSON形式则无法调试而且也不太直观,我们一般不看它。

大致理解了网页构造后,我们再来实际感受下:

我们先在左侧写一个var a = 10; 并试着选中10,看右边:很明显,选中的部分会被标黄,那我们就知道如果想对这个10做操作,选中这个NumericLiteral即可。同理,var/a/=也一样。

二、AST环境与基础代码

首先先配置相关环境:

安装命令:
npm i @babel/core --save-dev //Babel 编译器本身,提供了 babel 的编译 API;

npm i @babel/types //判断节点类型,构建新的AST节点等
npm i @babel/parser //将Javascript代码解析成AST语法树
npm i @babel/traverse //遍历,修改AST语法树的各个节点
npm i @babel/generator //将AST还原成Javascript代码

配置完成后,我们创建三个js文件,分别是main.js,encode.js,decode.js,其中main.js放主要ast代码,encode.js放混淆的代码,decode.js则作为将要被反混淆放入的代码,

main.js如下:

//main.js// fs模块 用于操作文件的读写
const fs = require("fs");
// @babel/parser 用于将JavaScript代码转换为ast树
const parser = require("@babel/parser");
// @babel/traverse 用于遍历各个节点的函数
const traverse = require("@babel/traverse").default;
// @babel/types 节点的类型判断及构造等操作
const types = require("@babel/types");
// @babel/generator 将处理完毕的AST转换成JavaScript源代码
const generator = require("@babel/generator").default;// 混淆的js代码文件
const encode_file = "./encode.js"
// 反混淆的js代码文件
const decode_file = "./decode.js"// 读取混淆的js文件
let jsCode = fs.readFileSync(encode_file, {encoding: "utf-8"});
// 将javascript代码转换为ast树
let ast = parser.parse(jsCode)// todo 编写ast插件
const visitor = {}// 调用插件,处理混淆的代码
traverse(ast,visitor)// 将处理后的ast转换为js代码(反混淆后的代码)
let {code} = generator(ast);
// 保存代码
fs.writeFile('decode.js', code, (err)=>{});

其中划出我们的最重点:

const visitor = {} 这里面的{},作为ast插件就是主要需要填写的地方。调用这个插件才会对那些混淆代码造成影响,变成另一种代码形式。

另外,

  • traverse 用于遍历和转换抽象语法树(AST)的工具,转换语法树需要配置visitor使用
  • visitor 是一个对象,里面可以定义一些方法,用来过滤节点

如上,环境与基础代码搭建后,我们接下来进行实战演示。

三、AST反混淆的属性

1. 写插件

在encode.js里写如下代码:

console.log('曼波!');
var a = 10;

再去AST exploerer也把这个复制下去,选中console.log('曼波!');看右边的标黄:

很明显,对象是ExpressionStatement(var a =10;对应的是下面的variable),所以我们对这个做操作即可。

进main.js写ast插件:

const visitor = {ExpressionStatement(path){console.log("ast反混淆ing......")}
}

运行程序:没有问题。

同理,后面再遇到任何想要变化的代码,按如上操作即可。于vistior={}中写 插件(path){ 操作 } 的格式即可。其中path代表当前正在遍历的节点路径,借助这个path我们就能访问和操作相关节点的属性与关系。

2. path常用属性

const visitor = {ExpressionStatement(path){console.log('当前节点对象:',path.node);console.log('节点对象类型:',path.type);console.log('节点源码:',path.toString());}
}

如图,path.node就是当前节点对象的语法树,path.type为类型,path.toString()为当前源码。

3.enter与exit

const visitor = {ExpressionStatement:{enter(path,state){console.log('开始曼波!')},exit(){console.log('结束曼波!')}}
}

顾名思义,enter与exit会分别在进入与离开当前节点对象时运行,可以在此处编写出入时想要进行的操作。

4. 多个函数处理一个节点

写个enter,并让它接受一个函数数组即可。

//encode.js
var a = 10;
function func(){console.log('曼波!');
}
function f1(){console.log('f1曼波!')
}
function f2(){console.log('f2曼波!')
}
const visitor = {"FunctionDeclaration":{enter:[f1,f2]}
}

5. 一个函数处理多个节点

const visitor = {"ExpressionStatement|VariableDeclaration":{enter(path,state){console.log('开始曼波')},exit(){console.log('结束曼波')}}
}

6. 将所有函数的首个参数改为o

//encode.js
var a = 10;
function func1(param1,param2){console.log('function1!');
}
function func2(a1,a2){console.log('function2!');
}
//ast插件B,用于修改函数参数名
const updateParamNameVisitor = {//Identifier表示被遍历节点的标识符(函数参数)Identifier(path){if(path.node.name === this.paramName){console.log(path.node) //当前节点就是函数参数path.node.name = "o"}}
}
//ast插件,假设命名为插件A
const visitor = {//指定需要遍历的节点类型为函数FunctionDeclaration(path){//获取被遍历函数的第一个参数const paramName = path.node.params[0].name;//调用traverse对函数节点向下遍历,修改函数的第一个参数//此处path就特指了被遍历的函数节点的路径(调用插件B-》updateParamNameVisitor)path.traverse(updateParamNameVisitor,{paramName:paramName})}
}
// 调用插件A,处理js代码
traverse(ast,visitor)

在decode.js中可见参数已改为o

7. 判断节点类型(将某个标识符统一改为另一个)

//encode.jsvar a = 10;
function a(a,num2){return a + num2;
};
console.log(a());
const visitor = {enter(path){//在js代码中定位到所有标识符为a(变量名为a、函数名为a等)的节点,将其名字改为bif(types.isIdentifier(path.node,{"name":"a"})){path.node.name = "b";}}
}
// 调用插件,处理js代码
traverse(ast,visitor)
// decode.js
var b = 10;
function b(b, num2) {return b + num2;
}
;
console.log(b());

8. 替换节点属性值

var a = 10 ; 的情况下:

// todo 编写ast插件
const visitor = {VariableDeclarator(path){//修改为数字类型path.node.init = types.numericLiteral(123123)}
}
traverse(ast,visitor)

9. 替换节点

节点替换节点 (replaceWith):

// 原: var a = 1; var b = 1;// todo 编写ast插件
const visitor = {NumericLiteral(path){//修改为字符串类型	path.replaceWith(types.valueToNode("123321"))}
}
/*
* 替换后的代码:var a = "123321";var b = "123321";
* */
traverse(ast,visitor)

字符串源码替换节点 (replaceWithSourceString)

// todo 编写ast插件
const visitor = {NumericLiteral(path){path.replaceWithSourceString(`function add(a,b){return a + b}`)}
}
traverse(ast,visitor)
/*
* 替换结果:
var a = function add(a, b) {return a + b;
};
* */

10. 删除节点

const visitor = {VariableDeclarator(path){path.remove();}
}

四、AST反混淆实战

1. 实战1: 

var b = 1 + 2;
var c = "coo" + "kie";
var a = 1+1,b = 2+2;
var c = 3;
var d = "1" + 1;
var e = 1 + '2';

将其变成如下形式: 思路--表达式节点遍历,之后提取表达式元素,计算后替换

var b = 3;
var c = "cookie";
var a = 2,b = 4;
var c = 3;
var d = "11";
var e = "12";

代码实现:(其中为了便于理解拆分成了很多种可能形式,但其处理方式实际上就是

value = left.value + right.value这一种即可)

const visitor = {//遍历表达式节点BinaryExpression(path){// 取出表达式的各个元素:1 + 2var {left, operator, right} = path.node// 数字相加处理if (types.isNumericLiteral(left) && types.isNumericLiteral(right) && operator == "+") {value = left.value + right.value// 将原来的节点当中的原来的值进行替换path.replaceWith(types.valueToNode(value))}//字符串相加if (types.isStringLiteral(left) && types.isStringLiteral(right) && operator == "+") {value = left.value + right.value// 将原来的节点当中的原来的值进行替换path.replaceWith(types.valueToNode(value))}if (types.isStringLiteral(left) && types.isNumericLiteral(right) && operator== "+" || types.isNumericLiteral(left) && types.isStringLiteral(right)) {value = left.value + right.value// 将原来的节点当中的原来的值进行替换path.replaceWith(types.valueToNode(value))}}
}

2. 实战2:

// 处理前
var arr = '3,4,0,5,1,2'['split'](',')// 处理后
var arr = ["3", "4", "0", "5", "1", "2"]

目标:将上方代码变为下方形式

代码实现:

const visitor = {//遍历函数调用节点(split函数)CallExpression(path) {//获取函数调用节点的调用者callee和函数参数argumentslet {callee, arguments} = path.node// 通过打印节点的树结构决定访问哪些属性//console.log(callee.object.value,arguments[0].value)let data = callee.object.value //获取split函数调用者let func = callee.property.value //获取函数名let arg = arguments[0].value //获取split函数参数var res = data[func](arg) //调用函数获取返回值//用于替换当前节点path.replaceWith(types.valueToNode(res))}
}

实战3:

//处理前:
var a = 0x25,b = 0b10001001,c = 0o123456,
d = "\x68\x65\x6c\x6c\x6f\x2c\x41\x53\x54",
e = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";//处理后:
var a = 37,b = 137,c = 42798,d = "hello,AST",e = "hello,AST"

目标:上变下

思路:进AST explorer网站,将上面代码放进去并选中右边的进制,看圈黄范围:其中extra的raw是涵盖进制数据的,所以我们可以直接为其设置undefined,这样可以只采用最下方的value,以达到简化目的。

代码实现:

const visitor = {NumericLiteral({node}) {//如果节点存在extra属性且raw是以0o、0b或者0x开头的//i表示不区分大小写匹配,意味着在匹配时忽略字符的大小写差异。if (node.extra && /^0[obx]/i.test(node.extra.raw)) {//移除了数字字面量节点的编码类型信息。node.extra = undefined;}},StringLiteral({node}) {//如果节点存在extra属性且raw是以\u或者\x//g 表示全局匹配,意味着在整个字符串中查找所有匹配项,而不仅仅是找到第一个匹配就停止。if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {//移除了数字字面量节点的编码类型信息。node.extra = undefined;}},
}

五、小结

对于AST混淆的代码,哪怕有个几万行也不用怕。先观察它有多少种混淆,再对这些混淆逐一写上专门的处理规则即可。因为混淆方法不特定,所以我们没有办法写一个通用的混淆框架解,只能分析代码并写上专门的处理规则。

http://www.dtcms.com/a/336499.html

相关文章:

  • 2001-2024年中国玉米种植分布数据集
  • Cesium学习(二)-地形可视化处理
  • AutoSar BSW介绍
  • PyTorch 面试题及详细答案120题(01-05)-- 基础概念与安装
  • 全星质量管理 QMS:驱动制造业高质量发展的核心工具
  • 雷卯针对香橙派Orange Pi 5 Ultra开发板防雷防静电方案
  • Java研学-SpringCloud(五)
  • 如何理解“速度模式间接实现收放卷恒张力控制“
  • 题目2:使用递归CTE分析产品层级关系
  • 【从零开始学习Redis】项目实战-黑马点评D2
  • 【会议跟踪】ICRA 2021 Workshop:Visual-Inertial Navigation Systems
  • 多线程—飞机大战(加入播放音乐功能版本)
  • 【Virtual Globe 渲染技术笔记】6 着色
  • C语言---第一个C语言程序
  • Tomcat下载、安装及配置详细教程
  • Hybrid Beamforming Design for OFDM Dual-Function Radar-Communication System
  • LaTeX中表示实数集R的方法
  • 零基础搭建公网 Nginx:通过 cpolar 内网穿透服务实现远程访问
  • 朝花夕拾(四) --------python中的os库全指南
  • 【计算机数学】关于全概率和贝叶斯公式的使用场景说明
  • Linux目录相关的命令
  • 排列组合+数量+资料
  • 聊聊Vuex vs Pinia
  • MySQL执行计划解读
  • 人脸AI半球梯控/门禁读头的功能参数与技术实现方案
  • 网络常识-DNS如何解析
  • 集成运算放大器(反向加法,减法)
  • Linux Shell定时检查日期执行Python脚本
  • 【AIGC】DDPM scheduler解析:扩散模型里的“调度器”到底在调什么?
  • 线程的同步