一、路径爆炸初探

什么是路径爆炸?

路径爆炸,说白了就是程序的状态空间太大了,大到你的电脑根本算不过来。

我刚开始接触符号执行时,对这个概念的理解很模糊。直到有一次,我拿一个只有几十行代码的C程序跑angr,结果等了半小时还没出结果。我当时就懵了——这么小的程序,怎么会跑不动?

后来我才明白,这就是路径爆炸在作祟。

你想想看,angr在做符号执行时,每遇到一个条件分支(比如if-else),就会把当前状态分裂成两个。如果程序里有n个分支,理论上就会有2ⁿ条路径。当n=10时,是1024条;当n=20时,是100万条;当n=30时,是10亿条。指数增长就是这么恐怖。

核心定义:路径爆炸是指符号执行过程中,由于程序分支数量的指数级增长,导致状态空间过大,无法在合理时间内完成分析的现象。

为什么angr会遇到路径爆炸?

angr本质上是一个符号执行引擎。它的工作方式是:把程序的每条路径都当作一个独立的状态来维护。每个状态包含寄存器值、内存内容、路径约束等信息。

问题出在哪里呢?

  • 分支无处不在——循环、条件判断、函数调用,甚至异常处理都会产生分支
  • 状态膨胀——每个新状态都要复制父状态的全部信息,内存消耗巨大
  • 路径合并困难——不同路径到达同一代码位置时,angr默认不会自动合并

我记得有一次分析一个网络协议解析器,代码里有个switch语句,case分支有40多个。angr直接给我创建了40多个状态,每个状态还要继续往下走...结果你猜怎么着?内存直接爆了。

避坑指南:我曾经天真地以为,只要给虚拟机分配足够多的内存就能解决问题。结果128GB内存照样被吃光。路径爆炸不是硬件能硬扛的,必须从算法层面解决。

一个简单的路径爆炸示例

来看一个最典型的例子。这个程序虽然简单,但足以让你直观感受到路径爆炸的威力。

// path_explosion.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int x = 0;
    int i;
    
    for (i = 0; i < 10; i++) {
        if (rand() % 2) {
            x += 1;
        } else {
            x -= 1;
        }
    }
    
    if (x == 0) {
        printf("x is zero\n");
    } else {
        printf("x is not zero\n");
    }
    
    return 0;
}

这个程序循环10次,每次根据随机数决定x加1还是减1。最后判断x是否为0。

如果用angr来分析这个程序,会发生什么?

angr会把rand() % 2当作符号值来处理。每次循环都会产生两个分支:一个走加1路径,一个走减1路径。循环10次,就是2¹⁰ = 1024条路径。

嗯,1024条路径看起来还好对吧?但注意,这只是10次循环。如果循环次数增加到20次,就是100万条路径。30次,10亿条。40次,1万亿条。

我实际测试过,用angr分析这个程序(循环10次),在我的笔记本上跑了大概3分钟才出结果。如果循环20次,直接跑了一小时还没完,我只好强制终止了。

小技巧:如果你想快速验证路径爆炸的效果,可以用angr的exploration_technique中的LoopSeer来观察循环展开的过程。它能告诉你angr到底创建了多少个状态。

路径爆炸的直观展示

下面这张图展示了路径爆炸的核心逻辑。你可以看到,随着分支数量的增加,路径数量是如何呈指数级增长的。

路径爆炸示意图:分支数量 vs 路径数量 分支数量 (n) 0 5 10 15 20 25 0 2⁵ 2¹⁰ 2¹⁵ 2²⁰ 2¹⁰ = 1024 2¹⁵ ≈ 3.2万 2²⁰ ≈ 100万 安全区域 警告区域 危险区域 路径数量 = 2ⁿ

从这张图可以清楚看到:当分支数量小于10时,路径数量还在可接受范围内。一旦超过15,路径数量就开始疯狂增长。到了20个分支,就是百万级别了。

在实际的二进制分析中,一个稍微复杂点的程序,分支数量轻松上百。这就是为什么路径爆炸是符号执行领域最头疼的问题之一。

路径爆炸的常见场景

根据我的经验,以下场景最容易触发路径爆炸:

场景 原因 典型例子
循环展开 每次循环迭代产生分支 字符串处理、数据校验
switch语句 大量case分支 协议解析、命令分发
递归调用 每层递归产生新状态 树遍历、深度优先搜索
符号化输入 输入长度不确定导致分支爆炸 网络数据包解析

关键认知:路径爆炸不是angr的bug,而是符号执行这个技术路线固有的挑战。理解这一点很重要——你不能指望换个工具就能解决,而是要学会如何与路径爆炸共存、对抗。

好了,这一章我们初步认识了路径爆炸。说白了就是:程序分支太多,状态空间太大,电脑算不过来。下一章我会聊聊,面对路径爆炸,我们有哪些实用的应对策略。


专注资料整理