目录

  1. 1. 前言
  2. 2. 基本概念
    1. 2.1. 静态分析的评定标准
    2. 2.2. 抽象
    3. 2.3. 近似
      1. 2.3.1. 控制流
  3. 3. 中间表示(IR)
    1. 3.1. 编译器与静态分析器
    2. 3.2. AST & IR
    3. 3.3. 三地址码(3 Address Code,3AC)
    4. 3.4. 基本块 Basic Blocks(BB)
    5. 3.5. 控制流图 Control Flow Graphs(CFG)
  4. 4. 安全问题
    1. 4.1. 信息流安全
      1. 4.1.1. 访问控制 vs 信息流安全
      2. 4.1.2. 信息流
↑

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

静态程序分析

2025/3/4
  |     |   总文章阅读量:次

前言

大作业所迫,8周时间试图产出一个静态分析工具,正好我也试试能不能写一个当毕设(

参考:

https://fushuling.com/index.php/2025/01/08/%e9%9d%99%e6%80%81%e5%88%86%e6%9e%90%e5%85%a5%e9%97%a8/

https://www.bilibili.com/video/BV1b7411K7P4/

https://www.o2oxy.cn/4414.html

https://www.o2oxy.cn/4400.html


基本概念

静态分析(Static Analysis):在运行一个程序 P 之前,通过静态分析 P 来推测其行为,并且确定是否满足一些特定的性质

我们需要关心的是:

  • 是否存在私有信息泄露(Private Information Leak)
  • 是否有空指针的解引用(Null Pointer Dereference)操作
  • 类型转换(Type Cast)操作是否安全
  • 两个变量能否指向同一个内存地址
  • 是否存在无法满足的断言(assert)语句
  • 是否存在控制流无法到达的代码(Dead Code)

静态分析的评定标准

  • Sound:即全面的或者完备的,简单来说,满足 Sound 即允许错报不允许漏报
  • Truth:真实的,顾名思义,就是一个程序真正存在的漏洞,是最理想的分析结果
  • Complete:完整的,简单来说,满足 Complete 即允许漏报但不允许错报

image-20250304100113739

那么很明显,同时满足 Sound 和 Complete 是几乎不可能的,Truth 是 Sound 的子集,Complete 又是 Truth 的子集,Sound 属于一种 Overapproximate,即过拟合,而 Complete 属于一种 Underapproximate,即弱拟合

在真实的程序分析中,显然我们要先做到 Sound,在此基础上向 Truth 上靠拢,宁可误报不能漏报


抽象

将具体域的值映射到抽象域中

例如按符号(+,-,0)给变量值做划分,大于0就划分为+,小于0就划分为-

而不确定的值划分为 unknown ,错误的值划分为 undefined

image-20250304104612811

近似

定义计算抽象值的转换规则

image-20250304112824254

控制流

image-20250304113018189

所有 control flows 的汇聚点,都要执行合并(merge):抽象做近似


中间表示(IR)

编译器与静态分析器

编译流程:

image-20250311144903686

  • Scanner: 扫描源代码,进行词法分析(Lexical Analysis),词法分析会用到正则表达式(Regular Expression),词法分析后的结果为一个标记(Token)串。

    以自然语言为例:You dkasnd&^$Roewn godd,由于不能构成单词,所以不能通过 Scanner

  • Parser: 遍历标记串,进行语法分析(Syntax Analysis),这里的语法分析分析的是上下文无关的语法(Context Free Grammer),解析器的内部应该是实现了一个有限状态机,用于识别和分析每个语法块格式的正确性,语法分析的结果为一棵抽象语法树(Abstract Syntax Tree,AST)。

    以自然语言为例:Like your hair I,词法正确但是语法不正确,不能通过 Parser

  • Type Checker: 会遍历抽象语法树,进行语义分析(Semantic Analysis),不过编译器的语意分析是简单的,主要是分析属性语法(Attribute Grammer),比如说变量类型,并适当调整一下语法树。语义分析的结果我们称之为装饰过的抽象语法树(Decorated AST)。

    以自然语言为例:Apples eat you,词法、语法正确,但是语义上不正确,不能通过 Type Checker

  • Translator: 会将抽象语法树翻译成中间表示(Intermediate Representation, IR),IR 的出现解耦了编译器的机器相关(Machine Dependent)部分和机器无关(Machine Independent)部分,上述几个层次在不同架构的机器上面是可以几乎不加改动地复用的。

  • Code Generator: 会将 IR 转化成物理 CPU 能够直接执行的比特序列,也就是机器代码

可以把 IR 之前的部分类比为前端, IR 后面的部分类比为后端


AST & IR

对于这个代码:

do i = i + 1; while (a[i] < v);

AST 和 IR 的结果分别是:

image-20250311150031223

AST:之前接触过 https://c1oudfl0w0.github.io/blog/2024/02/10/AST%E6%B3%A8%E5%85%A5/#%E8%AF%8D%E6%B3%95%E5%88%86%E6%9E%90

解析出来的语法树用json表示为:

{
  "type": "Program",
  "start": 0,
  "end": 31,
  "body": [
    {
      "type": "DoWhileStatement",	// do-while结构
      "start": 0,
      "end": 31,
      "body": {	// body
        "type": "ExpressionStatement",
        "start": 3,
        "end": 13,
        "expression": {
          "type": "AssignmentExpression",
          "start": 3,
          "end": 12,
          "operator": "=",
          "left": {
            "type": "Identifier",
            "start": 3,
            "end": 4,
            "name": "i"
          },
          "right": {
            "type": "BinaryExpression",
            "start": 7,
            "end": 12,
            "left": {
              "type": "Identifier",
              "start": 7,
              "end": 8,
              "name": "i"
            },
            "operator": "+",	// 右子树根节点
            "right": {
              "type": "Literal",
              "start": 11,
              "end": 12,
              "value": 1,
              "raw": "1"
            }
          }
        }
      },
      "test": {
        "type": "BinaryExpression",
        "start": 21,
        "end": 29,
        "left": {
          "type": "MemberExpression",
          "start": 21,
          "end": 25,
          "object": {
            "type": "Identifier",
            "start": 21,
            "end": 22,
            "name": "a"
          },
          "property": {
            "type": "Identifier",
            "start": 23,
            "end": 24,
            "name": "i"
          },
          "computed": true,
          "optional": false
        },
        "operator": "<",	// 右子树根节点
        "right": {
          "type": "Identifier",
          "start": 28,
          "end": 29,
          "name": "v"
        }
      }
    }
  ],
  "sourceType": "module"
}

IR:三地址码

  1. i = i + 1
  2. 临时变量 t1 = a[i]
  3. if t1 < v goto 1,即回到第一步重复执行

可以看出来,抽象语法树(AST)是源代码结构的一种抽象表示,以树状的形式表现编程语言的语法结构,相对而言层次较高,和语法结构更接近,并且依赖于具体的语言类,缺少和程序控制流相关的信息

而 IR 与机器代码接近,通常与具体的语言无关,简单通用,包含程序的控制流信息,也是我们做静态分析的基础


三地址码(3 Address Code,3AC)

通常使用 3AC 形式的 IR

所谓的三地址码是指:每个三地址码指令,都可以被分解为四个元组(运算符,运算对象1,运算对象2,结果)

因为每个陈述都包含了三个变量,所以它被称为三地址码。

例子:

a+b+3

转化成三地址码:这里是结果 运算符 对象1 运算符 对象2

t1 = a + b
t2 = t1 + 3

其中a, b是原始变量,3是常数,t1和t2是编译过程中生成的临时变量


系统中常见的三地址码指令:

x = y bop z //bop:二元运算,二进制算术或逻辑运算
x = uop y //uop:一元运算(减号、否定、强制转换)
x = y
goto L //L: 表示程序位置的标签  goto L就是一种无条件跳转
if x goto L // if ... 满足条件后再xxx属于一种条件跳转
if x rop y goto L //rop:关系运算符(>、<、==、>=、<=等)

真实案例:https://github.com/soot-oss/soot

观察一下这个 java 方法调用的 3AC,部分涉及到 JVM 的设计

image-20250311154008354

image-20250311154140232


基本块 Basic Blocks(BB)

具体定义是:一个基本块中只能有一个入口和一个出口

入口就是其中的第—个语句,出口就是其中的最后一个语句。对一个基本块来说,执行时只从其入口进入,从其出口退出

在具体程序中,我们划分 BasicBlock 的方式就是找到不同的 leader,即不同 BasicBlock 的入口点,通过不同的入口点划分不同的 BasicBlock

划分 leader 的原则:

  • 程序的第一条语句:即整个程序最初的入口
  • 转移语句的目标语句:比如一个 goto 语句跳转到了第三句代码,那么这个第三句代码必然是 leader
  • 紧跟在条件转移语句后面的语句:比如第三句代码是一个 goto 语句,那么第四句代码必然是 leader

3AC 的例子:

(1)x = input
(2)y = x
(3)z = x * y
(4)if z < x goto (7)
(5)p = x / y
(6)q = p + y
(7)a = q
(8)b = x + a
(9)c = 2a - b
(10)if p == q goto (12)
(11)goto(3)
(12)return

按照第一条原则,程序的第一条语句必须是 leader,我们可以得到 leader:(1)

按照第二条原则,转移语句的目标语句必须是 leader,我们可以得到 leader:(3) (7) (12)

按照第三条原则,紧跟在条件转移语句后面的语句必然是 leader,我们可以得到 leader:(5) (11) (12)

对三个集合进行合并,整个程序的 leader 便是:(1) (3) (5) (7) (11) (12)

通过不同的 leader 划分不同的 BasicBlock,我们可以划分出该程序的 BasicBlock 分别为:

  1. {(1) (2)}
  2. {(3) (4)}
  3. {(5) (6)}
  4. {(7) (8) (9) (10)}
  5. {(11)}
  6. {(12)}

控制流图 Control Flow Graphs(CFG)

有了 BasicBlock 作为工具,我们就可以很清晰的画出程序的 Control Flow Graph,即控制流图

类似于 IDA 的 Flow chart

image-20250311163206322

CFG 控制流程图,是一个过程或程序的抽象表现,是用在编译器中的一个抽象数据结构,由编译器在内部维护,代表了一个程序执行过程中会遍历到的所有路径

它用图的形式表示一个过程内所有基本块执行的可能流向, 也能反映一个过程的实时执行过程

CFG 能反映出一个过程的许多信息,包括但不限于:

  • 是哪一个过程;
  • 一个过程的入口(第一个基本块)和出口(最后一个基本块);
  • 一个基本块的所有可能的下一个基本块(所有的出口);
  • 一个基本块的所有可能的上一个基本块(所有的入口);
  • 一个基本块所对应的语句表

CFG 的节点是 BasicBlock,只有两种情况下从块A到块B存在边:

  • 从A的末尾到B的开头有一个有条件或无条件的跳转

    image-20250311163903640

  • B立即按照原始指令顺序跟随A且A不会以无条件跳转结束

    因此这种A以无条件跳转结尾的情况下,A与B之间不存在边

    image-20250311165659509

    若A不以跳转结尾,那么二者间存在边

    image-20250311165739355

在日常的分析中,我们会经常把跳转到指令标签替换为跳转到基本块,这样就简化了我们分析的难度

image-20250311165820963

对于前面给出来的 3AC 代码块而言,CFG 如下

image-20250311165904957


安全问题

信息流安全

访问控制 vs 信息流安全

访问控制(Access Control):是一种保护敏感数据的标准方法,主要用于检查程序是否有权访问某些信息,关注如何访问信息

信息流安全(Information Flow Security):是一种端到端的方法,跟踪信息如何流经程序,以确保程序安全地处理信息,关注信息如何传播

信息流

如果变量 x 中的信息被转移到变量 y 中