js逆向--基于babel学习AST(Abstract Syntax Tree, 抽象语法树)
AST (Abstract Syntax Tree, 抽象语法树)
- https://astexplorer.net/一个在线的生成AST的工具,对学校AST很有帮助
Babel
- 基于node.js
- 官方文档
- plugin handbook这篇参考比较多,里面介绍了一些api的使用
安装
- npm install -g @babel/node
关键概念
-
parser与generator
-
traverse与visitor
-
path与node
- node与代码一一对应,作为path的一部分
- path维护的是node之间的关系,包含了scope和parent
- 建议通过path来操作node
- path的方法
- findParent(callback):向上遍历父级path,返回callback返回值为true的父级path
-
scope
path: NodePath
- node
- scope: Scope 节点所在的作用域内;如果节点是FunctionDeclaration,即函数声明,那么scope是函数自己
- bindings: 当前节点所在作用域内定义的变量
- Binding
- 这个表示变量
- kind: let|var|const
- identifier: 变量名节点
- path: 变量声明节点的path
- referenced: true|false 是否被引用
- references: 被引用的次数
- referencePaths: [NodePath...] 被引用的节点的path
- scope: 变量声明所在作用域
- constant: true没被修改过|false被修改过
- 如果要获取某个变量的所属作用域:
traverse(ast, { Identifier(path) { let name = path.node.name; let binding = path.scope.getBinding(name); let realScope = bingding.scope; # 这个才是变量的真实所属作用域 } });
- Binding
- block:当前节点所在作用域节点
- path:当前节点所在作用域节点的path
- getBinding('name'): 在当前及父级作用域内查找
- getOwnBinding('name'): 在当前作用域内查找
- bindings: 当前节点所在作用域内定义的变量
- parentPath:父节点path
- parent: 父节点
- container: 如果当前节点在父节点为独立节点,container为父节点;如果当前节点在父节点中是数组中的一员,则container为当前节点及其兄弟节点组成的数组
- key:container为单节点时,key为当前节点在父节点中的属性名;container为数组时,key为当前节点在数组中的下标
- listKey:container为单节点时,listKey=undefined;container为数组时,listKey为当前节点所在数组在父节点的属性名
生成语法树
```
import {parse} from "@babel/parser";
const code = 'xxx';
const ast = parse(code);
```
根据语法输还原成代码
```
import generator from "@babel/generator";
let gen_code = generator(ast);
console.log(gen_code.code);
```
遍历
```
import traverse from "@babel/traverse";
traverse(
ast, {
enter(path) {
console.log(index++);
console.log(path);
},
}
);
```
类型
代码
const a = 3;
VariableDeclaration
- kind: const, var, let
- declarations: [VariableDeclarator, VariableDeclarator...]
VariableDeclarator
- id: Identifier
- init: xxxLiteral (NumericLiteral, StringLiteral...)
Identifier
- name: 'xxx'
NumericLiteral, StringLiteral
- value
- extra
- rawValue
- raw
代码
function func(a) {
console.log(a);
return true;
}
FunctionDeclaration
- id: Identifier (如果是匿名函数,id的值为null)
- params: [Identifier, Identifier...]
- body: BlockStatement
BlockStatement
- body: [ExpressionStatement, ReturnStatement, ...]
ExpressionStatement console.log(a)
- expression: CallExpression
CallExpression
- callee: MemberExpression console.log 如果这里直接通过函数名调用函数,比如:atob(a),那么callee就是一个Idetifier
- arguments: [Identifier...]
MemberExpression
- object: Identifier
- property: Identifier
- 可以嵌套,比如:a.b.c 解析为
- MemberExpression
- object: MemberExpression a.b
- object: Identifier a
- property: Identifier b
- property: Identifier c
- object: MemberExpression a.b
- MemberExpression
ReturnStatement
- argument: BooleanLiteral
BooleanLiteral
- value: true, false
代码
a = 1, b = 2;
ExpressionStatement
- expression: SequenceExpression
SequenceExpression
- expressions: [AssignmentExpression...]
AssignmentExpression
- operator: = ...
- left: Identifier
- right: NumericLiteral
代码
let obj = {
name: 'o',
func: function(a, b) {
return a + b + 1000;
}
}
ObjectExpression
- properties: [ObjectProperty, ObjectProperty...]
ObjectProperty
- key: Identifier
- value: (StringLiteral, FunctionExpression...)
BinaryExpression (a + b + 1000 部分)
- left: BinaryExpression (a + b部分)
- operator: "+"
- right: NumericLiteral (1000)
FunctionExpression
- id: null
- params: [Identifier, Identifier...]
- body: BlockStatement
- 看上去跟 FunctionDeclaration 差不多, 只是位置不一样吧?
- FunctionDeclaration;函数声明,用于定义函数
- FunctionExpression:函数表达式,用于语句中
代码
if (a == b) {
} else if (b==c) {
} else {
}
IfStatement
- test: BinaryExpression
- consequent: BlockStatement
- alternate: IfStatement
代码
for(let i = 0; i < 10; i++) {
}
ForStatement
- init: VariableDeclaration
- test: BinaryExpression
- update: UpdateExpression
- body: BlockStatement
UpdateExpression
- operator: ++或--
- argument:
- prefix: 是否前置 true或false
代码
var arr = [1, 2, 3];
ArrayExpression
- elements: [Literal, Literal...]
代码
/*
1
2
*/
str = '123'; // Base64Encrypt
// abc
var a = 1; // base64encrypt
`
block comment
`
ExpressionStatement
- leadingComments: [{type: 'CommentBlock'}]
- trailingComments: [{type: 'CommentLine'}, {type: 'CommentLine'}]
VariableDeclaration
- leadingComments: [{type: 'CommentLine'}, {type: 'CommentLine'}]
- trailingComments: [{type: 'CommentLine'}]
ExpressionStatement
- leadingComments: [{type: 'CommentLine'}]
- expression: TemplateLiteral
- quasis: [TemplateElement]
TemplateElement
- value:
案例:
- 生成语法树 ———— 来自崔大《python3网络爬虫开发实战2》
- 创建目录 learn-ast
- cd learn-ast
- npm init
- npm install -D @babel/core @babel/cli @babel/preset-env
- 新建文件 .babelrc,添加如下内容
{ "presets":[ "@babel/preset-env" ] }
- 新增目录codes,并在目录下新增文件code1.js,代码如下:
const a = 3; let string = "hello"; for (let i = 0; i < a; i++) { string += "world"; } console.log("string", string);
- 回到learn-ast目录,创建文件basic1.js,代码如下:
import { parse } from "@babel/parser"; import fs from "fs"; const code = fs.readFileSync("codes/code1.js", "utf-8"); let ast = parse(code); console.log(ast.program.body);
- 执行:babel-node basic1.js
- 可能出现的问题
- 在powershell下运行报错:...AppData\Roaming\npm\babel-node.ps1,因为在此系统上禁止运行脚本
- 解决办法:在powershell下执行命令set-ExecutionPolicy RemoteSigned
- @babel/core不存在
internal/modules/cjs/loader.js:1032 throw err; ^ Error: Cannot find module '@babel/core'
- 解决办法:全局安装core, npm install -g @babel/core
- 在powershell下运行报错:...AppData\Roaming\npm\babel-node.ps1,因为在此系统上禁止运行脚本
- 可能出现的问题