AST原理(反混淆)

server/2024/11/17 7:20:23/
一、AST原理

在这里插入图片描述

jscode = 'var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";'

在上述代码中,a 是一个变量,它被赋值为一个由 Unicode 转义序列组成的字符串。Unicode 转义序列在 JavaScript 中以 \u 开头,后跟四个十六进制数字,表示一个 Unicode 字符。

这里的字符串 "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054" 实际上是使用 Unicode 转义序列表示的 "hello,AST"。每个 \uXXXX 表示一个字符:

  • \u0068 -> “h”
  • \u0065 -> “e”
  • \u006c -> “l”
  • \u006c -> “l”
  • \u006f -> “o”
  • \u002c -> “,”
  • \u0041 -> “A”
  • \u0053 -> “S”
  • \u0054 -> “T”

所以,变量 a 的值是 "hello,AST"

AST(Abstract Syntax Tree,抽象语法树)是源代码的抽象语法结构的树状表示形式。在 JavaScript 中,AST 会将代码分解成树中的节点,每个节点代表代码中的一个构造(如变量声明、字面量、表达式等)。

如果我们将上述代码转换为 AST,它将包含以下主要节点:

  1. VariableDeclaration:表示变量声明的节点。
    在这里插入图片描述

    里面是函数体的内容
  2. VariableDeclarator:表示声明中的变量和赋值的节点。包含起始位置和结束位置

  3. Identifier:表示变量名 a 的节点。
    在这里插入图片描述

  4. Literal:表示字符串字面量的节点,其值为 "hello,AST"
    在这里插入图片描述

使用 AST 技术,我们可以分析、遍历、修改或解释代码的结构。例如,代码编辑器、编译器和代码转换工具(如 Babel)会使用 AST 来理解和操作源代码。

准备需要替换的JS代码
// 解析js代码  会把js源码转换成ast语法树,返回的结果是json的结构的数据
const parse = require('@babel/parser')
// 在lxml相当于是xpath
// 编写节点和进行转换
const traverse = require('@babel/traverse').default// 准备需要转换的js代码jscode = 'var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";'var ast = parse.parse(jscode);// 传递两个参数(ast语法数据, 访问器对象)
//遍历、修改 AST 语法树的各个节点
traverse(ast, {
//     根据类别定位标签  , path 是定位之后的地址VariableDeclarator(path){console.log(path.parentPath)}
})
Babel库应用场景

Babel 库的应用场景主要涉及以下几个方面:

  1. 跨浏览器兼容性

    • 现代 JavaScript(如 ES6/ES2015 及更高版本)引入了许多新特性,如箭头函数、类、模块、模板字符串、默认参数等。
    • 不是所有浏览器都支持这些新特性,尤其是旧版本的浏览器(如 Internet Explorer)。
    • Babel 可以将这些现代 JavaScript 代码转换成旧版本的浏览器也能理解和执行的 ES5 代码。
  2. 开发新特性

    • 开发者可以使用最新的 JavaScript 特性来编写代码,提高开发效率和代码质量。
    • Babel 会处理新语法和提案中的特性,让开发者不必等到所有用户的浏览器都支持这些新特性。
  3. 工具链集成

    • Babel 可以集成到现代前端工具链中,如 Webpack、Rollup、Gulp 等。
    • 它可以作为构建过程中的一步,自动编译所有 JavaScript 文件。
  4. 插件和预设

    • Babel 提供了插件系统,允许开发者自定义转换规则。
    • 预设(presets)是一组插件的集合,可以轻松地为特定目的配置 Babel。
  5. 代码优化

    • Babel 插件可以用于代码优化,如移除开发中的代码、简化代码结构等。
  6. 支持 TypeScript、Flow

    • Babel 可以转换 TypeScript 或 Flow 注解的代码,使其成为普通的 JavaScript 代码。

举例说明:

假设您正在使用箭头函数(一种 ES6 特性)编写代码:

const add = (a, b) => a + b;

在旧版本的浏览器中,箭头函数可能不被支持。Babel 可以将上述代码转换为:

var add = function(a, b) {return a + b;
};

这样,即使是不支持 ES6 的旧浏览器也可以正确执行这段代码。

当我们说 Babel 允许代码“运行在当前和旧版本的浏览器或其他环境中”时,意思是 Babel 生成的代码不仅可以在支持最新 JavaScript 特性的最新浏览器中运行,也可以在那些只支持旧 JavaScript 版本的旧浏览器中运行。此外,其他环境可能包括 Node.js、Electron 或任何 JavaScript 引擎。这使得开发者可以编写最新和最优雅的代码,同时确保它在尽可能多的环境中都能工作。

Babel库学习

​ 根据官网介绍,它是一个JavaScript 编译器,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容 的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

  1. @babel/core :Babel 编译器本身,提供了 babel 的编译 API;
  2. @babel/parser :将 JavaScript 代码解析成 AST 语法树;
  3. @babel/traverse :遍历、修改 AST 语法树的各个节点;
  4. @babel/generator :将 AST 还原成 JavaScript 代码;
  5. @babel/types :判断、验证节点的类型、构建新 AST 节点等。
// 安装命令
npm install @babel/parser --save-dev

二、AST语法学习

  • 参考地址:https://www.babeljs.cn/docs/
  • 在线解析:https://astexplorer.net
//练习语法
var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054"
1. AST输出树结构
  1. type: 表示当前节点的类型,我们常用的类型判断方法,就是判断当前的节点是否为某个类型。
  2. start: 表示当前节点的起始位。
  3. end: 表示当前节点的末尾。
  4. loc : 表示当前节点所在的行列位置,里面也有start与end节点,这里的start与上面的start是不同 的,这里的start是表示节点所在起始的行列位置,而end表示的是节点所在末尾的行列位置。
  5. errors:是File节点所特有的属性,可以不用理会。
  6. program:包含整个源代码,不包含注释节点。
    1. sourceType: 通常用于标识源代码的类型,以告诉解析器或编译器它正在处理的代码是模块代码还是脚本代码(Script, Module)
    2. body:包含了程序的主体代码,即程序的主要逻辑。
      1. 语句块:“body” 可能表示一组语句,通常是一个代码块,这些语句按顺序执行。
      2. 函数体:对于函数或方法定义,“body” 包含了函数的主体代码,即函数内部的语句和逻辑。
      3. 类定义:对于类定义,“body” 可能包含类的成员,如属性和方法。
      4. 模块内容:对于模块或文件,“body” 可能包含文件中的顶级语句和声明。
      5. declarations:通常用于表示变量、常量、函数、类等的声明
      6. id:是函数,变量,类的名称
      7. init: 通常代表声明的初始化值
  7. comments:源代码中所有的注释会在这里显示。
2.常见节点类型

在这里插入图片描述

3.babel库学习

​ 根据官网介绍,它是一个JavaScript 编译器,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容 的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

  1. @babel/core :Babel 编译器本身,提供了 babel 的编译 API;
  2. @babel/parser :将 JavaScript 代码解析成 AST 语法树;
  3. @babel/traverse :遍历、修改 AST 语法树的各个节点;
  4. @babel/generator :将 AST 还原成 JavaScript 代码;
  5. @babel/types :判断、验证节点的类型、构建新 AST 节点等。
// 安装命令
npm install @babel/parser --save-dev
1. parser库使用
  • 将JavaScript源代码 转换成一棵 AST 树、返回结果(在这里赋值给 ast )是一个 JSON 结构的数据
const parse = require('@babel/parser')
// JS 转 ast语法树
jscode = `var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";`
let ast = parse.parse(jscode);
console.log(JSON.stringify(ast,null,'\t'))
2. traverse 库学习
  • 节点插件编写与节点转化
  • 你可以使用 traverse 函数来遍历AST。通常,你需要提供两个参数:AST 和访问器对象。
const parse = require('@babel/parser')
const traverse = require('@babel/traverse').default
// JS 转 ast语法树
jscode = `var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";`
// 转换js代码为ast树结构
let ast = parse.parse(jscode);// 用查找定位节点(ast结构树, 访问器对象)
traverse(ast, {// 定位VariableDeclarator类别,path是定位之后的地址VariableDeclarator(path){console.log('Found identifier:', path.node.init.value);}
})
1. path属性语法学习
var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";

path.node :表示当前path下的node节点

path.toString() :当前路径所对应的源代码

path.parentPath :用于获取当前path下的父path,多用于判断节点类型

  • 解析:VariableDeclarator 是一个访问者方法(visitor method),它会在遍历到每个 VariableDeclarator 类型的 AST 节点时被调用。

  • 作用:在反混淆的过程中,需要根据父节点的类型决定如何处理当前节点。例如,如果一个混淆的字符串字面量Literal是函数调用的一部分,那么可能需要以不同的方式处理它。

    • 举例说明:假设我们有以下 ES6 代码片段,我们想要转换箭头函数为普通函数表达式,但只针对在对象字面量中作为属性值的箭头函数:

      const obj = {greet: () => console.log("Hello, world!"),
      };const standaloneGreet = () => console.log("Standalone Hello, world!");
      

      在这个例子中,我们只想转换 obj.greet 中的箭头函数,而不转换 standaloneGreet

      const parser = require('@babel/parser');
      const traverse = require('@babel/traverse').default;
      const t = require('@babel/types');
      const generator = require('@babel/generator').default;
      // 假设的 JavaScript 代码
      const code = `
      const obj = {greet: () => console.log("Hello, world!"),
      };const standaloneGreet = () => console.log("Standalone Hello, world!");
      `;// 解析 JavaScript 代码生成 AST
      const ast = parser.parse(code, {sourceType: 'module', // 依据代码类型选择 "script" 或 "module"
      });traverse(ast, {ArrowFunctionExpression(path) {if (path.parentPath.isObjectProperty()) {// 创建一个 BlockStatement,将原来的表达式包装在 ExpressionStatement 中const blockStatement = t.blockStatement([t.expressionStatement(path.node.body)]);// 创建函数表达式,使用上面创建的 blockStatement 作为 bodyconst functionExpression = t.functionExpression(null, // idpath.node.params, // paramsblockStatement, // bodyfalse, // generatorfalse // async);path.replaceWith(functionExpression);}}
      });// 生成转换后的代码
      const output = generator(ast, { /* options */ }, code);
      // 输出转换后的代码
      console.log(output.code);
      

在这里插入图片描述

  • 在某些情况下,混淆可能会改变代码的结构。通过检查父节点,开发者可以决定是否需要重建代码的某些部分以恢复其原始意图。

    • 分析父节点:通过分析父节点,可以获取更多关于当前节点的信息,比如它是一个独立的变量声明还是一个变量声明列表的一部分。
    • 执行进一步操作:有时候需要基于父节点的信息来决定如何操作当前节点,例如你可能只想修改某个特定函数中的变量声明。
      在这里插入图片描述
  • path.container :用于获取当前path下的所有兄弟节点(包括自身)

  • path.type :获取当前节点类型

  • path.get('') :获取path的子路径,取值的方式有点像Xpath

const parse = require('@babel/parser')
const traverse = require('@babel/traverse').default
// JS 转 ast语法树
jscode = `var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";
var a = "1111";`
// 转换js代码为ast树结构
let ast = parse.parse(jscode);// 用查找定位节点(ast结构树, 访问器对象)
traverse(ast, {VariableDeclarator(path){// console.log(path.node); // 表示当前path下的node节点// console.log(path.type) // 获取当前节点类型// console.log(path.toString()); // 用来获取当前遍历path的js源代码// console.log(path.parentPath.node); //用于获取当前path下的父path,多用于判断节点类型// console.log(path.get('init').toString()); // 获取下面的节点// console.log(path.container); // 用于获取当前path下的所有兄弟节点(包括自身)// 只获取一个数据console.log(path.node.init.value);// 找到第一个后,可以停止遍历// path.stop();}
})
2. 替换原有节点
  • path.replaceWith :(单)节点替换函数
    • 还原数字相加: 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’;
const parse = require('@babel/parser')
const traverse = require('@babel/traverse').default
const types = require('@babel/types')
const generator = require("@babel/generator").default;// JS 转 ast语法树
jscode = `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';
`
// 转换js代码为ast树结构
let ast = parse.parse(jscode);// 用查找定位节点(ast结构树, 访问器对象)
traverse(ast, {BinaryExpression(path) {// 取出数组数据的单独对象var {left, operator, right} = path.node// 数字相加处理if (types.isNumericLiteral(left) && types.isNumericLiteral(right) && operator == "+" || types.isStringLiteral(left) && types.isStringLiteral(right)) {value = left.value + right.value// console.log(value);// 会把原来的节点当中的原来的值进行替换path.replaceWith(types.valueToNode(value))// console.log(path.parentPath.node)}if (types.isStringLiteral(left) && types.isStringLiteral(right) && operator == "+") {value = left.value + right.value// console.log(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// console.log(value);// 会把原来的节点当中的原来的值进行替换path.replaceWith(types.valueToNode(value))}}
})
// 将ast还原成JavaScript代码
let {code} = generator(ast);
console.log(code)
  • replaceWithMultiple 多节点替换函数,调用方式

path.replaceWithMultiple(ArrayNode);

  • 实参一般是 Array 类型,它只能用于 Array 的替换。
  • 即所有需要替换的节点在一个Array里面 举例:对如下变量进行处理
替换前:var arr = '3,4,0,5,1,2'['split'](',')
替换后:var arr = ["3", "4", "0", "5", "1", "2"]
const generator = require("@babel/generator").default;
const parse = require('@babel/parser')
const traverse = require('@babel/traverse').default
const types = require('@babel/types')
// JS 转 ast语法树
jscode = `
var arr = '3,4,0,5,1,2'['split'](',')
`
// 转换js代码为ast树结构
let ast = parse.parse(jscode);traverse(ast, {CallExpression(path) {let {callee, arguments} = path.nodelet data = callee.object.valuelet func = callee.property.valuelet arg = arguments[0].valuevar res = data[func](arg)path.replaceWithMultiple(types.valueToNode(res))}
})// 将ast还原成JavaScript代码
let {code} = generator(ast);
console.log(code)
3. 自执行方法还原
!(function () {console.log('123')
})
JS反混淆工具分享
https://tool.yuanrenxue.cn/

http://www.ppmy.cn/server/32471.html

相关文章

亿图图示使用教程

亿图图示是一款功能强大的图形绘制软件,可以用来创建各种类型的图表、流程图、思维导图等。本教程将介绍亿图图示的基本使用方法和一些高级功能。 一、安装亿图图示 下载亿图图示安装包:在亿图图示官网(www.edrawsoft.cn)下载安…

恶补《操作系统》4_2——王道学习笔记

4.1_5 文件存储空间管理 1、存储空间的划分与初始化 文件卷(逻辑卷)的概念目录区与文件区 2、几种管理方法 空闲表法:首位置长度,回收时注意修改空闲链表法(空闲盘块链、空闲盘区链)位示图法 成组链接法…

中文NLP一般流程

一.获取语料(数据信息) 语料:一种语言学研究的内容。语料是构成语料库的最基本单元。所以,人们简单地用文本进行表示, 并把文本中的上下文关系作为现实世界中语言的上下文关系的替代品。通常将一个文本集合称为语料库(Corpus),当有几个这样的…

初学python记录:力扣1235. 规划兼职工作

题目: 你打算利用空闲时间来做兼职工作赚些零花钱。 这里有 n 份兼职工作,每份工作预计从 startTime[i] 开始到 endTime[i] 结束,报酬为 profit[i]。 给你一份兼职工作表,包含开始时间 startTime,结束时间 endTime …

c#使用Elastic.Clients.Elasticsearch 库进行ElasticSearch的增删改查操作,根据变量动态构建查询条件。

实体类Shop结构: public class Shop {public string UUID { set; get; }public string ItemType { set; get; }public long ItemId { set; get; }public string ItemName { set; get; }public long Gold { set; get; }public long Number { set; get; }public string Data { s…

14_Scala面向对象编程_属性

属性 1.类中属性声明 // 1.给Scala声明属性;var name :String "zhangsan"val age :Int 302.系统默认赋值 scala由于初始化变量必须赋值,为了解决此问题可以采用下划线赋值,表示系统默认赋值 , –但是此方法局限于变量&…

js方法返回类型

在JavaScript中,方法可以是以下几种形式: 1. **函数声明**:传统的函数声明方式。 javascript function greet() { console.log(Hello, World!); } 2. **函数表达式**:使用变量赋值的方式定义函数。 javascript const greet…

基于Springboot的旅游管理系统(有报告)。Javaee项目,springboot项目。

演示视频: 基于Springboot的旅游管理系统(有报告)。Javaee项目,springboot项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构&…