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/
相关推荐
知孤云出岫2 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web
mushangqiujin2 小时前
ctfshow web文件上传 web166-170
网络安全
Suckerbin5 小时前
Hms?: 1渗透测试
学习·安全·网络安全
newxtc6 小时前
【国内中间件厂商排名及四大中间件对比分析】
安全·web安全·网络安全·中间件·行为验证·国产中间件
follycat11 小时前
[极客大挑战 2019]HTTP 1
网络·网络协议·http·网络安全
Lionhacker1 天前
网络工程师这个行业可以一直干到退休吗?
网络·数据库·网络安全·黑客·黑客技术
centos081 天前
PWN(栈溢出漏洞)-原创小白超详细[Jarvis-level0]
网络安全·二进制·pwn·ctf
程序员小予1 天前
如何成为一名黑客?小白必学的12个基本步骤
计算机网络·安全·网络安全
蜗牛学苑_武汉1 天前
Wazuh入侵检测系统的安装和基本使用
网络·网络安全