AST原理(反混淆)

一、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 特性)编写代码:

javascript 复制代码
const add = (a, b) => a + b;

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

javascript 复制代码
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 节点等。
JavaScript 复制代码
// 安装命令
npm install @babel/parser --save-dev

二、AST语法学习

javascript 复制代码
//练习语法
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 节点等。
JavaScript 复制代码
// 安装命令
npm install @babel/parser --save-dev
1. parser库使用
  • 将JavaScript源代码 转换成一棵 AST 树、返回结果(在这里赋值给 ast )是一个 JSON 结构的数据
JavaScript 复制代码
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 和访问器对象。
JavaScript 复制代码
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 代码片段,我们想要转换箭头函数为普通函数表达式,但只针对在对象字面量中作为属性值的箭头函数:

      javascript 复制代码
      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 作为 body
            const functionExpression = t.functionExpression(
              null, // id
              path.node.params, // params
              blockStatement, // body
              false, // generator
              false // 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"]

JavaScript 复制代码
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.node
        let data = callee.object.value
        let func = callee.property.value
        let arg = arguments[0].value
        var 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/
相关推荐
m0_547486661 天前
《信息安全基础》全套PPT课件2026
网络安全·信息安全
m0_715674431 天前
数据分类分级的范式转换:从规则匹配到场景化高准确率一键部署
大数据·网络·人工智能·网络安全
白帽攻防录2 天前
SRC 挖洞:Apache APISIX 非规范路径授权绕过深度复盘,CVE-2026-94269 怎么绕过网关访问后端
网络安全·apache·src漏洞挖掘
学逆向的2 天前
win32注入代码
开发语言·网络安全·api·win32
Web Security Loop2 天前
MAC安装最新Burpsuite pro详细步骤教程
web安全·macos·网络安全·安全性测试
墨云安全2 天前
红队评估和渗透测试的区别是什么?2026 年从目标、方法到交付物的对比
网络·安全·web安全·网络安全·渗透测试
刘婉晴2 天前
【APP安全测试】Frida 基础
网络安全
科技象限3 天前
数据防泄密系统怎么选?看它怎么把几道防线叠成网
运维·安全·网络安全·安企神
sbjdhjd3 天前
云安全 | 从特权容器到旧版 runC:Docker 宿主机访问与运行时缺陷复盘
运维·网络安全·docker·云原生·kubernetes·云计算·云安全
sbjdhjd3 天前
云安全 | Docker 容器逃逸复盘(三):docker.sock 挂载与 Docker-in-Docker 风险
经验分享·网络安全·docker·云原生·容器·kubernetes·云安全