AST笔记(技巧,插件)————持续更新

npm install @babel/core (测试安装全局不成功,不知道原因)
查看ast的网站:https://astexplorer.net/
参考自夜幕蔡老板星球文章:
下边是个简单的例子:

const {
    parse
} = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
jscode = 'var s = 92; b = Z(12344, s);'
let ast = parse(jscode);
var test = {}
const visitor =
    {
        'VariableDeclarator|Identifier' (path) {
            console.log(path.toString())
            if (path.isVariableDeclarator()) {
                test[path.node.id.name] = path.node.init.value
            }
            if (path.parent.type ==='CallExpression'&& test.hasOwnProperty(path.node.name)) {
                path.replaceWith(t.valueToNode(test.s))
            }
        }
    }
traverse(ast, visitor);
let {
    code
} = generator(ast);
console.log(code)

常用方法:

图片.png

常用方法补充:

  获取所有的前兄弟节点:path.getAllPrevSiblings()
  获取前一个兄弟节点:path.getPrevSibling()

常用节点:

图片.png

补充:

1、SequenceExpression 一个序列表达式,也就是由逗号分割的表达式序列

    type: "SequenceExpression";
    expressions: [ Expression ];
}

2、。。

常用模板

const fs = require('fs');
const {parse} = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
jscode = 'var a  '
let ast = parse(jscode);
const visitor = 
{
 //下边插件代码
}}
traverse(ast,visitor);
let {code} = generator(ast);
console.log(code)

1、打印原来的节点的源码:

var js = 'var a = 1'
const visitor = 
{
    VariableDeclarator(path){
        console.log(path.toString()); // a = 1
        console.log(generator(path.node).code) // a= 1
}}

2、构造literal (可以代替t.NumericLiteral(234567),t.StringLiteral('1234') 注意:使用这两种过传参类型必须和声明类型一样,如果使用valueNode的话就是传入参数的默认类型)

{
 VariableDeclarator(path){
     console.log(t.valueToNode('123'));  // { type: 'StringLiteral', value: '123' }
     console.log(t.valueToNode(123));  //  { type: 'NumericLiteral', value: 123 }
     console.log(t.valueToNode(null))  //  { type: 'NullLiteral' }
 }}

3、替换replaceWith (path.replaceInline可以用来代替path.replaceWith)

{
    'VariableDeclarator'(path){
        console.log(path.toString());  //a = 1
        path.replaceWith(t.valueToNode(1)) //var 1;
}}

4、删除节点remove

{
    'VariableDeclarator'(path){
        console.log(path.toString())  // a = 1 + 2;b = 2
        if (path.node.id.name === 'b'){
        path.remove()}; 
}} // 还原为var a = 1 + 2;

5、判断节点类型(大部分情况下,在类型前边加is 如:isVariableDeclarator,两种使用方式如下)

{
    'VariableDeclarator'(path){
        console.log(path.toString()) //a = 1 + 2
        console.log(path.type); // VariableDeclarator
        console.log(t.isVariableDeclarator(path.node))  //true
        console.log(path.isVariableDeclarator()) // true
}}

6、节点设置值set(key, node)

{ // 将var a修改为 var a
    'VariableDeclarator'(path){
        console.log(path.toString())  //a
        const {init} = path.node
        console.log(init === path.node.init) //true
        init || path.set('init', t.Identifier("1"))
}} //输出为var a = 1;

7、插入节点

{
    'Identifier' (path) {
        path.insertBefore(t.valueToNode('22')); //节点前插入
        path.insertAfter(t.valueToNode('22'));//节点后插入
    }

8、作用域

scope相关:
path.scope 获取当前路径的作用域
scope.dump() 打印当前作用域

scope.rename(oldName, newName, block)  变量重命名,会修改所有的变量绑定
scope.getBinding(name) 获取当前所有绑定
scope.getBinding(name).referenced 绑定是否被引用
scope.getBinding(name).constantViolations 获取当前所有绑定修改
scope.getBinding(name).referencePaths  获取当前所有绑定路径

二、练习小题:

1、var b = 1+2 --> var b = 3

解1:

BinaryExpression(path) {
            console.log(path) //具体可以打印出来看
            var left = path.node.left.value
            var right = path.node.right.value
            var operator = path.node.operator
            var res = eval(left+operator+right)
            path.replaceInline(t.valueToNode(res))
            },

解2:

BinaryExpression(path) {
        const{confident, value} = path.evaluate();
        path.replaceInline(t.valueToNode(value))
    }

2、简单控制流

var arr = "3|0|1|2|4".split("|");
var cnt = 0;
while (true) {
    switch (arr[cnt++]) {
        case "0":
            console.log("This is case-block 0");
            continue;
        case "1":
            console.log("This is case-block 1");
            continue;
        case "2":
            console.log("This is case-block 2");
            continue;
        case "3":
            console.log("This is case-block 3");
            continue;
        case "4":
            console.log("This is case-block 4");
            continue;
    }
    break;
}

简单还原后的结果为:

console.log("This is case-block 3");
console.log("This is case-block 0");
console.log("This is case-block 1");
console.log("This is case-block 2");
console.log("This is case-block 4");

插件为:

    'WhileStatement'(path){
        // console.log(path.node)
        const {test, body} = path.node;
        if (!t.isBooleanLiteral(test) || test.value !== true){return};
        if (body.body.length === 0 || !t.isSwitchStatement(body.body[0])) {return};
        let switch_state = body.body[0];
        let{discriminant, cases} = switch_state;
        if (!t.isMemberExpression(discriminant) || !t.isUpdateExpression(discriminant.property)){return};
        let arr_name = discriminant.object.name;
        let arr = [];

        let all_pre_siblings = path.getAllPrevSiblings();
        if (all_pre_siblings.length !== 2){return};

        all_pre_siblings.forEach(pre_pth =>{
            const {declarations} = pre_pth.node;
            let {id, init} = declarations[0];
            if (arr_name == id.name){
                arr = init.callee.object.value.split('|');
            }
            pre_pth.remove();
        });
        let ret_body = [];
        arr.forEach(index =>{
            let case_body = cases[index].consequent;
            if (t.isContinueStatement(case_body[case_body.length -1]))
                {case_body.pop()}
            ret_body = ret_body.concat(case_body)
        })
        path.replaceInline(ret_body)
    }

3、替换

!function(a,b) {c = a | b;}(111,222);==>!function() {c = 111 | 222}();
好玩的地方是,通过函数内的作用域找到,在函数中的变量引用,注意,下边代码得作用域是函数的作用域,导致如果出现啊引用,将无法替换

CallExpression(path) {
        let callee = path.get('callee');
        let arguments = path.get('arguments');
        if (!callee.isFunctionExpression() || arguments.length === 0) { //这里实参的长度判断可以写死。
            return;
        }
        //对着网站解析,获取形参
        let params = callee.get('params');
        let scope = callee.scope;
        for (let i = 0; i < arguments.length; i++) { //遍历实参,因为形参可能比实参长。
            let arg = params[i];
            let {
                name
            } = arg.node;
            const binding = scope.getBinding(name); // 在当前作用域中,找到参数的引用
            console.log(binding)
            if (!binding || binding.constantViolations.length > 0) { //形参有被改变,不能被还原
                continue; // 表示在当前作用域中的引用,因为现在的作用域是整个函数,所有如果'!function(a,b) {c = a | b;a=1}(111,222);'或者'!function(a,b) {a=1;c = a | b}(111,222);'是无法修改a的值的
            }
            for (refer_path of binding.referencePaths) { //因为是字面量,所以直接替换,但是遇到Array类型的需要另外处理
                //也无非获取父节点,然后判断索引
                refer_path.replaceWith(arguments[i]);
                //上面的参数可以是path,也可以是node。但是我遇到过path报错,node可以的情况。
            }
            arg.remove();
            arguments[i].remove();
        }
    },

4、作用域内变量替换

var a = window;let b = a.btoa("Hello,AST"); =>let b = window.btoa("Hello,AST");

VariableDeclarator(path) {
        const {id, init} = path.node;
        if (!t.isIdentifier(init,{name: "window"})){return}
        let name = id.name
        let scope = path.scope;
        const binding = scope.getBinding(name); // 获取windows在当前节点的作用域中的值
        if (!binding || binding.constantViolations.length!==0){return}; 
        scope.rename(name, 'window')  //重点,上边有笔记
        path.remove()
    }

5、exit使用

var a = "Hello,AST"["charCodeAt"](5); => var a= 44

const visitor = {
    "MemberExpression"(path)
    {
        let property = path.get('property');
        if (property.isStringLiteral())
        {
            let value = property.node.value;
            path.node.computed = false;
            property.replaceWith(t.Identifier(value));
        }
    },
    "CallExpression":{
        exit:function(path)
        {
            const {confident,value} = path.evaluate();
            confident && path.replaceInline(t.valueToNode(value));
        }
    },
}

6、if_to_switch

输入:

function test()
{
    var index = 0,arr = [3, 0, 2, 1],pindex;
    while (1)
    {
        pindex = arr[index++];
        if (pindex<1){
            console.log("this is index 0");
        }else if (pindex<2){
            return;
        }
        else if (pindex<3){
            console.log("this is index 2");
        }   else {
            console.log("Hello world!");
        }
    }
}

输出:

function test() {
  var index = 0,
      arr = [3, 0, 2, 1],
      pindex;

  while (1) {
    switch (arr[index++]) {
      case 0:
        console.log("this is index 0");
        break;

      case 1:
        return;
        break;

      case 2:
        console.log("this is index 2");
        break;

      default:
        console.log("Hello world!");
        break;
    }
  }
}

test();

插件:

visitor1 = {
    'WhileStatement': function (path) {
        let case_item = {}
        let discriminant
        function handle(node) {
            if (t.isIfStatement(node)) {
                let left_name = node.test.left.name  // 获得条件左边变量名称
                if (!eval(left_name) && eval(left_name)!== 0){ // 如果arr数组取索引为undefind就结束循环
                    end_bool=true}
                let right_value = node.test.right.value // 获得条件右边的值
                if (eval(left_name) === right_value) {  // 如果左右相等,就是获得的这个值,放到case_item中
                    case_item[right_value] = node.consequent.body[0] // 定义对象,把代码和数值对应
                } // 如果左右不相等,进行递归,一直找到相等的
                handle(node.alternate) // if 条件是循环相套的,所以需要进行递归
            }
            else {
                case_item['default'] = node.body[0] // 如果不是循环条件,就是else中的值
            }  // 否则对else进行默认处理
        }

        let body2 = path.node.body.body[1]  // 得到循环体
        if (!t.isIfStatement(body2)) {
            return
        }  // 判断类型

        // 获取函数内初始数据,并执行,获得初始变量作用域
        let parentpath = path.parentPath
        let body1 = parentpath.node.body[0]
        eval(generator(body1).code)

        discriminant = path.node.body.body[0].expression.right
        let end_bool = false
        while (1) {
            if(end_bool){break}
            let body0 = generator(path.node.body.body[0]).code
            eval(body0) // 执行初始值 pindex = arr[index++];获得条件参数变量作用域
            handle(body2)
        }


        // 构造switch
        let case_list = []
        for (key in case_item){
            if (key !== 'default'){
                console.log(key)
                console.log(case_item[key])
            case_list.push(t.switchCase(t.NumericLiteral(parseInt(key, 10)-1), [case_item[key], t.BreakStatement()]))
        }
        else{
            case_list.push(t.switchCase(undefined,[case_item['default'], t.BreakStatement()]))
            }}
        new_node = t.switchStatement(discriminant, case_list)

        path.get("body.body.1").replaceInline(new_node)
        path.get("body.body.0").remove()
    }
}


三、绝密插件

1、变量的还原

# binding.referencePaths获取变量所有的引用
VariableDeclarator(path) {
      const {id,init} = path.node;
      if (!t.isLiteral(init)) {return};
      let name = id.name;
      const binding = path.scope.getBinding(name);
      if (!binding || binding.constantViolations.length > 0) {return};
      for (let refer of binding.referencePaths) {refer.replaceWith(init);};
      path.remove()
  }
#如果遍历的对象是FunctionDeclaration
# const binding = path.scope.parent.getBinding(name);

2、三元表达式,节点替换
输入:a = m?11:22
输出: m ? a = 11 : a = 22;

    ConditionalExpression(path) {
        let {test, consequent, alternate} = path.node; //分别获得m、11、22的路径
        const a = path.parentPath.isAssignmentExpression(); // 判断父类路径是不是这个节点
        if (a) {
            let {
                operator,
                left
            } = path.parentPath.node; // 如果是这个节点,获得当前节点的符号和赋值变量
            if (operator == '=') {  // 如果节点是赋值节点
                consequent = t.AssignmentExpression('=', left, consequent), // 改写为Ass节点
                alternate = t.AssignmentExpression('=', left, alternate),
                path.parentPath.replaceWith(t.ConditionalExpression(test, consequent, alternate)) // 改写为Con节点,并替换原节点
            }
        }
    },

3、变量声明,查找

"VariableDeclarator"(path)
    {
        let {id,init} = path.node;
        if (init !== null) return;
        const binding = path.scope.getBinding(id.name);
        
        if (!binding || binding.constantViolations.length !== 1) return; 
        
        let vio_path = binding.constantViolations[0];
        if (vio_path.isAssignmentExpression())
        {
            const {left,operator,right} = vio_path.node;
            if (operator === '=' && t.isLiteral(right))
            {
                path.set('init',right);
                vio_path.remove();
            }
        }
    },

4 、某音web计算

const visitor =
    {
        'BinaryExpression'(path) {
            if (path.toString() === '0 / 0') {
                return
            } //这种情况下会出现替换失败
            let test_right = path.get("right");
            let test_left = path.get("left");
            if (!path.parentPath.node && path.parentPath && path.parentPath.isBinaryExpression()) { // 如果有父路径并且父节点类型是二进制的,返回
                return
            }
            // 如果左边节点和右边节点 包含二进制或者数字面量
            if (test_right.isBinaryExpression() || test_left.isBinaryExpression() || test_left.isNumericLiteral() || test_right.isNumericLiteral()) {
                const {value} = path.evaluate();
                if (typeof value === 'number') { // 一定要判断数据类型
                    path.replaceInline(t.valueToNode(value))
                }
            }
        },
        'NumericLiteral'(path) { // 16进制转十进制
            if ((path.node.extra !== undefined) && (path.node.extra.raw === path.toString())) {
                let value = path.node.value
                path.replaceInline(t.valueToNode(value))
            }
        }

    }

5、补全if语句
if (1 === 1) b = 2, c = 3; else { c = 4 } => if (1 === 1) {b = 2, c = 3} else { c = 4 }
(1)转成代码进行替换

const visitor =
    {
        SequenceExpression: {
            exit(path) {
                if (path.parentPath.isExpressionStatement() && path.parentPath.parentPath.isIfStatement()) {
                    js_code1 = parse(
                        `
                    {${path.toString()}}
                    `
                    )
                    path.parentPath.replaceWith(js_code1.program.body[0])
                }
            }
        }
    }

(2)节点转化替换

const visitor =
    {
        SequenceExpression: {
            exit(path) {
                if (path.parentPath.isExpressionStatement() && path.parentPath.parentPath.isIfStatement()) {
                    const node = path.node;
                    path.parentPath.replaceWith(t.BlockStatement([t.ExpressionStatement(node)]))
                }
            }
        }
    }

某里滑块三元表达式转if

void(1 - 1 ? aa = b + 4 : c + 5 ? cc = a + 1 : b + 2 ? ee = c + 3 : ff = 4)
=>
if (1 - 1) { aa = b + 4 } else { if (c + 5) { cc = a + 1 } else { if (b + 2) { ee = c + 3 } else { ff = 4 } } }

const visitor =
    {
        ConditionalExpression: {
            exit(path) {
                let {test, consequent, alternate} = path.node;
                let new_consequent = types.BlockStatement([types.ExpressionStatement(consequent)]);
                let new_alternate = types.BlockStatement([types.ExpressionStatement(alternate)]);
                let if_node = types.IfStatement(test, new_consequent, new_alternate);
                path.replaceWithMultiple(if_node);
                path.stop()
            },
        }

    }


const visitor1 =
    {
        UnaryExpression(path) {
            let operator = path.node.operator;
            if (operator === "void") {
                path.replaceWithMultiple(path.node.argument)
            }
        },
    }

traverse(ast, visitor1);
traverse(ast, visitor);

某数vmp版本的if else 转switch case注:一定要补全if else的{}

const {
    parse
} = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
const fs = require('fs')
jscode = `
 if (_$jN <= 3) {
                if (_$jN <= 0) {
                    _$bR[4] = 2;
                    _$dG = _$hB;
                } else {
                    if (_$jN <= 1) {
                        _$dz = _$jE[--_$$g];
                        _$$J = _$hz[++_$dG];

                        if (_$dz) {
                            0;
                        } else {
                            _$dG += _$$J;
                        }
                    } else {
                        if (_$jN <= 2) {
                            _$aB();

                            _$aK = _$aK[_$ac];
                            _$dz = _$aK();
                        } else {
                            _$jE[_$$g++] = _$bU[_$hz[++_$dG]];
                        }
                    }
                }
            }`
// var jscode = fs.readFileSync('./test.js').toString()

let ast = parse(jscode);

var test = {}


const visitor =
    {
        BlockStatement: function (path) {
            if (path.parentPath.node.type !== "IfStatement"){return}

            var b1 = path.parentPath.node.test.type === "Identifier" ||
                path.parentPath.node.test.type === "UnaryExpression" || // 当前节点的条件是否有判断
                path.parentPath.node.test.type === "MemberExpression"// 当前节点的条件是否有判断
            if (b1) {
                return
            }

            var has_if = false
            path.node.body.forEach(function (b) { // 如果子节点不带if判断的字节点才能继续
                if (b.type === "IfStatement") {
                    if (b.test.type === "Identifier" || b.test.type === "UnaryExpression" || b.test.type === "MemberExpression") {
                        has_if = true
                    } else {
                        has_if = false
                    }
                } else {
                    has_if = true
                }
            })
            if (has_if === false) {
                return
            }
            var test_value;
            if (path.parentPath.node.alternate === path.node) {// 向父节点查找
                test_value = path.parentPath.node.test.right.value + 1
            }
            if (test_value === undefined) {
                test_value = path.parentPath.node.test.right.value
            }

            var body_code = ''
            path.node.body.forEach(function (x) {
                body_code += generator(x).code
            })
            test[test_value] = body_code

        }
    }
traverse(ast, visitor);
generator(ast);
var code = `
    switch (_$jN) {
`
console.log(test)
for (var x in test) {
    var c_code = 'case ' + x + ':' + test[x] + 'break;'
    code += c_code
}
code += '}'
console.log(code)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容