js逆向--基于babel学习AST(Abstract Syntax Tree, 抽象语法树)

AST (Abstract Syntax Tree, 抽象语法树)

Babel

安装

  • 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;  # 这个才是变量的真实所属作用域
            }
        });
        
    • block:当前节点所在作用域节点
    • path:当前节点所在作用域节点的path
    • getBinding('name'): 在当前及父级作用域内查找
    • getOwnBinding('name'): 在当前作用域内查找
  • 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
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
      • 可能出现的问题
        1. 在powershell下运行报错:...AppData\Roaming\npm\babel-node.ps1,因为在此系统上禁止运行脚本
          • 解决办法:在powershell下执行命令set-ExecutionPolicy RemoteSigned
        2. @babel/core不存在
          internal/modules/cjs/loader.js:1032
              throw err;
              ^
          
          Error: Cannot find module '@babel/core'
          
          • 解决办法:全局安装core, npm install -g @babel/core
posted @ 2022-03-25 14:07  liDB  阅读(450)  评论(0编辑  收藏  举报