上一篇中主要介绍了 Javascripts AST 中的一些基本知识和简单示例。本篇主要会涉及到 AST 的修改和生成 Javascripts 源文件。

Javascripts 函数作用域

javascripts 里面的作用域可以划分为:

  • 全局作用域
  • 函数作用域
  • 代码块级作用域
const pi = 3.14;

function sum(number_list) {
    let total = 0;
    for (let a in number_list) {
      total += a;
    }
    return total
}

const multi = function(a, b){
  {
    let c = 4;
  }
	return a * b;
}

默认会有一个全局的作用域,除了全局作用域之外,还有两个作用域:

  • function sum 函数作用域
  • for 块级作用域
  • 匿名函数作用域

为啥需要这样分?作用域的本质是用来隔离变量的,变量通俗一点讲就是”资源”,作用域粒度越戏,隔离效果越好,程序设计更加严谨。

如何获取作用域信息?

根据 type 来区分新的作用域。

  • 全局作用域:type: Program
  • 函数作用域:type: FunctionDeclaration
  • 块级作用域:type: BlockStatement
  • 匿名函数作用域: type: FunctionExpression

具体可以参考在线工具 https://astexplorer.net/ , 也可以参考附录·AST 对象树。

如何标记进入了新的作用域?

只需要根据节点的 type 判别即可:

function createNewScope(node){
  return node.type === 'FunctionDeclaration' ||
    node.type === 'FunctionExpression' ||
    node.type === 'Program' ||
    node.type === 'BlockStatement';
}

验证代码 scope_check.js (代码见附录) 如下:

+  enter new scope, type:  Program start:  2
++  enter new scope, type:  FunctionDeclaration start:  4
+++  enter new scope, type:  BlockStatement start:  4
++++  enter new scope, type:  BlockStatement start:  6
----  leave scope, type:  BlockStatement start:  6
---  leave scope, type:  BlockStatement start:  4
--  leave scope, type:  FunctionDeclaration start:  4
++  enter new scope, type:  FunctionExpression start:  12
+++  enter new scope, type:  BlockStatement start:  12
++++  enter new scope, type:  BlockStatement start:  13
----  leave scope, type:  BlockStatement start:  13
---  leave scope, type:  BlockStatement start:  12
--  leave scope, type:  FunctionExpression start:  12
-  leave scope, type:  Program start:  2

可以看到最深的作用域有四级。

如何找出作用域里面定义的变量?

根据作用域的类型,那么变量的类型相应有:

  • 函数参数
  • 全局定义
  • 局部变量

参考附录代码(scope_variables.js):

if (node.type === "VariableDeclarator") {
  var currentScope = scopeList[scopeList.length - 1];
  currentScope[node.id.name] = null;
  if (node.init) {
    currentScope[node.id.name] = node.init.value;
  }
}
if (
  node.type === "FunctionDeclaration" ||
  node.type == "FunctionExpression"
) {
  var currentScope = scopeList[scopeList.length - 1];
  for (const val of node.params) {
    currentScope[val.name] = "funtion_params_" + val.name;
  }
}
if (node.type === "AssignmentExpression") {
  var currentScope = scopeList[scopeList.length - 1];
  currentScope[node.left.name] = node.right.value;
}

执行结果为:

variables inside scope:  { total: undefined }
variables inside scope:  { total: 0, a: null }
variables inside scope:  { number_list: 'funtion_params_number_list' }
variables inside scope:  { c: 4 }
variables inside scope:  {}
variables inside scope:  { a: 'funtion_params_a', b: 'funtion_params_b' }
variables inside scope:  { pi: 3.14, multi: undefined }

思考如何修改函数参数变量的名字

上述例子中,我们已经可以获取各个作用域的变量的列表,并把这些变量存储到列表中了,那么如何修改了?

const pi = 3.14;
function sum(funtion_params_number_list) {
    let total = 0;
    for (let a in funtion_params_number_list) {
        total += a;
    }
    return total;
}
const multi = function (funtion_params_a, funtion_params_b) {
    {
        let c = 4;
    }
    return funtion_params_a * funtion_params_b;
};

上述是替换后的效果,首先需要找到所有的 params 变量并记录下来:

if (
    node.type === "FunctionDeclaration" ||
    node.type == "FunctionExpression"
  ) {
    var currentScope = scopeList[scopeList.length - 1];
    for (const val of node.params) {
      currentScope[val.name] = "funtion_params_" + val.name;
    }
  }

替换所有符合要求的 Identifier :

if (node.type === "Identifier") {
  let _var = findVariableInScopeStack(node.name);
  let regex = RegExp("funtion_params");
  if (_var && regex.test(_var)) {
    node.name = _var;
  }
}

使用递归方式查询变量是否定义:

// find variable in scope stack
function findVariableInScopeStack(name) {
  for (const scope of scopeList) {
    if (name && scope && name in scope) {
      return scope[name];
    }
  }
  return null;
}

最终使用 escodegen 将修改后的 AST 树回写到源文件:

// convert to javascript
console.log("javascript: ");
console.log(escodegen.generate(ast));

完整替换代码参见附录(params_rename.js)。

本节完,下节将使用变量替换方法破解常见的混淆加密方式。

附录

params_rename.js

var esprima = require("esprima");
var estraverse = require("estraverse");
var escodegen = require("escodegen");

var scopeList = [];

const code = `
const pi = 3.14;

function sum(number_list) {
let total = 0;
for (let a in number_list) {
    total += a;
}
return total
}

const multi = function(a, b){
{
    let c = 4;
}
return a * b;
}
`;
var ast = esprima.parse(code, { loc: true });

estraverse.traverse(ast, {
  enter: enter,
  leave: leave
});

// convert to javascript
console.log("javascript: ");
console.log(escodegen.generate(ast));

// enter the node
function enter(node) {
  if (createsNewScope(node)) {
    scopeList.push({});
  }

  if (node.type === "VariableDeclarator") {
    var currentScope = scopeList[scopeList.length - 1];
    currentScope[node.id.name] = null;
    if (node.init) {
      currentScope[node.id.name] = node.init.value;
    }
  }

  if (
    node.type === "FunctionDeclaration" ||
    node.type == "FunctionExpression"
  ) {
    var currentScope = scopeList[scopeList.length - 1];
    for (const val of node.params) {
      currentScope[val.name] = "funtion_params_" + val.name;
    }
  }
  if (node.type === "AssignmentExpression") {
    var currentScope = scopeList[scopeList.length - 1];
    currentScope[node.left.name] = node.right.value;
    // console.log("update variable: ", node.left.name);
  }

  if (node.type === "Identifier") {
    let _var = findVariableInScopeStack(node.name);
    let regex = RegExp("funtion_params");
    if (_var && regex.test(_var)) {
      node.name = _var;
    }
  }
}

// leave the node
function leave(node) {
  if (createsNewScope(node)) {
    scopeList.pop();
  }
}

// new scope
function createsNewScope(node) {
  return (
    node.type === "FunctionDeclaration" ||
    node.type === "FunctionExpression" ||
    node.type === "Program" ||
    node.type === "BlockStatement"
  );
}

// find variable in scope stack
function findVariableInScopeStack(name) {
  for (const scope of scopeList) {
    if (name && scope && name in scope) {
      return scope[name];
    }
  }
  return null;
}

scope_variables.js

var esprima = require("esprima");
var estraverse = require("estraverse");
var escodegen = require("escodegen");

var scopeList = [];

const code = `
const pi = 3.14;

function sum(number_list) {
let total = 0;
for (let a in number_list) {
    total += a;
}
return total
}

const multi = function(a, b){
{
    let c = 4;
}
return a * b;
}
`;
var ast = esprima.parse(code, { loc: true });

estraverse.traverse(ast, {
  enter: enter,
  leave: leave
});

// enter the node
function enter(node) {
  if (createsNewScope(node)) {
    scopeList.push({});
  }

  if (node.type === "VariableDeclarator") {
    var currentScope = scopeList[scopeList.length - 1];
    currentScope[node.id.name] = null;
    if (node.init) {
      currentScope[node.id.name] = node.init.value;
    }
  }
  if (
    node.type === "FunctionDeclaration" ||
    node.type == "FunctionExpression"
  ) {
    var currentScope = scopeList[scopeList.length - 1];
    for (const val of node.params) {
      currentScope[val.name] = "funtion_params_" + val.name;
    }
  }
  if (node.type === "AssignmentExpression") {
    var currentScope = scopeList[scopeList.length - 1];
    currentScope[node.left.name] = node.right.value;
    // console.log("update variable: ", node.left.name);
  }
}

// leave the node
function leave(node) {
  if (createsNewScope(node)) {
    let currentScope = scopeList.pop();
    console.log("variables inside scope: ", currentScope);
  }
}

// new scope
function createsNewScope(node) {
  return (
    node.type === "FunctionDeclaration" ||
    node.type === "FunctionExpression" ||
    node.type === "Program" ||
    node.type === "BlockStatement"
  );
}

scope_check.js

// 遍历所有的作用域并标记
var esprima = require("esprima");
var estraverse = require("estraverse");
var escodegen = require("escodegen");

var scopeList = [];

const code = `
const pi = 3.14;

function sum(number_list) {
let total = 0;
for (let a in number_list) {
    total += a;
}
return total
}

const multi = function(a, b){
{
    let c = 4;
}
return a * b;
}
`;
var ast = esprima.parse(code, { loc: true });

estraverse.traverse(ast, {
  enter: enter,
  leave: leave
});

// enter the node
function enter(node) {
  if (createsNewScope(node)) {
    scopeList.push(node.type);
    console.log("+".repeat(scopeList.length), " enter new scope, type: ", node.type, "start: ", node.loc.start.line);
  }
}

// leave the node
function leave(node) {
  if (createsNewScope(node)) {
    console.log("-".repeat(scopeList.length), " leave scope, type: ", node.type, "start: ", node.loc.start.line);
    scopeList.pop();
  }
}

// new scope
function createsNewScope(node) {
  return (
    node.type === "FunctionDeclaration" ||
    node.type === "FunctionExpression" ||
    node.type === "Program" ||
    node.type === "BlockStatement"
  );
}

AST 对象树

{
  "type": "Program",
  "start": 0,
  "end": 215,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 16,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 6,
          "end": 15,
          "id": {
            "type": "Identifier",
            "start": 6,
            "end": 8,
            "name": "pi"
          },
          "init": {
            "type": "Literal",
            "start": 11,
            "end": 15,
            "value": 3.14,
            "raw": "3.14"
          }
        }
      ],
      "kind": "const"
    },
    {
      "type": "FunctionDeclaration",
      "start": 18,
      "end": 140,
      "id": {
        "type": "Identifier",
        "start": 27,
        "end": 30,
        "name": "sum"
      },
      "expression": false,
      "generator": false,
      "params": [
        {
          "type": "Identifier",
          "start": 31,
          "end": 42,
          "name": "number_list"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 44,
        "end": 140,
        "body": [
          {
            "type": "VariableDeclaration",
            "start": 50,
            "end": 64,
            "declarations": [
              {
                "type": "VariableDeclarator",
                "start": 54,
                "end": 63,
                "id": {
                  "type": "Identifier",
                  "start": 54,
                  "end": 59,
                  "name": "total"
                },
                "init": {
                  "type": "Literal",
                  "start": 62,
                  "end": 63,
                  "value": 0,
                  "raw": "0"
                }
              }
            ],
            "kind": "let"
          },
          {
            "type": "ForInStatement",
            "start": 69,
            "end": 121,
            "left": {
              "type": "VariableDeclaration",
              "start": 74,
              "end": 79,
              "declarations": [
                {
                  "type": "VariableDeclarator",
                  "start": 78,
                  "end": 79,
                  "id": {
                    "type": "Identifier",
                    "start": 78,
                    "end": 79,
                    "name": "a"
                  },
                  "init": null
                }
              ],
              "kind": "let"
            },
            "right": {
              "type": "Identifier",
              "start": 83,
              "end": 94,
              "name": "number_list"
            },
            "body": {
              "type": "BlockStatement",
              "start": 96,
              "end": 121,
              "body": [
                {
                  "type": "ExpressionStatement",
                  "start": 104,
                  "end": 115,
                  "expression": {
                    "type": "AssignmentExpression",
                    "start": 104,
                    "end": 114,
                    "operator": "+=",
                    "left": {
                      "type": "Identifier",
                      "start": 104,
                      "end": 109,
                      "name": "total"
                    },
                    "right": {
                      "type": "Identifier",
                      "start": 113,
                      "end": 114,
                      "name": "a"
                    }
                  }
                }
              ]
            }
          },
          {
            "type": "ReturnStatement",
            "start": 126,
            "end": 138,
            "argument": {
              "type": "Identifier",
              "start": 133,
              "end": 138,
              "name": "total"
            }
          }
        ]
      }
    },
    {
      "type": "VariableDeclaration",
      "start": 142,
      "end": 215,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 148,
          "end": 215,
          "id": {
            "type": "Identifier",
            "start": 148,
            "end": 153,
            "name": "multi"
          },
          "init": {
            "type": "FunctionExpression",
            "start": 156,
            "end": 215,
            "id": null,
            "expression": false,
            "generator": false,
            "params": [
              {
                "type": "Identifier",
                "start": 165,
                "end": 166,
                "name": "a"
              },
              {
                "type": "Identifier",
                "start": 168,
                "end": 169,
                "name": "b"
              }
            ],
            "body": {
              "type": "BlockStatement",
              "start": 170,
              "end": 215,
              "body": [
                {
                  "type": "BlockStatement",
                  "start": 175,
                  "end": 198,
                  "body": [
                    {
                      "type": "VariableDeclaration",
                      "start": 182,
                      "end": 192,
                      "declarations": [
                        {
                          "type": "VariableDeclarator",
                          "start": 186,
                          "end": 191,
                          "id": {
                            "type": "Identifier",
                            "start": 186,
                            "end": 187,
                            "name": "c"
                          },
                          "init": {
                            "type": "Literal",
                            "start": 190,
                            "end": 191,
                            "value": 4,
                            "raw": "4"
                          }
                        }
                      ],
                      "kind": "let"
                    }
                  ]
                },
                {
                  "type": "ReturnStatement",
                  "start": 200,
                  "end": 213,
                  "argument": {
                    "type": "BinaryExpression",
                    "start": 207,
                    "end": 212,
                    "left": {
                      "type": "Identifier",
                      "start": 207,
                      "end": 208,
                      "name": "a"
                    },
                    "operator": "*",
                    "right": {
                      "type": "Identifier",
                      "start": 211,
                      "end": 212,
                      "name": "b"
                    }
                  }
                }
              ]
            }
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "module"
}