[逆向知识] AST抽象语法树:混淆与反混淆的逻辑互换(一)

博客配套代码发布于github:半自动化cookie更新(欢迎顺手Star一下⭐)

相关逆向知识:

[逆向知识] AST抽象语法树:混淆与反混淆的逻辑互换(二)-CSDN博客

相关爬虫专栏:JS逆向爬虫实战  爬虫知识点合集  爬虫实战案例 逆向知识点合集


前言:

AST作为逆向知识中一个非常重要的知识点,在面对难度稍大的反爬时必须知道其概念与处理的方式。本文将全面讲解AST抽象语法树的相关知识以及处理方法,先对其有个相对清晰的认知。

而在AST(二)的下一篇文章中,我们将进一步插入大量图片与实际演示代码辅助理解,尽可能帮助读者掌握混淆代码的应对方式。

一、 什么是抽象语法树(AST)?

在我们深入探讨混淆技术之前,必须先理解编译器(或解释器)是如何“阅读”我们的代码的。当你写下一行代码时,例如:

var result = 10 + (20 / 2);

计算机并不能直接理解这个字符串。它需要经过几个阶段的处理:

  • 词法分析: 将代码字符串分解成一个个有意义的最小单元,称为“Token”。例如,var, result, =, 10, +, (, 20, /, 2, ), ;

  • 语法分析: 根据编程语言的语法规则,将这些Token组合成一个树形结构,这个结构就是AST

AST精确地描绘了代码的语法结构。对于上面那行代码,其AST(简化后)长这个样子:

- VariableDeclaration (声明一个变量)- id: Identifier { name: 'result' } (变量名)- init: BinaryExpression (初始值为一个二元表达式)- operator: '+' (运算符)- left: Literal { value: 10 } (左操作数)- right: BinaryExpression (右操作数是另一个二元表达式)- operator: '/'- left: Literal { value: 20 }- right: Literal { value: 2 }

核心要点:AST抛弃了代码中的空格、注释和括号等非结构性元素,只保留了代码的骨架和逻辑。这使得我们可以通过程序来分析、修改和生成代码,而这正是混淆与反混淆的技术基石。

二、 利用AST进行代码混淆:把清晰变模糊

代码混淆的目的是在不改变代码执行结果的前提下,使其逻辑变得难以阅读和分析。有了AST,我们不再需要用复杂的正则表达式去替换字符串,而是可以直接操作代码的“骨架”。

以下是几种常见的基于AST的混淆技术:

  1. 标识符重命名

    • 逻辑:遍历AST中的所有 Identifier(标识符) 节点,将有意义的变量名(如 resultuserName)替换成无意义的短字符(如 _0x1a2b_0x5c8f)。

    • AST操作:找到所有定义变量/函数的作用域,记录下其中的标识符,然后将它们和所有引用到它们的地方统一重命名。

    • 示例

      // 原始代码
      function calculateSum(a, b) {var total = a + b;return total;
      }// 混淆后代码
      function _0xabc1(a, b) {var _0xdef2 = a + b;return _0xdef2;
      }
      
  2. 常量替换与表达式转换

    • 逻辑:将代码中的常量(如数字、字符串)替换成一个等价的、但更复杂的表达式。

    • AST操作:找到 Literal(字面量) 节点,并将其替换为一个 BinaryExpression(二元表达式) 或其他更复杂的结构。

    • 示例

      // 原始代码
      var key = "secret";
      var value = 1000;// 混淆后代码
      var key = "\x73\x65\x63\x72\x65\x74"; // 字符串拆分为十六进制表示
      var value = 500 + 500; // 数字拆分为表达式
      
  3. 控制流平坦化

    • 逻辑:将代码块(如 if/elsefor 循环)打散,放进一个巨大的 switch 语句中,由一个状态变量来控制执行顺序。这使得代码的执行流程不再是线性的,而是跳跃式的,极大地干扰了静态分析。

    • AST操作:识别出代码中的基本逻辑块,将它们包裹成 case 语句。然后用一个 while 循环和 switch 语句替换掉原始的控制流结构(如 IfStatementForStatement 等)。

    • 示例

      // 原始代码
      function checkAccess(level) {let message;if (level > 5) {message = "Allowed";} else {message = "Denied";}return message;
      }// 混淆后代码 (简化版)
      function checkAccess(level) {let message;var state = '1|0|2'.split('|');var i = 0;while (true) {switch (state[i++]) {case '0':message = "Denied";continue;case '1':if (level > 5) {// 如果满足条件,下一个状态是'2'state[i-1] = '2';} else {// 否则,下一个状态是'0'state[i-1] = '0';}continue;case '2':message = "Allowed";continue;}return message;}
      }
      
  4. 僵尸代码注入 (Dead Code Injection)

    • 逻辑:在代码中插入一些永远不会执行或不影响最终结果的“垃圾”代码。

    • AST操作:在AST的任意合法位置(如 BlockStatement 中)插入新的、无用的节点,例如一个永远为 falseif 语句块。

    • 示例

      // 原始代码
      function getResult(a) {return a * 10;
      }// 混淆后代码
      function getResult(a) {// 注入一个永远不会执行的if分支if ("" === "abc") {var x = 1 + 2;console.log(x);return x - 3;} else {// 原始逻辑被放在这里return a * 10;}
      }
      

三、 利用AST进行代码反混淆:从模糊到清晰的实践

反混淆的本质,就是混淆的逆向过程。既然混淆是通过修改AST来增加复杂性,那么反混淆就是通过修改AST来消除这些复杂性。

二者的工具和原理是完全一致的:解析 (Parse) -> 遍历 (Traverse) -> 修改 (Modify) -> 生成 (Generate)。下面我们将结合实际操作,看看如何处理常见的混淆技术。

1. 表达式化简与常量折叠

这是反混淆最基础、最有效的步骤之一。

  • 处理思路:遍历AST,找到所有能立即计算出结果的表达式,如 500 + 500"a" + "b",以及像 "\x73\x65\x63\x72\x65\x74" 这样的十六进制字符串。

  • AST操作

    1. 遍历:使用工具(如 JavaScript 的 babel/traverse)遍历 AST,寻找 BinaryExpression(二元表达式)和 StringLiteral(字符串字面量)节点。

    2. 识别:如果一个 BinaryExpression 的左右操作数都是 Literal(常量),或者一个 StringLiteral 包含转义字符,就可以进行处理。

    3. 修改:在遍历函数中,执行表达式计算(例如 eval() 或手动实现计算逻辑),然后用一个新的 Literal 节点替换掉整个表达式节点。

  • 示例

    // 待反混淆代码
    var value = 500 + 500;
    var key = "\x73\x65\x63\x72\x65\x74";// 反混淆后代码
    var value = 1000;
    var key = "secret";
    

2. 字符串解密

许多混淆器会将所有字符串加密,并用一个解密函数在运行时还原。

  • 处理思路:找到这个解密函数及其调用点,在静态分析时提前执行它,将加密的字符串还原为明文。

  • AST操作

    1. 识别:首先,需要识别出解密函数的定义。它通常是一个接收加密字符串作为参数并返回明文字符串的函数。

    2. 定位:遍历AST,找到所有对这个解密函数的CallExpression(函数调用)节点。

    3. 沙箱执行:创建一个安全的沙箱环境(例如,使用 Node.js 的 vm 模块),将解密函数的代码和它的参数传入,执行后获取返回值。

    4. 修改:用一个新的 StringLiteral 节点替换掉整个函数调用节点。

  • 示例

    // 待反混淆代码
    function _decrypt(str) {// ...复杂的解密逻辑return decodedStr;
    }
    var url = _decrypt("加密的字符串1");
    var msg = _decrypt("加密的字符串2");// 反混淆后代码
    // (解密函数可以被移除,或者保持原样)
    var url = "http://example.com";
    var msg = "Hello, World!";
    

3. 控制流反平坦化

这是反混淆中最具挑战性的任务,需要复杂的静态分析。

  • 处理思路:分析 while-switch 结构中的状态变量case 之间的跳转关系,从而重建出原始的 if/else 和循环结构。这通常需要模拟程序执行,跟踪状态变量的值。

  • AST操作

    1. 识别:找到包含 while(true)switch 语句的结构。识别出状态变量(控制 switch 的变量)和分发函数(通常是一个数组,例如 _0xabc.split('|'))。

    2. 分析:通过污点分析符号执行等技术,跟踪状态变量的流向。记录每个 case 代码块以及它们是如何通过 continue 或变量赋值跳转到下一个 case 的。

    3. 重建:根据分析结果,将这些 case 块重新组织成更高级的结构。例如,如果 case '1' 后的下一个状态取决于一个 if 条件,那么就可以将这两个 case 重新组合成一个 IfStatement。这个过程通常非常复杂,需要自定义的分析算法。

四、 混淆与反混淆的逻辑互换:同一场游戏,不同方向

现在,我们可以清晰地看到这两者之间的对称关系:

混淆操作 (增加复杂度)反混淆操作 (降低复杂度)
AST层面

常量 -> 复杂表达式

替换 LiteralExpression

复杂表达式 -> 常量

替换 ExpressionLiteral

有意义变量名 -> 无意义符号

修改 Identifier 节点的 name 属性

作用域分析与重命名

修改 Identifier 节点的 name 属性

线性控制流 -> while-switch平坦化

替换 If/ForWhile/Switch

while-switch -> 还原高级结构

重新组合 case 块为 If/For

插入无用代码块

插入或删除特定的AST节点

移除无用或不可达代码块

删除不可达的AST节点

结论就是:无论是混淆还是反混淆,其核心都是在 AST 这个层面上,对代码的结构进行程序化的、大规模的增熵(使其更混乱)或减熵(使其更有序)操作。

五、小结

AST为我们提供了一个上帝视角来审视和操作代码。它不仅仅是编译器工作的中间产物,更是代码自动化处理的利器。对于软件开发者而言,了解AST可以帮助你编写更强大的代码转换工具(如 Babel 插件、代码格式化工具)。而对于安全研究人员来说,掌握基于AST的分析技术,则是深入理解、破解和防御复杂代码混淆攻击的必备技能。

在下篇文章[逆向知识] AST抽象语法树:混淆与反混淆的逻辑互换(二)-CSDN博客中,我们将进一步,了解如何实际运用各种工具来真正理解并借助AST破解掉混淆的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93472.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全合规6--服务器安全检测和防御技术

一、服务器安全风险主要威胁:不必要的服务暴露(如仅需HTTP却开放多余端口)。外网扫描(IP/端口扫描)、DDoS攻击。系统漏洞攻击(操作系统、软件版本已知漏洞)。Web攻击(SQL注入、XSS、…

Mutually aided uncertainty

cycle loss calculation in order to regularize the two aux-decoders 辅助信息 作者未提供代码

go基础学习笔记

思维导图变量 声明形式为var 变量名 变量类型 赋值形式为变量名变量值 声明和赋值同时形式为变量名:变量值 多个变量同时声明使用形式为 var (x intb bool )当有多个变量类型一样时,可以放在一行,形式为var x,y int,当类型一样,并且需要赋值同…

C++析构函数和线程退出1

线程作为程序在操作系统中的执行单元,它是活动对象,有生命周期状态,它是有始有终的。有启动就有结束,在上篇文章中讨论了线程作为数据成员启动时的顺序问题,如何避免构造函数在初始化对象时对线程启动的负面影响&#…

【语法】JSON格式与基础语法

文章目录JSON 简介JSON 语法规则JSON 名称/值对JSON 值类型JSON文件存储JSON示例数据示例Python解析JSON代码JSON 简介 JSON 语法是 JavaScript 语法的子集。JSON 是存储和交换文本信息的语法。JSON: JavaScript Object Notation(JavaScript 对象表示法)。 JSON 语法规则 数…

GitHub 热榜项目 - 日榜(2025-08-16)

GitHub 热榜项目 - 日榜(2025-08-16) 生成于:2025-08-16 统计摘要 共发现热门项目:13 个 榜单类型:日榜 本期热点趋势总结 本期GitHub热榜呈现三大技术热点:1) AI应用深入垂直领域,SpatialLM将大语言模型应用于空间…

什么是EDA(Exploratory Data Analysis,探索性数据分析)

EDA(Exploratory Data Analysis,探索性数据分析)是一种在正式建模前,通过统计量和可视化方法来理解数据特征、发现模式与异常、并提出假设的过程。 这张图里你会看到: 直方图:展示单变量的分布,…

计算机毕业设计java的小天鹅酒店月子会所管理小天鹅酒店母婴护理中心管理系统设计小天鹅酒店产后护理会所信息化管理平台

计算机毕业设计java的小天鹅酒店月子会所管理9zl079(配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。在当今数字化时代,随着人们对产后护理需求的不断增加,…

Docker-14.项目部署-DockerCompose

一.DockerCompose大家可以看到,我们部署一个简单的java项目,其中包含3个容器:MySQLNginxJava项目而稍微复杂的项目,其中还会有各种各样的其它中间件,需要部署的东西远不止3个。如果还像之前那样手动的逐一部署&#xf…

Vue组件基础解析

一、组件的核心意义 组件是Vue中实现UI复用与逻辑封装的基础单元,能将复杂UI拆分为独立、可重用的部分,最终组织成嵌套的树状结构(类似HTML元素嵌套)。Vue组件模型支持自定义内容与逻辑封装,也能兼容原生Web Component。 二、组件的定义方式 根据是否使用构建步骤,Vue…

第5问 对于数据分析领域,统计学要学到什么程度?

1. ​统计学在数据分析中的定位​​核心作用​:统计学是数据分析的底层方法论,涵盖数据描述、推断预测和模型构建。​两大分支​:​描述统计​(EDA阶段):数据清洗、特征工程的基础(如均值/分布/…

[go] 桥接模式

桥接模式 是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。 模型说明抽象部分(Abstraction)提供高层控制逻辑,依赖于完成底层实际工作的实现对象…

GitHub的使用教程

第一章:准备工作 1.1:安装Git并设置你的GitHub账户 1.1.1:注册 GitHub 账号: 访问 https://github.com/ 并注册一个新账号。 可以使用qq邮箱进行注册 输入邮箱后点击sign up for GitHub,设置密码后进行注册,输入验…

Day56 Java面向对象10 方法重写

Day56 Java面向对象10 方法重写 1.为什么要方法重写 当子类不需要父类方法的全部内容 或 父类的方法无法满足子类的需求时,就需要在子类重写父类的方法 2.如何方法重写 重写必须发生在继承关系中,只能是子类重写父类子类重写的方法名必须和父类方法一致,方法体可以不同子类重写…

【C++】标准库中用于组合多个值的数据结构pair、tuple、array...

在 C 标准库中,有多种数据结构可用于组合多个值,每种结构都有其特定的设计目的和适用场景。以下是主要组合数据结构的分类解析: 一、核心组合数据结构 1. std::pair (C98) 用途:存储两个相关值(键值对、坐标点等&#…

深入解析C++ STL链表(List)模拟实现

目录 一、需要实现的三个类及其成员函数接口 二、结点类的模拟实现 构造函数 三、迭代器类的模拟实现 1、迭代器类的作用 2、迭代器类模板参数说明 3、构造函数 4、前置运算符重载 5、后置运算符重载 6、前置 -- 运算符重载 7、后置 -- 运算符重载 8、运算符重载 …

将mysql数据库表结构导出成DBML格式

前言 DBML(数据库标记语言)是一种简单易读的 DSL 语言,用于定义数据库结构。 因为需要分析商品模块的表设计是否合理,所以需要图形化表,并显示表之前的关系。 想来想去,找到了DBML。所以就需要将数据库结构…

玩转tokenizer

🌟 案例 1:加载现成的 BERT 分词器from tokenizers import Tokenizer# 加载一个预训练的 BERT tokenizer(文件需要提前下载,比如bert-base-uncased) tokenizer Tokenizer.from_file("bert-base-uncased-tokenize…

Day53--图论--106. 岛屿的周长(卡码网),110. 字符串接龙(卡码网),105. 有向图的完全联通(卡码网)

Day53–图论–106. 岛屿的周长(卡码网),110. 字符串接龙(卡码网),105. 有向图的完全联通(卡码网) 106. 岛屿的周长(卡码网) 方法:深搜 思路&am…

Elasticsearch 数据建模与映射(Mapping)详解

在 Elasticsearch 中,数据建模与映射(Mapping) 是决定搜索性能、存储效率和功能支持的核心环节。合理的映射设计能让搜索更精准、聚合更高效、存储更节省。 本文将全面详解 Elasticsearch 的 数据建模原则、字段类型、动态映射、自定义分析器…