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

这里写目录标题

  • 介绍
    • JavaScript 和 Babel 简介
    • Babel 的历史和发展
    • Babel 的优势和应用场景
    • Babel 的原理和工作流程
  • 安装babel模块
  • @babel/parser
      • 解析代码
      • 解析选项
  • @babel/traverse
    • path对象
    • scope属性
    • bindings对象
  • @babel/generator
  • @babel/types
      • 节点类型
      • 节点创建
      • 节点判断
  • js代码常见混淆方式
    • 还原前
    • 还原后
  • 还原插件

介绍

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;
}

你可能感兴趣的:(AST语法树,混淆还原,javascript,学习,前端,爬虫,爬山算法)