一. Ast - 反混淆(基础篇)

目录

​编辑

1. 什么是Ast-了解

2. 什么是反混淆-了解

3. 了解Ast结构

4. 思考

5. 前置准备

6. 什么是babel

7. 安装babel

8. ast反混淆代码基本结构

9. babel中的组件

parser与generator 组件

traverse 组件 与visitor

enter与exit

visitor 其他写法

traverse 指定节点向下遍历

types组件

10. path对象

path和node

path常用属性和方法

常用属性:

常用方法:

node节点

scope常用属性和方法

binding常用属性和方法

11. AST 节点类型对照表


AST抽象语法树在线转换工具:AST explorer

  • 这个网站作用:将代码转换成抽象语法树

1. 什么是Ast-了解

  • AST是抽象语法树的缩写。
  • AST是一种用于表示程序代码结构的树状数据结构。
  • 在编译器和解释器中,AST被用于解析和表示源代码的语法结构
  • AST可以看作是源代码的一种抽象表示形式,它去除了源代码中的具体细节,只保留了语法结构和逻辑关系
  • AST中每个节点(Node) 表示源代码的一个语法元素,例如:变量声明,函数定义,循环语句,等,而节点之间的关系表示了语法结构的层次和关联关系
  • 作用:
    • 通过构建AST,编译器解析器可以对源代码进行分析和处理
    • 编译器可以在AST上进行语法检查、类型检查和优化等操作
    • 解析器可以根据AST生成中间代码或目标代码
    • AST还能用于代码重构,代码生成等领域

以下是将正常代码通过在线AST在线网站进行转换为AST语法树

2. 什么是反混淆-了解

什么是混淆?

  • 混淆可以理解为是一种代码加密技术,主要用于隐藏代码的真实功能,以防止代码被逆向工程师分析和修改。通过混淆,让代码变得复杂和难以理解,使得逆向工程师在调试工程中消耗大量的时间或者放弃,从而达到一种保护
  • 混淆总的来说就是一种代码保护方案,将原始代码转换为可读性较差或者没有可读性的代码🤢

什么是反混淆?

  • 反混淆就是将混淆后的代码,还原成具有可读性代码,方便逆向工程师进行调试。

混淆是一种艺术

3. 了解Ast结构

AST在线转换为抽象语法树的网站:AST explorer

  • 这个网站在反混淆中经常用到,因为我们要将被混淆的代码放到上面,然后转换为抽象语法树,然后对抽象语法树的结构进行分析。从而编写还原混淆的代码

在线解析工具使用:

  1. 打开网站后,将parser settings(解析器设置),设置成 @babel-parser,因为我们编写的反混淆代码使用的是 @babel 库,所以选择@babel/parser。在代码中@babel/parser的用处也就是将javascript代码转换为ast语法树结构
  2. 左侧的空白区域我们可以输入一些javascript代码(没混淆的或者混淆后的代码都可以),然后在右侧会展示转换好的ast抽象语法树
  3. 在左侧输入: console.log("0基础菜鸟学习")
  4. 在右侧展示抽象语法树结构的时候,默认的是 树 结构,我们也可以转换为 JSON 结构

节点解释:

  1. type: 表示当前节点的类型
  2. start:表示当前节点的起始位置,图片中的start为0,起始位置是从 c 前面开始起算
  3. end:表示当前节点的末尾
  4. loc:表示当前节点所在的行列位置
    1. loc中的start,表示节点所在起始的行列位置
    2. loc中的end,表示节点所在末尾的行列位置

节点解释:

  • ExpressionStatement表示为当前的代码是一个console.log()
  • ExpressionStatement中会有一个expression的子节点,expression节点的类型是 CallExpression,
  • expression节点中又包含了callee和arguments两个节点,callee可以理解为函数名,而arguments这是它的参数
  • callee节点包含了object,property和computed节点
    • object表示对象,当前对象为 console
    • property表示属性,当前属性为 log
    • computed表示其方式

在后续的ast反混淆编写中,就是将原有的节点进行增删改查。然后得到一个新的代码

4. 思考

场景1:遇到以下情况的时候可以复制粘贴到控制台输出拿到正确值,但是一般不会有这样的场景,因为混淆不可能只混淆一点,而是整个文件代码都混淆,所以想多了

场景2:但是如果遇到这样的场景,如果手动一个一个复制到控制台解开,会很浪费时间,所以学习Ast的用处就是将混淆代码进行批量还原

5. 前置准备

  1. 具备一些javascript基础和nodejs基础
  2. 已安装好nodejs运行环境

6. 什么是babel

  1. 在ast中,babel是一个广泛使用的JavaScript编译器工具。
  2. 它可以将高级的JavaScript代码转换为向后兼容的版本,以便在更旧的浏览器或环境中运行
  3. Babel使用AST(抽象语法树)来分析和修改代码。
  4. 通过在各个阶段插入插件,Babel可以执行各种转换操作,如转换ES6代码为ES5代码、添加Polyfills和补丁、优化代码等。
  5. Babel的主要目标是提供一个灵活且可扩展的工具,以适应不同的项目需求,并帮助开发者更好地利用新的JavaScript语言特性。

7. 安装babel

  • 安装命令:
    • npm i @babel/core --save-dev
    • npm i @babel/types 判断节点类型,构建新的AST节点等
    • npm i @babel/parser 将Javascript代码解析成AST语法树
    • npm i @babel/traverse 遍历,修改AST语法树的各个节点
    • npm i @babel/generator 将AST还原成Javascript代码

8. ast反混淆代码基本结构

以下是使用babel库来反混淆代码的模板

// fs模块 用于操作文件的读写
const fs = require("fs");
// @babel/parser 用于将JavaScript代码转换为ast树
const parser = require("@babel/parser");
// @babel/traverse 用于遍历各个节点的函数
const traverse = require("@babel/traverse").default;
// @babel/types 节点的类型判断及构造等操作
const types = require("@babel/types");
// @babel/generator 将处理完毕的AST转换成JavaScript源代码
const generator = require("@babel/generator").default;


// 混淆的js代码文件
const encode_file = "./encode.js"
// 反混淆的js代码文件
const decode_file = "./decode.js"


// 读取混淆的js文件
let jsCode = fs.readFileSync(encode_file, {encoding: "utf-8"});
// 将javascript代码转换为ast树(json结构)
let ast = parser.parse(jsCode)

// todo 编写ast插件
const visitor = {

}

// 调用插件,处理混淆的代码
traverse(ast,visitor)

// 将处理后的ast转换为js代码(反混淆后的代码)
let {code} = generator(ast);
// 保存代码
fs.writeFile('decode.js', code, (err)=>{});

9. babel中的组件

parser与generator 组件

  • parser和generator这两个组件的作用是相反的。
  • parser用于将js代码转换成ast,generator用于将ast转换成js代码
  • parser将代码转换为ast:
    • let ast = parser.parse(参数一,参数二)
    • 参数一:混淆的js代码
    • 参数二:配置参数
      • sourceType: 默认是script,当解析的js代码中,含有 import,export 等关键字的时候需要指定sourceType为module,不然会报错
  • generator将ast转换为代码:
    • let code = generator(参数一,参数二)
    • 参数一:ast语法树
    • 参数二:配置参数
      • retainLines:表示是否使用与源代码相同的行号,默认为false,也就是输出的是格式化后的代码
      • comments:表示是否保留注释,默认为true
      • compact:表示是否压缩代码,与其相同作用的选项还有minified,concise只不过压缩的程度不一样,minified压缩的最多,concise压缩的最少。
  • parser和generator集合使用的示例代码:
    const fs = require("fs");
    const parser = require("@babel/parser");
    const traverse = require("@babel/traverse").default;
    const types = require("@babel/types");
    const generator = require("@babel/generator").default;
    
    
    const encode_file = "./encode.js"
    const decode_file = "./decode.js"
    
    
    let jsCode = fs.readFileSync(encode_file, {encoding: "utf-8"});
    // 将js代码转换为ast
    let ast = parser.parse(jsCode,{
        sourceType:"script"
    })
    
    // todo 编写ast插件
    const visitor = {
    
    }
    
    traverse(ast,visitor)
    
    let {code} = generator(ast,{
        retainLines:false,
        comments:true,
        compact:"concise"
    });
    fs.writeFile('decode.js', code, (err)=>{});
    

traverse 组件 与visitor

  • traverse 用于遍历和转换抽象语法树(AST)的工具,转换语法树需要配置visitor使用
  • visitor 是一个对象,里面可以定义一些方法,用来过滤节点
    • visitor 示例代码:
      • 1. 在代码中首先声明了visitor对象,对象的名字可以任意取。
      • 2. 在visitor对象中定义一个名为ExpressionStatement的方法,这个方法的名字是需要遍历的是节点类型。
      • 3. traverse会遍历所有节点,当节点类型为 ExpressionStatement时,调用visitor中对应的方法。
      • 4. 如果想要处理其他节点类型,那么可以继续在visitor中继续定义对应的方法
      • 5. visitor 对象中的方法接收一个参数,traverse在遍历的时候会把当前节点的Path对象传给它,传进去的Path对象不是节点node
      • 6. 最后把visitor作为第二个参数传入traverse中,传给traverse的第一个参数是整个ast。
      • traverse(ast,visitor) 的意思是,从头开始遍历ast中的所有节点,过滤出ExpressionStatement节点,执行相应的方法。在ast中如果有多个ExpressStatement就会输出对应的次数

enter与exit

  • 在遍历节点的过程中,实际上有两次机会来访问节点,enter表示进入节点时,exit表示退出节点时。可以在代码中编写遍历时进入要做的操作和退出时要做的操作。

visitor 其他写法

一个函数同时处理两个节点:

  • 可以把方法名用 | 连接成FunctionExpression|BinaryExperssion形式的字符串,把同一个函数应用到多个节点,也就是说函数中的功能可以同时处理FunctionExpression|BinaryExpression节点

多个函数处理一个节点:

  • 把多个函数应用于同一个节点。把函数赋值给enter或exit,将enter改为接收一个函数数组就行

traverse 指定节点向下遍历

  • traverse 可以指定在任意节点向下遍历。
  • 例如,想要把代码中所有函数的第一个参数改为 x

types组件

  • types组件,主要用于判断节点类型,生成新的节点

判断节点:

  • 方式1:不推荐的判断节点类型写法
  • 方式2:
    • 使用types组件提供的节点判断方式,存在返回true,不存在返回false
    • 语法格式:types.is节点名称

生成新的节点:

  • 生成新的节点,然后使用generator组件转换为代码
  • 也可以按照AST的结构来构造一段json数据。然后生成代码,不过还是推荐使用types组件来生成

10. path对象

path和node

path和node区别在于,path对象中包含node。node只是path对象中的一个属性。

path常用属性和方法

常用属性:

  • path.node 获取当前路径对应的节点
  • path.parent 获取当前路径对应节点的父节点
  • path.parentPath 获取当前路径对应节点的父路径
  • path.scope 表示当前path下的作用域,写插件经常用到
  • path.container 获取当前path下的所有兄弟节点(包括自身)
  • path.type 获取当前path的节点类型
  • path.key 获取当前path的key值,key通常用于path.get函数

常用方法:

  • path.get(key) 获取当前路径下指定属性名(key),对应的子路径。
    • 例如:path.get("body") 获取当前路径下名为 body 的子路径
  • path.getSibling(index)    获取当前路径对应节点的兄弟节点的路径。通过指定索引(index)可以获取相应的兄弟路径。
  • path.getFunctionParent()    获取当前路径对应节点的最近的函数父节点的路径。
  • path.getPrevSibling()    获取当前path的前一个兄弟节点,返回的是path类型。
  • path.getAllPrevSiblings()    获取当前path的所有前兄弟节点,返回的是Array类型,其元素都是path类型。
  • path.getNextSibling()    获取当前path的后一个兄弟节点,返回的是path类型。
  • path.getAllNextSiblings()    获取当前path的所有后兄弟节点,返回的是Array类型,其元素都是path类型。
  • path.evaluate()    用于计算表达式的值,大家可以参考 constantFold 插件的写法。
  • path.findParent()    向上查找满足回调函数特征的path,即判断上级路径是否包含有XXX类型的节点。
  • path.find()    功能与 path.findParent 方法一样,只不过从当前path开始进行遍历。
  • path.getFunctionParent()    获取函数类型父节点,如果不存在,返回 null。
  • path.getStatementParent()    获取Statement类型父节点,这个基本上都会有返回值,如果当前遍历的是 Program 或者 File 节点,则会报错
  • path.getAncestry()    获取所有的祖先节点,没有实参,返回的是一个Array对象。
  • path.isAncestor(maybeDescendant)    判断当前遍历的节点是否为实参的祖先节点.
  • path.isDescendant(maybeAncestor)    判断当前遍历的节点是否为实参的子孙节点.
  • path.traverse(visitor)    遍历当前路径下的所有子节点,并应用指定的 visitor。
  • path.replaceWith(node)    用指定的节点替换当前路径对应的节点。
  • path.remove()    从 AST 中移除当前路径对应的节点。
  • path.insertBefore(nodes)    在当前路径对应节点之前插入一个或多个节点。
  • path.insertAfter(nodes)    在当前路径对应节点之后插入一个或多个节点。
  • path.toString()    用于将 AST 节点转换回对应的源代码字符串。

node节点

  • node节点是path对象中的一个属性
  • node节点中常用的属性:
    • path.node.type 获取当前节点类型
    • path.node.declarations    对于 VariableDeclaration 节点, 获取变量声明列表。
    • path.node.init.value    获取某个节点的值。
    • delete path.node.init;    删除节点,使用系统的 delete 方法。

scope常用属性和方法

  • scope.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常用属性和方法

  • binding.path    用于定位初始拥有binding的path;
  • binding.constant    用于判断当前变量是否被更改,true表示未改变,false表示有更改变量值。
  • binding.referenced    用于判断当前变量是否被引用,true表示代码下面有引用该变量的地方,false表示没有地方引用该变量。注意,引用和改变是分开的。
  • binding.referencePaths    它是一个Array类型,包含所有引用的path,多用于替换。

11. AST 节点类型对照表


序号    类型原名称    中文名称    描述
1    Program    程序主体    整段代码的主体
2    VariableDeclaration    变量声明    声明一个变量,例如 var let const
3    FunctionDeclaration    函数声明    声明一个函数,例如 function
4    ExpressionStatement    表达式语句    通常是调用一个函数,例如 console.log()
5    BlockStatement    块语句    包裹在 {} 块内的代码,例如 if (condition){var a = 1;}
6    BreakStatement    中断语句    通常指 break
7    ContinueStatement    持续语句    通常指 continue
8    ReturnStatement    返回语句    通常指 return
9    SwitchStatement    Switch 语句    通常指 Switch Case 语句中的 Switch
10    IfStatement    If 控制流语句    控制流语句,通常指 if(condition){}else{}
11    Identifier    标识符    标识,例如声明变量时 var identi = 5 中的 identi
12    CallExpression    调用表达式    通常指调用一个函数,例如 console.log()
13    BinaryExpression    二进制表达式    通常指运算,例如 1+2
14    MemberExpression    成员表达式    通常指调用对象的成员,例如 console 对象的 log 成员
15    ArrayExpression    数组表达式    通常指一个数组,例如 [1, 3, 5]
16    NewExpression    New 表达式    通常指使用 New 关键词
17    AssignmentExpression    赋值表达式    通常指将函数的返回值赋值给变量
18    UpdateExpression    更新表达式    通常指更新成员值,例如 i++
19    Literal    字面量    字面量
20    BooleanLiteral    布尔型字面量    布尔值,例如 true false
21    NumericLiteral    数字型字面量    数字,例如 100
22    StringLiteral    字符型字面量    字符串,例如 vansenb
23    SwitchCase    Case 语句    通常指 Switch 语句中的 Case

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值