深入学习 JavaScript 转译器 Babel ,AST还原混淆代码

介绍

JavaScript 和 Babel 简介

  1. JavaScript 是一种广泛应用于 Web 开发的脚本语言,它最初是由网景公司的 Brendan Eich 开发的。JavaScript 具有易学易用、灵活、动态等特点,它能够帮助开发者在 Web 应用中实现丰富的交互和动态效果。

  2. 然而,由于 JavaScript 的语法和特性不断更新,旧版的浏览器和环境可能无法完全支持新的 JavaScript 特性,这就使得开发者在编写 JavaScript 代码时需要考虑兼容性问题。为了解决这个问题,Babel 应运而生。

  3. Babel 是一个 JavaScript 编译器,它可以将最新版本的 JavaScript 代码转换成向下兼容的代码,以便旧版的浏览器和环境可以运行。Babel 的转译过程是基于 AST(抽象语法树)的,它可以通过分析 AST 来理解源代码的结构和含义,然后进行相应的转换。

  4. Babel 的使用非常灵活,可以通过命令行、Webpack、Rollup 等多种方式来集成到项目中。开发者可以根据自己的需求来配置 Babel,选择需要的插件和预设,从而实现对不同版本的 JavaScript 代码的转译。同时,Babel 还提供了许多插件和预设,可以实现更加复杂的转译和扩展功能,例如转译 JSX、ES6 模块化、装饰器等。

  5. 总之,Babel 是一个非常重要的 JavaScript 工具,它可以帮助开发者更加轻松地编写和维护兼容性更好的 JavaScript 代码,同时也为 JavaScript 社区的发展做出了重要贡献。

Babel 的历史和发展

  1. Babel 诞生于 2014 年,最初的名字是 6to5,它的初衷是为了解决 JavaScript 新特性向下兼容的问题。当时,ES6 标准已经发布,但是很多浏览器和环境并不支持 ES6,这就使得开发者无法充分利用新特性。

  2. 6to5 利用了新的 JavaScript 特性,如箭头函数、模板字符串等,将其转换为旧的 ES5 代码,以便兼容性更广的浏览器和环境也能够运行。6to5 很快获得了广泛的关注和认可,它的开发者们决定将其更名为 Babel,并将其转变为一个更加通用的 JavaScript 编译器。

  3. 随着时间的推移,Babel 逐渐发展成为一个功能强大、灵活易用的 JavaScript 工具。除了对新特性的转译,Babel 还可以处理 TypeScript、Flow、JSX 等不同的 JavaScript 方言。同时,Babel 的插件系统也逐渐完善,开发者们可以自己编写插件来定制转译过程,或者使用现成的插件来实现更加复杂的功能。

  4. 2015 年,Babel 正式发布了 6.0 版本,这个版本带来了很多重要的变化,其中最显著的是支持了 ES6 的新语法和特性,这使得开发者可以更加方便地使用 ES6 来编写 JavaScript 代码。此后,Babel 还不断更新迭代,推出了支持 ES7、ES8 等新版本的 JavaScript 标准的转译,并增加了对一些实验性的 JavaScript 特性的支持。

  5. 目前,Babel 已经成为了 JavaScript 社区中最流行、最重要的工具之一,它对 JavaScript 的发展和演进做出了重要贡献。

Babel 的优势和应用场景

  1. 帮助解决兼容性问题:Babel 可以将最新版本的 JavaScript 代码转换为向下兼容的代码,以便旧版的浏览器和环境可以运行。这就帮助开发者解决了兼容性问题,使得开发者可以更加放心地使用最新的 JavaScript 特性来编写代码。

  2. 提高开发效率:Babel 可以自动化地将新版本的 JavaScript 代码转换为向下兼容的代码,使得开发者可以更加专注于编写高质量的代码,而不需要考虑兼容性问题。这就大大提高了开发效率。

  3. 支持多种 JavaScript 方言:除了处理标准的 JavaScript 代码,Babel 还可以处理 TypeScript、Flow、JSX 等不同的 JavaScript 方言。这就使得 Babel 成为了一个通用的 JavaScript 工具,能够应对各种不同的应用场景。

  4. 插件系统功能强大:Babel 的插件系统非常灵活,可以自定义转译规则,也可以添加新的语法和特性。这就使得开发者可以根据自己的需求来配置 Babel,选择需要的插件和预设,实现对不同版本的 JavaScript 代码的转译。

基于以上优势,Babel 的应用场景非常广泛,下面列举几个典型的应用场景:

  1. 在前端开发中,Babel 可以帮助开发者更加方便地使用最新的 JavaScript 特性来编写代码,同时保证兼容性。例如,在使用 React 开发 Web 应用时,Babel 可以将 JSX 转换为 JavaScript 代码。

  2. 在 Node.js 开发中,Babel 可以帮助开发者使用最新的 JavaScript 特性和方言,例如使用 TypeScript 来编写 Node.js 应用程序。同时,Babel 还可以将 Node.js 应用程序打包成支持多种环境的代码。

  3. 在开发工具中,Babel 可以作为编译器使用,例如将 JavaScript 代码转换为 ES5 或 ES6 代码。同时,Babel 还可以作为 Webpack 等构建工具的插件,用于处理 JavaScript 代码的转译和打包。

Babel 的原理和工作流程

Babel 的原理主要分为三个步骤:

  1. 解析(Parsing):Babel 首先会将 JavaScript 代码解析成抽象语法树(AST)。这个过程可以通过使用 Babylon 解析器或者其他支持解析 JavaScript 代码的解析器来完成。解析的结果是一个包含 JavaScript 代码的 AST。

  2. 转换(Transformation):在转换阶段,Babel 将会遍历 AST,对 AST 中的节点进行修改或者删除,并生成新的 AST。这个过程中,可以使用 Babel 插件来添加新的语法或者修改现有的语法。例如,可以使用 @babel/plugin-transform-arrow-functions 插件将 ES6 的箭头函数转换为 ES5 的函数表达式。

  3. 生成(Code Generation):在代码生成阶段,Babel 会将生成的新 AST 转换回 JavaScript 代码,并输出到指定的文件中。这个过程中,Babel 会根据需要进行缩进和格式化,并且会将新生成的 JavaScript 代码与原始代码进行比较,以便调试和测试。

安装babel模块

逆向解混淆,主要用到 Babel 的以下几个功能包,本文也仅介绍以下几个功能包:

  1. 安装 Babel:运行以下命令安装 Babel 及其相关模块:这里安装了 @babel/core@babel/cli 两个模块,@babel/core 是 Babel 工具链的核心模块,@babel/cli 是 Babel 命令行工具,可以在命令行中直接使用 Babel 进行代码转译等操作。
npm install @babel/core @babel/cli --save-dev
  1. 安装 Babel 解析器:Babel 使用解析器将 JavaScript 代码转换为抽象语法树(AST),以便进行后续的转换操作。运行以下命令安装 Babel 解析器:
npm install @babel/parser --save-dev
  1. 安装 Babel 转换器:Babel 使用转换器对 AST 进行转换操作,可以添加、删除、替换、修改节点等。@babel/traverse 是 Babel 提供的 AST 转换工具,运行以下命令进行安装:
npm install @babel/traverse --save-dev
  1. 安装 Babel 代码生成器:Babel 将转换后的 AST 重新生成 JavaScript 代码,以便进行后续的编译和执行。@babel/generator 是 Babel 提供的 AST 代码生成器,运行以下命令进行安装:
npm install @babel/generator --save-dev
  1. 安装 @babel/types 模块,可以使用 npm 或 yarn 命令进行安装:
npm install @babel/types
  1. 安装完成后,可以在项目中使用这些模块进行 JavaScript 代码的转译、解析和生成等操作。例如,以下代码演示了使用 Babel 解析器将一个字符串解析成 AST,并使用 Babel 代码生成器将 AST 转换成 JavaScript 代码:
  2. 需要注意的是,Babel 的使用需要一定的编程经验和 AST 知识,因此建议在学习 Babel 之前先掌握 JavaScript 和 AST 的相关知识。
const babelParser = require('@babel/parser');
const babelGenerator = require('@babel/generator').default;

const code = 'const a = 1;';
const ast = babelParser.parse(code);//将代码解析成ast语法树
const output = babelGenerator(ast, { /* options */ }, code);
console.log(output.code); // 输出转换后的代码:const a = 1;

@babel/parser

解析代码

@babel/parser 模块提供了一个名为parse()的函数,用于将 ECMAScript 代码解析成 AST。该函数接受两个参数:

  • code:需要解析的代码,一个字符串。

  • options:解析选项,一个对象。

例如,以下代码可以解析一个简单的 ECMAScript 代码片段,并打印出 AST:

const { parse } = require('@babel/parser');

const code = 'const x = 1 + 2;';
const ast = parse(code);
console.log(JSON.stringify(ast, null, 2));

解析选项

@babel/parser 模块的parse()函数可以接受一个选项对象作为第二个参数,用于指定解析器的行为。常用的选项包括:

  • sourceType:指定代码的来源类型,可以为 “script” 或 “module”。默认为 “script”。

  • plugins:指定解析器使用的插件列表,一个数组。默认为空数组。

  • ranges:是否在 AST 节点中包含节点位置信息,一个布尔值。默认为 false。

  • locations:是否在 AST 节点中包含源代码位置信息,一个布尔值。默认为 false。

  • onComment:指定解析器在解析注释时执行的回调函数,一个函数。默认为 null。

例如,以下代码可以使用 sourceType 选项将代码解析为 ES6 模块:

const { parse } = require('@babel/parser');

const code = 'export const x = 1;';
const ast = parse(code, { sourceType: 'module' });

在这个例子中,我们使用 parse() 函数解析一个 ES6 模块,将 sourceType 选项指定为 "module"

@babel/traverse

@babel/traverse 是一个用于对抽象语法树(AST)进行递归遍历和更新的工具库,它可以通过访问和修改 AST 节点来实现代码转换。

下面是一个简单的示例代码,其中包含了使用 @babel/parser 将 JavaScript 代码解析为 AST,并使用 @babel/traverse 对 AST 进行遍历和更新的过程。

const parser = require('@babel/parser');
const generator= require('@babel/generator').default
const traverse = require('@babel/traverse').default;

// 将 JavaScript 代码解析为 AST
const ast = parser.parse(`
  const double = x => x * 2;
  const result = double(3);
`);

// 遍历 AST,并对所有函数调用进行修改
traverse(ast, {
  CallExpression(path) {
    // 如果当前节点表示一个函数调用
    if (path.node.callee.name === 'double') {
      // 将函数名修改为 triple
      path.node.callee.name = 'triple';
      // 将函数调用的参数修改为原参数的两倍
      path.node.arguments[0] = {
        type: 'NumericLiteral',
        value: path.node.arguments[0].value * 2,
      };
    }
  },
});

// 将修改后的 AST 转换回 JavaScript 代码
const code =generator(ast).code;

console.log(code);

在这个示例代码中,我们首先使用 @babel/parser 将 JavaScript 代码解析为 AST,然后使用 @babel/traverse 对 AST 进行递归遍历,并对所有函数调用进行修改。具体来说,我们将函数 double 的名称修改为 triple,并将函数调用的参数修改为原参数的两倍。最后,我们使用 @babel/generator 将修改后的 AST 转换回 JavaScript 代码,并输出转换后的代码。




下面这个示例,它演示了如何使用 @babel/traverse 对 JavaScript 代码进行转换,将 var 关键字替换成 const 关键字。

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;

const code = `
  var a = 1;
  var b = 2;
  var c = a + b;
`;

// 将 JavaScript 代码解析为 AST
const ast = parser.parse(code);

// 遍历 AST,并将所有 var 声明替换成 const 声明
traverse(ast, {
  VariableDeclaration(path) {
    if (path.node.kind === 'var') {
      path.node.kind = 'const';
    }
  },
});

// 将修改后的 AST 转换回 JavaScript 代码
const output = generator(ast, {}, code);

console.log(output.code);

在这个示例中,我们首先将 JavaScript 代码传入 @babel/parser,将其解析为 AST。然后我们使用 @babel/traverse 对 AST 进行遍历,遍历过程中,我们判断当前节点是否为变量声明节点,如果是,则将节点中的 kind 属性(即变量声明的关键字)替换成 const。最后,我们使用 @babel/generator 将修改后的 AST 转换回 JavaScript 代码,并输出转换后的代码。

值得注意的是,@babel/traverse 遍历 AST 时,它会对每个节点调用与节点类型对应的方法。例如,在这个示例代码中,我们使用了 CallExpression 方法,因为我们要对所有函数调用进行修改。如果你要对其他类型的节点进行遍历和修改,可以参考 @babel/traverse 的文档,查看支持的节点类型和相应的方法名。

path对象

@babel/traverse模块中,path 对象表示一个节点(node)在 AST 树中的位置,提供了一些属性和方法,用于访问节点的属性、子节点、父节点、兄弟节点等,并且可以对 AST 树进行修改。下面是 path 对象的一些常用属性和方法的详细说明:

  • path.node
    nodepath 对象上的一个属性,表示当前节点的 AST 表示。node 对象包含了当前节点的所有属性和方法,你可以在钩子函数中使用 path.node 访问和修改节点的属性和方法。

  • 例如,如果当前节点是一个函数声明(FunctionDeclaration),那么 path.node.id 就是函数的名字,path.node.params 就是函数的参数列表,path.node.body 就是函数的主体等。

  • path.parent
    parent path 对象上的一个属性,表示当前节点的父节点。你可以使用 path.parent 访问和修改当前节点的父节点,也可以使用 path.parentPath 来获取父路径对象。

  • path.scope
    scopepath 对象上的一个属性,表示当前节点的作用域。作用域对象包含了当前节点的变量声明和函数声明等信息。你可以使用 path.scope 访问和修改当前节点的作用域。

- path.get(key)
get() 是 path 对象上的一个方法,用于获取当前节点的指定子节点。你可以使用 path.get(key) 访问当前节点的子节点,其中 key 可以是一个属性名、一个下标或者一个函数。

  • 例如,如果当前节点是一个函数调用(CallExpression),那么 path.get('callee') 就是获取函数名节点,path.get('arguments')[0] 就是获取第一个参数节点。

  • path.traverse(visitor)
    traverse() path 对象上的一个方法,用于遍历当前节点的子节点,并调用钩子函数处理子节点。你可以使用 path.traverse(visitor) 遍历当前节点的所有子节点,其中 visitor 是一个对象,包含了 enter 和 exit 两个钩子函数。

  • 例如,如果当前节点是一个if语句(IfStatement),那么可以使用 path.get('consequent').traverse(visitor) 遍历 if 语句的主体部分,并且在遍历每个子节点时调用 enter exit 钩子函数。

  • path.replaceWith(newNode)
    replaceWith() 是 path 对象上的一个方法,用于替换当前节点。你可以使用 path.replaceWith(newNode) 来替换当前节点,其中 newNode 是一个新的节点。

  • 例如,如果当前节点是一个变量声明(VariableDeclaration),可以使用 path.replaceWith(t.expressionStatement(t.stringLiteral('new code'))) 替换当前节点为一个新的表达式语句。

- path.remove()
remove() 是 path 对象上的一个方法,用于删除当前节点。你可以使用 path.remove() 删除当前

scope属性

在Babel中,@babel/traverse模块的path对象提供了一个scope属性,代表当前路径节点的作用域。在遍历语法树时,可以使用scope属性来判断当前节点是否处于某个特定的作用域内,以及在这个作用域内声明的变量和函数。

scope对象包含一些属性和方法,用于表示当前节点的作用域信息,比如:

  • block:当前节点所在的块级作用域
  • path:当前节点对应的路径
  • bindings:当前作用域内所有绑定(变量或函数)的信息
    其中,bindings属性是一个对象,它的键是所有在当前作用域中定义的变量或函数名,值是绑定对象,代表这些变量或函数的定义信息,比如它们的类型、声明的位置等。

bindings对象的值也是一个对象,包含了以下属性:

  • kind:变量或函数的类型,可以是var、let、const、function等
  • path:该变量或函数的定义路径
  • constantViolations:使用该变量或函数的非常量路径
  • referencePaths:使用该变量或函数的所有路径
  • referenced:是否被引用过
  • references:引用的路径数量
    例如,下面的示例代码演示了如何使用scope对象查找当前作用域中所有的变量和函数:
const babel = require('@babel/core');
const traverse = require('@babel/traverse').default;

const code = `
  const a = 1;
  function foo() {
    const b = 2;
    console.log(a, b);
  }
  foo();
`;

const ast = babel.parse(code);
traverse(ast, {
  enter(path) {
    const bindings = path.scope.bindings;
    console.log('Node:', path.node.type, 'Bindings:', Object.keys(bindings));
  }
});

在这个例子中,我们首先使用@babel/core模块的parse方法将源代码解析为AST。然后使用traverse方法遍历AST,在遍历每个节点时打印出它的类型和作用域信息。

在这个例子中,我们可以看到,在作用域中定义了a变量和foo函数,它们都被存储在bindings对象中,我们可以通过遍历bindings对象来获取这些变量和函数的信息。

bindings对象

每个 scope 对象都有一个名为 bindings 的属性,它是一个包含当前作用域中所有绑定的对象。在 JavaScript 中,绑定指的是标识符(Identifier)和值(Value)之间的关系。bindings 对象提供了一种获取在当前作用域中绑定的标识符和它们的值的方式。

bindings 对象是一个包含键值对的对象,其中键是标识符名称,值是描述绑定的对象。描述绑定的对象包含有关绑定的信息,例如绑定所在的节点、绑定的类型(变量、函数等)以及绑定的范围。此外,描述绑定的对象还提供了许多有用的方法,例如获取绑定的类型、获取绑定的值等。

以下是一个示例代码,展示如何使用 bindings 对象获取当前作用域中的所有绑定:

const babel = require('@babel/core');
const t = require('@babel/types');
const traverse = require('@babel/traverse').default;

const code = `
  const foo = 1;
  function bar() {}
  console.log(foo + bar());
`;

const ast = babel.parseSync(code);

traverse(ast, {
  Program(path) {
    const { bindings } = path.scope;

    for (const name in bindings) {
      if (bindings.hasOwnProperty(name)) {
        console.log(`Binding: ${name}`);
        console.log(`Binding type: ${bindings[name].kind}`);
        console.log(`Binding value: ${bindings[name].path.node}`);
      }
    }
  },
});

在这个示例中,我们遍历了 AST 并在 Program 节点上获取了当前作用域的 bindings 对象。然后,我们遍历了 bindings 对象并打印了每个绑定的名称、类型和值。注意,bindings[name].path.node 获取到的是一个 AST 节点,如果想要获取到节点对应的值,需要使用 @babel/generator 模块将其转换为代码。

const babel = require('@babel/core');
const t = require('@babel/types');
const traverse = require('@babel/traverse').default;

const code = `
  const foo = 1;
  function bar() {
    console.log(foo);
  }
`;

const ast = babel.parseSync(code);

traverse(ast, {
  FunctionDeclaration(path) {
    const { bindings } = path.scope;

    for (const name in bindings) {
      if (bindings.hasOwnProperty(name)) {
        console.log(`Binding: ${name}`);
        console.log(`Binding type: ${bindings[name].kind}`);
        console.log(`Binding value: ${bindings[name].path.node}`);

        // 获取标识符节点
        const identifier = bindings[name].identifier;
        console.log(`Identifier: ${identifier.name}`);

        // 获取绑定的所有引用
        const references = bindings[name].referencePaths;
        for (const reference of references) {
          console.log(`Reference: ${reference.node}`);
        }
      }
    }
  },
});

在这个示例中,我们遍历了 AST 并在FunctionDeclaration节点上获取了当前作用域的 bindings 对象。然后,我们遍历了 bindings 对象并打印了每个绑定的名称、类型和值。接着,我们使用 bindings[name].identifier 获取了绑定的标识符节点,并使用 bindings[name].referencePaths 获取了绑定的所有引用。注意,bindings[name].referencePaths 获取到的是一个路径数组,每个路径都包含一个引用节点。

@babel/generator

@babel/generator 是 Babel 中负责将 AST 转换回 JavaScript 代码的模块。它提供了一个 default 方法,该方法接受一个 AST 作为参数,并返回转换后的 JavaScript 代码。

下面是一个简单的示例,演示了如何使用 @babel/generator 将 AST 转换回 JavaScript 代码:

const generator = require('@babel/generator').default;

const ast = {
  type: 'Program',
  body: [
    {
      type: 'VariableDeclaration',
      kind: 'const',
      declarations: [
        {
          type: 'VariableDeclarator',
          id: { type: 'Identifier', name: 'x' },
          init: { type: 'NumericLiteral', value: 42 },
        },
      ],
    },
  ],
};

const code = generator(ast, {}, '');

console.log(code.code); // 输出:const x = 42;

在这个示例中,我们首先定义了一个 AST,它表示了一段简单的代码,其中包含一个 const 声明语句。然后,我们使用 @babel/generatordefault 方法将 AST 转换回 JavaScript 代码。由于我们不需要传递任何配置项,所以第二个参数传入一个空对象。最后,我们输出转换后的代码。

除了 default 方法外,@babel/generator 还提供了一些其他方法,例如 generate 方法,它与 default 方法功能类似,但可以在不同的选项和插件配置下生成代码。此外,还有 CodeGenerator 类,它可以在多个 AST 转换之间共享状态,以提高性能。

总之,@babel/generator 是一个非常有用的模块,它让你可以轻松地将 AST 转换回 JavaScript 代码,为你的代码转换工作提供了很大的便利。

@babel/types

@babel/types 模块提供了一组用于操作 AST 的工具,包括 AST 节点类型的定义节点创建节点判断等功能。下面简单介绍一下 @babel/types 模块中的常用功能。

节点类型

@babel/types 模块定义了 ECMAScript 代码中的各种 AST 节点类型,例如 IdentifierBinaryExpressionBlockStatement 等。每个节点类型都是一个 JavaScript 对象,包含该节点类型的属性和方法。例如,Identifier 节点类型包含以下属性:

  • type:节点类型,固定为 “Identifier”。

  • name:标识符的名称,一个字符串。

  • start:标识符在源代码中的起始位置(行号和列号)。

  • end:标识符在源代码中的结束位置(行号和列号)。

  • loc:标识符在源代码中的位置信息,包含 start 和 end 两个属性。

在使用 @babel/types 模块时,我们可以使用节点类型来创建新的节点或操作现有的节点。

节点创建

@babel/types 模块提供了一组用于创建 AST 节点的工具函数,例如 t.identifier(name)t.binaryExpression(operator, left, right) 等。这些函数会返回对应的节点对象,我们可以将它们用于构建新的 AST。

例如,以下代码可以创建一个 VariableDeclaration 节点:

const t        = require("@babel/types");

const variable = t.variableDeclaration(
  'const',
  [t.variableDeclarator(t.identifier('x'), t.numericLiteral(1))]
);

在这个例子中,我们使用 t.variableDeclaration 函数创建一个 VariableDeclaration 节点,其中指定了变量声明的类型为 "const",变量名为 "x",初始值为 1

节点判断

@babel/types 模块还提供了一组用于判断 AST 节点类型的工具函数,例如 t.isIdentifier(node)t.isBinaryExpression(node) 等。这些函数会返回一个布尔值,用于判断指定的节点对象是否属于某个节点类型。

例如,以下代码可以判断一个节点是否为 CallExpression 类型:

const t    = require("@babel/types");

function isCallExpression(node) {
  return t.isCallExpression(node);
}

在这个例子中,我们定义了一个名为 isCallExpression 的函数,它接受一个节点对象作为参数,使用 t.isCallExpression 函数判断该节点是否为 CallExpression 类型,并返回对应的布尔值。

综上所述,@babel/types 模块提供了一组用于操作 AST 的工具函数,可以用于创建、修改和判断 AST 节点。我们可以使用它们来实现对 ECMAScript 代码的转换、分析和生成。

js代码常见混淆方式

还原前

if (function (_0x48fd11, _0x52974e, _0x388e32) {
    function _0x549dad(_0x3d08e6, _0x563a9f, _0x32d68f, _0x935a1e, _0x2f5432, _0x11c4c1) {
        _0x563a9f = _0x563a9f >> 0x8,
        _0x2f5432 = 'po';
        var _0x868417 = 'shift',
        _0xca0acf = 'push',
        _0x11c4c1 = '‮';
        if (_0x563a9f < _0x3d08e6) {
            while (--_0x3d08e6) {
                _0x935a1e = _0x48fd11[_0x868417]();
                if (_0x563a9f === _0x3d08e6 && _0x11c4c1 === '‮' && _0x11c4c1['length'] === 0x1) {
                    _0x563a9f = _0x935a1e,
                    _0x32d68f = _0x48fd11[_0x2f5432 + 'p']();
                } else if (_0x563a9f && _0x32d68f['replace'](/[UOMPNTnblILJdPG=]/g, '') === _0x563a9f) {
                    _0x48fd11[_0xca0acf](_0x935a1e);
                }
            }
            _0x48fd11[_0xca0acf](_0x48fd11[_0x868417]());
        }
        return 0xf71c7;
    };
function _0x4f91(_0x162067, _0x2c1c19) {
    _0x162067 = ~~'0x'['concat'](_0x162067['slice'](0x1));
    var _0x1a4d27 = _0x235f[_0x162067];
    if (_0x4f91['MRYmFi'] === undefined) {
        (function () {
            var _0x27f542 = typeof window !== 'undefined' ? window : typeof process === 'object' && typeof require === 'function' && typeof global === 'object' ? global : this;
            var _0x5773e8 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
            _0x27f542['atob'] || (_0x27f542['atob'] = function (_0x1405c4) {
                var _0x4321b2 = String(_0x1405c4)['replace'](/=+$/, '');
                for (var _0x6467c8 = 0x0, _0x11d027, _0x40e1d1, _0x4cecb2 = 0x0, _0x2fff1b = ''; _0x40e1d1 = _0x4321b2['charAt'](_0x4cecb2++); ~_0x40e1d1 && (_0x11d027 = _0x6467c8 % 0x4 ? _0x11d027 * 0x40 + _0x40e1d1 : _0x40e1d1, _0x6467c8++ % 0x4) ? _0x2fff1b += String['fromCharCode'](0xff & _0x11d027 >> (-0x2 * _0x6467c8 & 0x6)) : 0x0) {
                    _0x40e1d1 = _0x5773e8['indexOf'](_0x40e1d1);
                }
                return _0x2fff1b;
            });
        }
            ());
        function _0x5734d9(_0x57c809, _0x2c1c19) {
            var _0xef41ee = [],
            _0x110b95 = 0x0,
            _0x244795,
            _0x1bed23 = '',
            _0x4a92ec = '';
            _0x57c809 = atob(_0x57c809);
            for (var _0x2253d1 = 0x0, _0x212cb4 = _0x57c809['length']; _0x2253d1 < _0x212cb4; _0x2253d1++) {
                _0x4a92ec += '%' + ('00' + _0x57c809['charCodeAt'](_0x2253d1)['toString'](0x10))['slice'](-0x2);
            }
            _0x57c809 = decodeURIComponent(_0x4a92ec);
            for (var _0xb19e69 = 0x0; _0xb19e69 < 0x100; _0xb19e69++) {
                _0xef41ee[_0xb19e69] = _0xb19e69;
            }
            for (_0xb19e69 = 0x0; _0xb19e69 < 0x100; _0xb19e69++) {
                _0x110b95 = (_0x110b95 + _0xef41ee[_0xb19e69] + _0x2c1c19['charCodeAt'](_0xb19e69 % _0x2c1c19['length'])) % 0x100;
                _0x244795 = _0xef41ee[_0xb19e69];
                _0xef41ee[_0xb19e69] = _0xef41ee[_0x110b95];
                _0xef41ee[_0x110b95] = _0x244795;
            }
            _0xb19e69 = 0x0;
            _0x110b95 = 0x0;
            for (var _0x19db4a = 0x0; _0x19db4a < _0x57c809['length']; _0x19db4a++) {
                _0xb19e69 = (_0xb19e69 + 0x1) % 0x100;
                _0x110b95 = (_0x110b95 + _0xef41ee[_0xb19e69]) % 0x100;
                _0x244795 = _0xef41ee[_0xb19e69];
                _0xef41ee[_0xb19e69] = _0xef41ee[_0x110b95];
                _0xef41ee[_0x110b95] = _0x244795;
                _0x1bed23 += String['fromCharCode'](_0x57c809['charCodeAt'](_0x19db4a) ^ _0xef41ee[(_0xef41ee[_0xb19e69] + _0xef41ee[_0x110b95]) % 0x100]);
            }
            return _0x1bed23;
        }
        _0x4f91['FommKf'] = _0x5734d9;
        _0x4f91['WVWLXP'] = {};
        _0x4f91['MRYmFi'] = !![];
    }
    var _0x41c863 = _0x4f91['WVWLXP'][_0x162067];
    if (_0x41c863 === undefined) {
        if (_0x4f91['zUDdVD'] === undefined) {
            var _0x7cb49b = function (_0x43d3b2) {
                this['dBIiIv'] = _0x43d3b2;
                this['ZJxFPE'] = [0x1, 0x0, 0x0];
                this['DtEyxk'] = function () {
                    return 'newState';
                };
                this['StAszO'] = '\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*';
                this['kCvLOW'] = '[\x27|\x22].+[\x27|\x22];?\x20*}';
            };
            _0x7cb49b['prototype']['MAUJkU'] = function () {
                var _0x1bf2d5 = new RegExp(this['StAszO'] + this['kCvLOW']);
                var _0x2af6bd = _0x1bf2d5['test'](this['DtEyxk']['toString']()) ? --this['ZJxFPE'][0x1] : --this['ZJxFPE'][0x0];
                return this['VQGnUA'](_0x2af6bd);
            };
            _0x7cb49b['prototype']['VQGnUA'] = function (_0x2eb9c4) {
                if (!Boolean(~_0x2eb9c4)) {
                    return _0x2eb9c4;
                }
                return this['YQnufA'](this['dBIiIv']);
            };
            _0x7cb49b['prototype']['YQnufA'] = function (_0x35a98e) {
                for (var _0x49c03f = 0x0, _0x367fd6 = this['ZJxFPE']['length']; _0x49c03f < _0x367fd6; _0x49c03f++) {
                    this['ZJxFPE']['push'](Math['round'](Math['random']()));
                    _0x367fd6 = this['ZJxFPE']['length'];
                }
                return _0x35a98e(this['ZJxFPE'][0x0]);
            };
            new _0x7cb49b(_0x4f91)['MAUJkU']();
            _0x4f91['zUDdVD'] = !![];
        }
        _0x1a4d27 = _0x4f91['FommKf'](_0x1a4d27, _0x2c1c19);
        _0x4f91['WVWLXP'][_0x162067] = _0x1a4d27;
    } else {
        _0x1a4d27 = _0x41c863;
    }
    return _0x1a4d27;
};

还原后

-----------------------------------非全部代码------------------------------------------------------------------

function getUrl(code, _0x3379ed, _0x46bf59) {
    var _0x524375 = null;
    var tp = new Date()["getTime"]();
    var key = CryptoJS["enc"]["Utf8"]["parse"](md5(pid + "-" + tp)["substring"](0x0, 0x10));
    var encryptedData = CryptoJS["AES"]["encrypt"](pid + "-" + tp, key, {
        "mode": CryptoJS["mode"]["ECB"],
        "padding": CryptoJS["pad"]["Pkcs7"]
    });
    $["ajax"]("/god/" + pid, {
        "async": _0x3379ed,
        "method": "POST",
        "dataType": "json",
        "data": {
            "t": tp,
            "sg": base64ToHex(encryptedData + ""),
            "verifyCode": code
        },
        "success": function (result) {
            if (result["url"] != null) {
                _0x524375 = dealUrl(result);
                _0x120e92 = _0x524375;
                lines["push"](_0x524375);
                _0x46bf59(result["url"]);
            } else {
                window["dp"]["notice"](result["error"], 0xbb8);
            }
        }
    });
    return _0x524375;
}

还原插件

  • 去除十六进制编码
function decodeHexString(ast) {
  traverse(ast, {
    StringLiteral(path) {
      path.get('extra').remove();
    }
  })

  code = generator(ast).code;
  ast= parser.parse(code);
  return ast;
}
  • 删除未被引用变量
function removeUnusedVariables(ast){
  const visitor ={
      VariableDeclarator(path) {
          const {id} = path.node;
          const binding = path.scope.getBinding(id.name);

          //如果变量被修改过,则不能进行删除动作。
          if (!binding || binding.constantViolations.length > 0) {
              return;
          }
          //长度为0,说明变量没有被使用过。
          if (binding.referencePaths.length === 0) {
              path.remove();
          }
      },
  }
  traverse(ast,visitor);
  code = generator(ast).code;
  ast= parser.parse(code);
return ast;
}
  • 流程平坦化switch语句
function decodeSwitch(ast){
  for(let i = 0; i < 100; i++){
    traverse(ast, {
        MemberExpression(path) {
            if (t.isStringLiteral(path.node.object) &&
                    t.isStringLiteral(path.node.property, {
                        value: 'split'
                    })) {
                //找到类型为 VariableDeclaration 的父节点
                let varPath = path.findParent(function (p) {
                        return t.isVariableDeclaration(p);
                    });
                //获取下一个同级节点
                let whilePath = varPath.getSibling(varPath.key + 1);
                //解析整个 switch
                let myArr = [];
                whilePath.node.body.body[0].cases.map(function (p) {
                    myArr[p.test.value] = p.consequent[0];
                });

                let parentPath = whilePath.parent;
                varPath.remove();
                whilePath.remove();
                // path.node.object.value 取到的是 '1|2|4|7|5|3|8|0|6'
                let shufferArr = path.node.object.value.split("|");
                shufferArr.map(function (v) {
                    parentPath.body.push(myArr[v]);
                });
                path.stop();
            }
        }
    });
  }
  code = generator(ast).code;
  ast= parser.parse(code);
  return ast;

}

  • object 还原
function decodeObject(ast){
  const visitor ={
        VariableDeclarator(path) {
            const {id, init} = path.node;

            //特征判断,对象为空则不处理
            if (!t.isObjectExpression(init) || init.properties.length === 0) return;

            let name = id.name;
            let scope = path.scope;

            for (const property of init.properties) {//遍历key、value
                let key = property.key.value;
                let value = property.value;

                //一般ob混淆,key长度都是5,也有是3的,自行调整即可。
                if (key.length !== 5) return;

                //如果是字面量
                if (t.isLiteral(value)) {
                    scope.traverse(scope.block, {
                        //遍历MemberExpression,找出与key相同的表达式
                        MemberExpression(_path) {
                            let _node = _path.node;
                            if (!t.isIdentifier(_node.object, {name: name})) return;
                            if (!t.isLiteral(_node.property, {value: key})) return;
                            _path.replaceWith(value);
                        },
                    })
                }
                //如果是函数表达式
                else if (t.isFunctionExpression(value)) {
                    let ret_state = value.body.body[0];

                    //特征判断,如果不是return表达式
                    if (!t.isReturnStatement(ret_state)) continue;

                    scope.traverse(scope.block, {
                        CallExpression: function (_path) {

                            //遍历CallExpression
                            let {callee, arguments} = _path.node;
                            if (!t.isMemberExpression(callee)) return;
                            if (!t.isIdentifier(callee.object, {name: name})) return;
                            if (!t.isLiteral(callee.property, {value: key})) return;
                            if (t.isCallExpression(ret_state.argument) && arguments.length > 0) {

                                //构造节点
                                _path.replaceWith(t.CallExpression(arguments[0], arguments.slice(1)));
                            } else if (t.isBinaryExpression(ret_state.argument) && arguments.length === 2) {

                                //构造节点
                                let replace_node = t.BinaryExpression(ret_state.argument.operator, arguments[0], arguments[1]);
                                _path.replaceWith(replace_node);
                            } else if (t.isLogicalExpression(ret_state.argument) && arguments.length === 2) {

                                //构造节点
                                let replace_node = t.LogicalExpression(ret_state.argument.operator, arguments[0], arguments[1]);
                                _path.replaceWith(replace_node);
                            }
                        }
                    })
                }
            }
        },
    }
    traverse(ast,visitor);
    code = generator(ast).code;
  ast= parser.parse(code);
    return ast;
}
  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Babel 插件通常是使用 `babel.transformFromAst` 方法将 AST 转换为代码。该方法接收两个参数:AST 和选项对象。 AST(抽象语法树)是由 Babel 解析 JavaScript 代码时生成的一个对象,它表示代码的结构。 选项对象是可选的,用于指定转换过程的一些选项,比如源代码的文件路径和目标语言的版本。 使用方法如下: ``` const { transformFromAst } = require('babel'); const code = `const x = 1;`; const ast = parse(code); // parse 函数用于将代码解析为 AST const options = {}; const { code: transformedCode } = transformFromAst(ast, options); ``` 在这个例子中,变量 `transformedCode` 将包含转换后的代码字符串。 ### 回答2: Babel插件可以通过对AST进行遍历和修改来生成将加工完成的AST翻译成代码。 首先,Babel解析负责将代码文件转换成AST(Abstract Syntax Tree)。AST代码的抽象表示形式,它以树状结构表示代码的逻辑结构和语法信息。 接下来,Babel插件通过对AST进行遍历来访问和分析节点。遍历可以使用Babel提供的访问者模式来实现。在遍历过程中,插件可以选择性地对AST节点进行修改,例如添加、删除或替换节点。 插件在遍历AST期间可以使用Babel提供的访问者方法来操作节点。例如,可以使用`enter`方法在进入某个节点时触发相应的回调函数,或者使用`exit`方法在离开节点时触发回调函数。这样,插件就能够在特定节点上执行自定义的操作。 最后,当所有的AST节点遍历和修改都完成后,Babel插件可以使用Babel提供的代码生成将最终的AST转换回代码代码生成会根据AST的结构和规则生成相应的代码字符串。 总结而言,Babel插件通过遍历AST并对节点进行修改,最终将加工完成的AST转换回代码。这样可以实现对源代码转译、转换或优化等操作,提供了一种灵活且可扩展的方式来处理JavaScript代码。 ### 回答3: Babel 是一个非常强大的 JavaScript 编译工具,它可以将最新版本的 JavaScript 代码转换为向后兼容的代码,使得我们可以在老旧的浏览或环境中运行新的 JavaScript 语法或特性。 Babel 的核心功能是生成抽象语法树 (AST),将源代码解析成一个包含了语法结构的对象。然后,它针对这个 AST 进行扩展、修改和转换工作,最后将经过修改后的 AST 生成新的代码。 插件是 Babel 中实现这些转换工作的关键。当 Babel 解析源代码后,它会顺序地应用一系列插件来修改 AST。每个插件都对应一个或一组转换规则,这些规则告诉 Babel 如何修改和遍历 AST。 当 Babel 遍历 AST 时,它会从顶层节点开始,递归地遍历所有节点。每当遇到一个节点时,Babel 会调用相应插件中的对应转换规则来对节点进行转换。这些规则可以通过修改当前节点,或者增加、删除、移动其他节点来实现。 在所有插件的转换规则应用完毕后,Babel 会将最终的 AST 重新生成为代码。这个过程称为代码生成。Babel 会从 AST 的根节点开始,生成与源代码对应的逐行代码。在此过程中,Babel 还会根据需要添加适当的缩进和换行符,以保持代码的可读性。 通过这种方式,Babel 插件可以将加工完成的 AST 翻译成代码。我们可以通过编写自定义的插件,来扩展 Babel 的功能,实现更多自定义的转换规则,从而将源代码转换成我们期望的形式。插件的生成代码过程是 Babel 实现 JavaScript 代码转换的核心机制之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值