我的理解:AST相当于把js代码所有语法解析为抽象的树.用处大概就是逆向的时候把混淆的代码还原逻辑,方便看逻辑.,以下所有笔记都是抄自悦来客栈的老板的星球

一. 直观地看AST

AST在线解析
用这个网站输入JS源码就可以看到AST解析出来的语法树了

  • type节点类型
  • 结构path->node->type
  • start,end节点起始.结尾位置
  • program源代码
  • comments注释
    具体还是输入代码到网站上看对应的类型,这样比较快速学习
    偷图地址在水印

二.代码框架

来源:悦来客栈的老板的星球


//babel库及文件模块导入
const fs = require('fs');//babel库相关,解析,转换,构建,生产
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const types = require("@babel/types");
const generator = require("@babel/generator").default;//读取文件
let encode_file = "./encode.js",decode_file = "./decode_result.js";
if (process.argv.length > 2)
{encode_file = process.argv[2];
}
if (process.argv.length > 3)
{decode_file = process.argv[3];
}let jscode = fs.readFileSync(encode_file, {encoding: "utf-8"});
//转换为ast树
let ast    = parser.parse(jscode);const visitor =
{//TODO  write your code here!
}//some function code//调用插件,处理源代码
traverse(ast,visitor);//生成新的js code,并保存到文件中输出
let {code} = generator(ast);
fs.writeFile('decode.js', code, (err)=>{});

三. bable库解析

这个抄的好啊https://blog.csdn.net/weixin_33826609/article/details/93164633
复制的是官方文档,但是上面的搜索比较方便
这个介绍的挺好https://www.cnblogs.com/YikaJ/p/10073540.html

我的笔记:

  • Bable三大步:解析(parse),转换(transform),生成(generate)
  1. 解析
    解析步骤接收代码并输出 AST。 这个步骤分为两个阶段: 词法分析 -> 语法分析。
    然而我们看到的一般都是语法分析后的

  2. 转换(核心)
    简单来说就是对节点的增删改查

  3. 生成
    把AST还原成源码,深度优先遍历AST,然后构建转换后的代码字符串

  • 其他看文章吧…没得精简的了.还需走一下文章内的demo流程

四. 直接实战

建议每个实践都对比一下AST树,大概就可以理解了

1.处理十六进制、中英文Unicode字符串或数值

从\x6c\x6f\x67 -> log

混淆代码


var _0x1201 = ['\x6c\x6f\x67', '\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21'];
(function(_0x2b91f5, _0x120157) {var _0x2e36e7 = function(_0x40e9dc) {while (--_0x40e9dc) {_0x2b91f5['\x70\x75\x73\x68'](_0x2b91f5['\x73\x68\x69\x66\x74']());}};_0x2e36e7(++_0x120157);
}(_0x1201, 0xa3));
var _0x2e36 = function(_0x2b91f5, _0x120157) {_0x2b91f5 = _0x2b91f5 - 0x0;var _0x2e36e7 = _0x1201[_0x2b91f5];return _0x2e36e7;
};
function hi() {var _0x379bb0 = _0x2e36;console[_0x379bb0('\x30\x78\x31')](_0x379bb0('\x30\x78\x30'));
}
hi();

解混淆代码
输入到第二点的框架中运行

const transform_literal = {NumericLiteral({node}) {if (node.extra && /^0[obx]/i.test(node.extra.raw)) {node.extra = undefined;}},StringLiteral({node}) {if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {node.extra = undefined;}},
}

解析:
适配是乱码的部分,删除掉extra节点(在AST中不是必须的),然而其value节点是可阅读的字符串,所以会显示解析完得字符串,具体去AST解析那儿看看就知道.

2. MemberExpression和ObjectProperty key值Literal化

从 a.length -> a[‘length’]

源代码:

var a = b.length;

解混淆代码

const member_property_literals = {MemberExpression:{exit({node}){const prop = node.property;if (!node.computed && types.isIdentifier(prop)){node.property = types.StringLiteral(prop.name);node.computed = true;}}},  ObjectProperty: {exit({node}){const key = node.key;if (!node.computed && types.isIdentifier(key)){node.key = types.StringLiteral(key.name);}}},
}

解析:
通过把Identifier构建为StringLiteral实现

3.优化无实参的自执行函数

!(function()
{
a = b;
})();
->
a = b;

反混淆代码

const simplify_auto_exec = {UnaryExpression(path) {let { operator, argument } = path.node;if (operator != "!" || !types.isCallExpression(argument)) return;//找到"!"和Call类型的let { arguments, callee } = argument;if (arguments.length != 0 || !types.isFunctionExpression(callee)) return;let { id, params, body } = callee;if (id != null || params.length != 0 || !types.isBlockStatement(body)) return;path.replaceWithMultiple(body.body);},
}

解析:
其实就是将函数体里面的内容替换整个表达式

1.因为是替换表达式,因此这里遍历UnaryExpression节点,这个可以在对照网站看的很清楚。
2.特征判断,UnaryExpression表达式的operator节点为符号"!";而argument节点是CallExpression类型
3.再次对argument节点进行特征判断,具体见代码。
4.满足条件后进行替换。

4.JavaScript全局函数计算值替换

var a = parseInt(“12345”,16),b = Number(“123”),c = String(true),d = unescape(“hello%2CAST%21”);
eval(“a = 1”);
->
var a = 74565,b = 123,c = “true”,d = “hello,AST!”;
eval(“a = 1”);

反混淆代码

const evaluate_global_func =
{"CallExpression"(path){let {callee,arguments} = path.node;if (!types.isIdentifier(callee) || callee.name == "eval") return; //跳过eval,限定替换Identifierif (!arguments.every(arg=>types.isLiteral(arg))) return;//需要不是Literallet func = global[callee.name];if (typeof func !== "function") return;//获取是否自带的对象,不是的话跳过let args = [];arguments.forEach((ele,index) =>{args[index] = ele.value;});//有些值可能是数组let value = func.apply(null,args);//主动调用.例如->Number('123')if (typeof value == "function") return;//调用完如果是函数就过滤path.replaceInline(types.valueToNode(value));},
}

解析:
开始变得基操了…但是需要对节点的理解才能不误伤不想修改的节点…

5.其它

Unicode转中文,代码压缩,删除注释,删除空行
删除空行和空语句(比较简单,放在一起)
删除辣鸡代码
删除屎代码

直接看文章吧,这部分属于善后工作

6.还原简单的CallExpression 类型 实战

这个时候需要插入蔡老板的node,path入门教学
自己写的代码,应该可以适配更多类型

源码

let a = 111;
var all = function () {var Xor = function (p, q) {return q + p;};let b = 222;let a = Xor (b,a);
};
var all2 = function () {let b = 444;
};

反混淆源码


const call2Express = {CallExpression(path) {let { arguments, callee } = path.node;let arg = [];arguments.every(element => {//获取这个Call的所有变量if (types.isLiteral(element)) {//常规类型arg.push(element.value);} else if (types.isIdentifier(element)) {//变量的话找一下有没有let elementName = element.name;path.findParent((pathTemp) => {if (pathTemp.isVariableDeclaration()) {// console.log(pathTemp.container);let argTemp = '';pathTemp.container.every(node1 => {//有的话存到数组中node1.declarations.every(node2 => {if (node2.id.name === elementName && types.isLiteral(node2.init)) {argTemp = node2.init.value;console.log(argTemp);return false;}return true;})return true;});if (argTemp !== '') {arg.push(argTemp);return true;}}});} else {return false;};return true;})if (arg.length === arguments.length) {let functionName = callee.name;path.findParent((pathTemp) => {//找到对应的方法if (pathTemp.isVariableDeclaration()) {pathTemp.container.forEach(node => {if (node.declarations.length != 1) return;if (node.declarations[0].id.name === functionName) {let { code } = generator(node);let argStr = '';for (let index = 0; index < arg.length; index++) {const element = arg[index];argStr += (',' + element);}argStr = argStr.substring(1);let res = eval(`${code} ${functionName}(${argStr});`);//运行结果path.replaceInline(types.valueToNode(res));//替换路径// console.log(res);return;}});return;}});}},
}

解析在注释,目前可以一直,实际还可以深入,例如把一些不是调用的常量相加."a=b+c,b=1,c=2"这种也加起来

6.

五.小知识点

全在蔡老板的星球和他公众号学的

  • 在AST获取代码,构建Function并执行获取结果
//获取赋值语句左边的 a
let expression = body[1].node.expression;
let name = expression.left.name;
//根据Function函数进行构造
let code = path.toString() + "\nreturn " + name;
//构造并运行,即可得到for循环的结果
let func = new Function("",code);
let value = func();
  • 待补充

笔记 https://mp.weixin.qq.com/s/yFcSXmXChGNaT7wPlaj0uw

构建节点的时候查询 -> https://babeljs.io/docs/en/babel-types

binding (只有变量定义和函数定义有)

变量定义:var a = 123; 这里的 a 就拥有 binding。
函数定义 function test(a,b,c) {}; a,b,c 有binding

  • let binding = scope.getBinding(name) 获取binding
  • binding.referencePaths可以获取到所有调用的地方,然后进行替换
  • binding.constant 表示是否可被修改, true才能改
  • binding.path 用于定位初始拥有binding的path;
  • binding.referenced 用于判断当前变量是否被引用,true表示代码下面有引用该变量的地方,false表示没有地方引用该变量。注意,引用和改变是分开的。
  • binding.constantViolations 它是一个Array类型,包含所有改变的path,多用于判断

path https://mp.weixin.qq.com/s/gd7anzKk4dFYuv_bGEpNOg

scope

  • scope.block 表示当前作用域下的所有node,参考上面的 this.block = node;
  • scope.dump() 输出当前每个变量的作用域信息。调用后直接打印,不需要加打印函数
  • scope.crawl() 重构scope,在某种情况下会报错,不过还是建议在每一个插件的最后一行加上。
  • scope.rename(oldName, newName, block) 修改当前作用域下的的指定的变量名,oldname、newname表示替换前后的变量名,为字符串。注意,oldName需要有binding,否则无法重命名。
  • scope.traverse(node, opts, state) 遍历当前作用域下的某些(个)插件。和全局的traverse用法一样。
  • scope.getBinding(name) 获取某个变量的binding,可以理解为其生命周期。包含引用,修改之类的信息

AST学习笔记 至少入个大门相关推荐

  1. COBOL 学习笔记 之 入門篇(续集)

    书接上一回(COBOL 学习笔记 之 入門篇 ) 从程序可以看到,COBOL程序分为四部分: IDENTIFICATION DIVISION.  ENVIRONMENT    DIVISION.  D ...

  2. JDT AST学习笔记

    JDT AST学习笔记 学有所成啦!见https://github.com/Foxssss/ASTLearning 1.在IfStmt中的ThenStmt有两种情况: if (a == b)a = 1 ...

  3. 肝货满满!CV学习笔记:入坑必备

    知乎:云时之间 链接:https://zhuanlan.zhihu.com/p/102044405 编辑:王萌 作者的话 最近因为一些原因被安排去做关于目标跟踪的一些工作,对我来说可谓是一个很大的挑战 ...

  4. 《neuralnetworks and deeplearning》学习笔记1-深入理解BP算法

    http://neuralnetworksanddeeplearning.com 一,损失函数的两个假定: 1,成本函数可被写为C = 1 / N ΣxCx  ,  Cx  针对单个x . 原因:BP ...

  5. EJB3.0学习笔记--SOAP-AXIS--深入Soap引擎

    1.SOAP: 简单对象访问协议,简单对象访问协议(SOAP)是一种轻量的.简单的.基于 XML 的协    议,它被设计成在 WEB 上交换结构化的和固化的信息. SOAP 可以和现存的许多因特网 ...

  6. Qt5.9学习笔记2-输入/显示/按钮/菜单/时间/字体/图片

    一般属性在类的接口中有读取函数和设置函数,对于设置属性函数就是属性名前加set 如QSpinBox类中有属性value用来显示组件当前值,可以使用setValue()方法来设置该组件的值. 1.输入组 ...

  7. C语言学习笔记02-输入输出运算符

    输出输入运算符 输出和输入 printf()的基本用法 scanf()的基本用法 输入(输出)控制符 运算符 算数运算符 关系运算符 逻辑运算符 赋值运算符 输出和输入 printf()的基本用法 p ...

  8. Robot Framework学习笔记5-导入Selenium2Library库报错的解决办法

    我们在创建第一个自动化脚本的时候,要导入Selenium2Library库,然后会出现红色的字体: 红色代表错误,没有该库,意思就是安装的时候没有安装成功.接着用命令安装一下这个库,结果出现如下的图: ...

  9. y空间兑换代码_【CV学习笔记】色彩空间

    关注"深度学习冲鸭",一起学习一起冲鸭! 设为星标,第一时间获取更多干货 作者:云时之间来源:知乎链接:https://zhuanlan.zhihu.com/p/103387082 ...

最新文章

  1. 十大编程算法助程序员走上高手之路
  2. java 手动线程调度_Java Thread 多线程 操作线程
  3. HarmonyOS之AI能力·文档检测校正
  4. mysql读锁和写锁
  5. Java代码实现Fibonacci数列
  6. Angualr8 ViewChild报错
  7. 德鲁伊 oltp oltp_内存中OLTP系列–简介
  8. Jquery自定义$的名称(自定义变量)
  9. 计算机考试中如何设置表格外边框,Excel表格中怎么为单元格区域设置边框
  10. 初识C语言,一起迈入编程世界的大门
  11. Matlab快速创建矩阵的方法(创建特殊矩阵)
  12. 解决viewer.js预览PDF文件 无法展示PDF水印的问题
  13. .mat图像显示(MATLAB实现)
  14. 涵洞CAD系统必须实现的功能
  15. jsx中文是什么牌子口红_cl口红是什么牌子 cl口红中文名字
  16. 苹果6如何截屏_苹果商量里需要花6元,才能买到的游戏:论如何建立一个修仙门派,到底好不好玩...
  17. MARKETS AND MARKET LOGIC——The Market‘s Principles (6)_3
  18. Cadence Allegro 如何隐藏和显示铜皮
  19. Vue中 Vue.component() 的使用
  20. PMP考试难不难通过率怎样?

热门文章

  1. Django--中间件
  2. 离散数学 —— 二元关系(图、零图与平凡图、度、握手定理、平行边、简单图与完全图、补图、子图与生成子图、同构、通路与回路、点与边割集、最短路线问题、强弱联通图、邻接矩阵与可达矩阵、欧拉图、平面图等)
  3. Krypital Group:dYdX「背叛」以太坊,应用链会成为Dapp的主流叙事么?
  4. java国内外详情研究动态,国内外研究现状分析及文献综述.doc
  5. HTML-通过点击网页上的文字弹出QQ添加好友页面
  6. 多分类-- ROC曲线和AUC值
  7. 2018端午小长假人气榜发布:上海迪士尼蝉联景区人气榜首
  8. 7种Dos攻击和防范方法
  9. 我只是穴居人-克拉克/惠勒定律的汉塞尔曼推论
  10. python短信验证码 容联云