🧩 JS逆向 AST 抽象语法树解析与实践
🔍 什么是 AST 技术?
抽象语法树(Abstract Syntax Tree,AST)是一种以树状结构表示程序源代码的方式,它将代码中的每个组成部分表示为树的一个节点。AST 是编译器和解释器中的重要概念,它用于将源代码转换为计算机能够理解的形式。AST 的主要作用是将源代码中的语法结构转化为树形结构,便于对代码进行分析、转换和优化。
在 JavaScript 中,AST 是通过解析器(如 acorn
或 esprima
)生成的。这些解析器会将 JavaScript 代码转换为 AST,随后可以通过 AST 对代码进行深入分析或转换。
AST 示例
以下是一个较复杂的 JavaScript 代码示例及其对应的 AST:
// JavaScript 代码示例 function add(a, b) { const sum = a + b; return sum; } const result = add(10, 20); console.log(result);
在这个代码示例中,函数 add
定义了两个参数 a
和 b
,并返回它们的和。代码的 AST 可能如下所示:
Program ├── FunctionDeclaration (add) │ ├── Parameters │ │ ├── Identifier (a) │ │ └── Identifier (b) │ ├── BlockStatement │ │ ├── VariableDeclaration (const) │ │ │ ├── VariableDeclarator (sum) │ │ │ │ └── BinaryExpression (+) │ │ │ │ ├── Identifier (a) │ │ │ │ └── Identifier (b) │ │ └── ReturnStatement │ │ └── Identifier (sum) ├── VariableDeclaration (const) │ ├── VariableDeclarator (result) │ │ └── CallExpression (add) │ │ ├── Literal (10) │ │ └── Literal (20) └── ExpressionStatement (console.log) └── Identifier (result)
🛠️ Parser API 学习
JavaScript 提供了一些工具和库,用于解析源代码并生成 AST。其中,acorn
和 esprima
是最常用的解析器库。acorn
是一个轻量级的解析器,具有高效且快速的特点,能够将 JavaScript 代码解析为 AST。
使用 acorn
解析 JavaScript 代码
以下是如何使用 acorn
解析 JavaScript 代码并输出 AST 的示例:
const acorn = require('acorn'); // 需要解析的 JavaScript 代码 const code = ` function add(a, b) { const sum = a + b; return sum; } const result = add(10, 20); console.log(result); `; // 使用 acorn 解析代码 const ast = acorn.parse(code, { ecmaVersion: 2020 }); // 输出 AST console.log(JSON.stringify(ast, null, 2));
在这个示例中,我们使用 acorn.parse
方法将 JavaScript 代码解析为 AST。ecmaVersion
选项指定了 ECMAScript 的版本,确保解析器能够正确处理不同版本的语法。JSON.stringify
方法用于将 AST 对象转换为 JSON 字符串,以便于阅读和调试。
acorn
配置选项
acorn
解析器提供了多种配置选项,允许我们根据需要调整解析行为:
const ast = acorn.parse(code, { ecmaVersion: 2020, // ECMAScript 版本 sourceType: 'module', // 支持 ES6 模块 locations: true, // 启用位置信息 onComment: (block, text, start, end) => { console.log(`Comment: ${text}`); } });
ecmaVersion
: 指定 ECMAScript 的版本,例如 2020。sourceType
: 可以是script
或module
,指定源代码类型。locations
: 启用位置信息以便于调试。onComment
: 回调函数,用于处理代码中的注释。
🔍 traverse
库学习
traverse
是一个用于遍历和操作 AST 的库,它可以帮助我们对 AST 进行各种操作,如遍历、修改和转换。traverse
提供了一种简洁的方式来访问 AST 的各个部分。
安装 traverse
库
npm install traverse
使用 traverse
遍历 AST
以下是如何使用 traverse
库遍历 AST 的示例:
const traverse = require('traverse'); const acorn = require('acorn'); // 解析 JavaScript 代码 const code = ` function add(a, b) { const sum = a + b; return sum; } const result = add(10, 20); console.log(result); `; const ast = acorn.parse(code, { ecmaVersion: 2020 }); // 遍历 AST traverse(ast).forEach(function (node) { if (node.type === 'Identifier') { console.log(`Found identifier: ${node.name}`); } });
在这个示例中,我们使用 traverse
库遍历 AST,并打印出所有标识符节点的名称。traverse
提供了一种简洁的方式来访问和操作 AST 的各个部分。traverse
支持多种遍历策略,可以根据需要定制遍历行为。
自定义遍历策略
traverse
允许我们定义自定义遍历策略,以满足特定需求:
traverse(ast).forEach(function (node) { if (node.type === 'FunctionDeclaration') { console.log(`Found function declaration: ${node.id.name}`); } else if (node.type === 'VariableDeclarator') { console.log(`Found variable declarator: ${node.id.name}`); } });
在这个示例中,我们定制了遍历策略,以便分别处理函数声明和变量声明节点。
🧩 字符串和编码还原
在处理 JavaScript 代码时,我们常常需要对字符串和编码进行还原。字符串可能包含转义字符、Unicode 编码等,需要对其进行解析和还原。
字符串解码
JavaScript 字符串中可能包含转义字符,例如 Unicode 转义序列。我们可以使用 JSON.parse
方法将这些转义字符还原为普通字符串:
// 原始字符串 const encodedString = '\\u0048\\u0065\\u006C\\u006C\\u006F'; // 将转义字符还原为普通字符串 const decodedString = JSON.parse(`"${encodedString}"`); console.log(decodedString); // 输出: Hello
在这个示例中,我们将包含 Unicode 转义序列的字符串 \\u0048\\u0065\\u006C\\u006C\\u006F
还原为普通字符串 Hello
。这种方法对于处理编码和转义字符非常有效。
编码还原
URL 编码是一种常见的编码方式,我们可以使用 decodeURIComponent
方法将其还原为普通字符串:
// URL 编码字符串 const encodedURI = 'Hello%20World%21'; // 还原为普通字符串 const decodedURI = decodeURIComponent(encodedURI); console.log(decodedURI); // 输出: Hello World!
🛠️ 通用常量还原
在 JavaScript 代码中,常量的还原通常涉及将常量值恢复为其原始形式。常量可能以不同的格式存在,如十六进制、二进制等。
十六进制常量还原
// 十六进制常量 const hexValue = 0x1A3F; // 还原为十进制 const decimalValue = hexValue; console.log(decimalValue); // 输出: 6719
在这个示例中,我们将十六进制常量 0x1A3F
还原为其十进制值 6719
。十六进制常量在 JavaScript 中经常用于表示颜色值、内存地址等。
二进制常量还原
// 二进制常量 const binaryValue = 0b101010; // 还原为十进制 const decimalValue = binaryValue; console.log(decimalValue); // 输出: 42
在这个示例中,我们将二进制常量 0b101010
还原为其十进制值 42
。二进制常量在 JavaScript 中用于表示位操作或二进制数据。
🧩 evaluate
方法学习
evaluate
方法通常用于执行或计算表达式。它可以在 JavaScript 代码中动态执行表达式并返回结果。使用 eval
方法时要特别
小心,因为它可以执行任意代码,可能导致安全问题。
使用 eval
执行代码
const expression = '2 + 3 * 4'; // 使用 eval 执行表达式 const result = eval(expression); console.log(result); // 输出: 14
在这个示例中,我们使用 eval
执行一个简单的数学表达式,并输出结果。eval
可以动态地执行任意 JavaScript 代码,因此使用时需谨慎。
使用 Function
构造函数
为了避免使用 eval
,可以使用 Function
构造函数动态地创建和执行代码:
const expression = '2 + 3 * 4'; // 使用 Function 构造函数执行表达式 const result = new Function('return ' + expression)(); console.log(result); // 输出: 14
Function
构造函数提供了一种更安全的方式来动态执行代码,但仍然需要确保代码的安全性。
🔍 实战解 OB 高级混淆
在实际应用中,我们可能会遇到 JavaScript 代码的高级混淆技术,这些技术可以使代码难以理解。以下是如何解混淆高级混淆代码的示例:
解混淆示例
高级混淆技术可能涉及到复杂的函数调用、字符串加密等。以下是一个解混淆示例:
// 混淆代码示例 const obfuscatedCode = ` (function() { var _0x1a2b = ['\x66\x6F\x6F', '\x62\x61\x72', '\x62\x61\x7A']; var _0x1234 = function(_0x5678) { return _0x1a2b[_0x5678]; }; console.log(_0x1234(0)); })(); `; // 还原混淆代码 const restoredCode = ` (function() { var _0x1a2b = ['foo', 'bar', 'baz']; var _0x1234 = function(index) { return _0x1a2b[index]; }; console.log(_0x1234(0)); })(); `; console.log(restoredCode); // 输出: 还原后的代码
在这个示例中,混淆代码使用了十六进制表示的字符串和函数调用。我们将混淆代码还原为更易读的形式,使其更容易理解。
极验 JS 实战解混淆
极验验证码是一种常见的防护技术,它通过混淆 JavaScript 代码来防止自动化访问。以下是如何解混淆极验 JS 代码的示例:
// 极验混淆代码示例 const geetestObfuscatedCode = ` (function() { var _0x1a2b = ['\x62\x79\x70\x61\x73\x73', '\x6c\x6f\x67']; console.log(_0x1a2b[0]); })(); `; // 还原混淆代码 const geetestRestoredCode = ` (function() { var _0x1a2b = ['bypass', 'log']; console.log(_0x1a2b[0]); })(); `; console.log(geetestRestoredCode); // 输出: 还原后的代码