符号执行入门

近些年符号执行因为其具有生成高覆盖率的测试用例和在复杂程序中发现更深层次的错误的能力,而再次受到关注。本篇博客简单探讨符号执行的入门知识,力求用最精简的语言告诉初学者什么是符号执行。其大部分内容来自 英国帝国理工 的 Cristian Cadar 发表在 ACM 通讯上的一篇短文 Symbolic Execution for Software Testing: Three Decades Later,这篇论文用非常简单的案例和通俗的语言阐释了符号执行的基本原理和细节,是为数不多的科普性论文。

经典符号执行

这是一个论证符号执行的简单案例,用户输入 x、y,在 line9 存在一个错误分支。传统的模糊测试会编译 x、y 值,最终走到错误分支,触发 bug。而符号执行则不然,符号执行的关键是**使用符号值代替具体值, 并将程序变量的值表示为符号输入值的符号表达式 , 其结果是程序计算的输出值被表示为符号输入值的函数 **

The key idea behind symbolic execution is to use symbolic values, instead of concrete data values as input and to represent the values of program variables as symbolic expressions over the symbolic input values.

其实看到上面的句子还是一头雾水,没关心,举个简单例子就明白了,如下所示

int twice(int v) {
    return 2 * v;
}

void testme(int x, int y) {
    z = twice(y);
    if (z == x) {
        if (x > y + 10)
            ERROR;
    }
}

int main() {
    x = sym_input();
    y = sym_input();
    testme(x, y);
    return 0;
}

一个符号执行的路径就是一个 true 和 false 组成的序列,其中第 i 个 true(或false)表示在该路径的执行中遇到的第 i 个条件语句,并且走的是 then(或else) 这个分支。一个程序所有的执行路径可以用执行树(Execution Tree)来表示testme 函数可以转化为下面的执行树

Created with Raphaël 2.3.0 Start 2 * y == x x > y + 10 x = 30, y = 15 End x = 2, y = 1 x = 0, y = 1 yes no yes no

函数有 3 条执行路径,{x = 0, y =1} 、{x = 2, y = 1} 和 {x = 30, y = 15} 即可遍历这 3 条路径。符号执行的目标就是生成这样的输入集合,在给定时间内遍历所有路径。

Symbolic execution maintains a symbolic state σ, which maps variables to symbolic expressions, and a symbolic path constraint PC, which is a quantifier-free first-order formula over symbolic expressions. At the beginning of a symbolic execution, σ is initialized to an empty map and PC is initialized to true. Both σ and PC are updated during the course of symbolic execution. At the end of a symbolic execution along an execution path of the program, PC is solved using a constraint solver to generate concrete input values. If the program is executed on these concrete input values, it will take exactly the same path as the symbolic execution and terminate in the same way.

符号执行维护符号状态 σ 和符号路径约束 PC

  • σ 表示变量到符号表达式的映射
  • PC 是符号表示的不含量词的一阶表达式

在符号执行开始时,σ 初始化为空映射,PC 初始化为真,在符号执行的过程中不断变化。在对程序的某一路径分支进行符号执行的终点,把 PC 输入约束求解器获得求解。如果用这些具体的输入值输入程序执行,它将会和符号执行运行在同一路径,并且以相同方式结束。

在这里插入图片描述

符号状态 σ

如上文所提的例子,符号状态 σ 开始为空,符号路径约束 PC 为 true。每次遇到 var = sym_input() 语句,符号执行就会向 σ 添加一个 var->s 的映射,s 是一个新的不受约束的符号值。程序 lines 14-15 执行结果是 σ = {x -> x 0 x_0 x0, y -> y 0 y_0 y0} 。每当遇到赋值语句 v = e,符号执行会更新 σ,添加一个 v 到 σ(e) 的映射。程序 lines 6 执行结果 σ = {x -> x 0 x_0 x0, y -> y 0 y_0 y0, z -> 2 y 0 y_0 y0}。

符号路径约束 PC

每个条件语句 if (e) S1 else S2,PC 更新为 PC∧σ(e),表示 then 分支;同时生成新的路径约束 PC‘ ,并且初始化为 PC∧¬σ(e),表示为 else 分支。如果 PC 满足,则程序走到 then 分支,如果 PC’ 满足,那么走到 else 分支。请注意,符号执行与实际执行的不同,符号执行两条路径都是可以走的,只需要分别维护它们的状态。如果 PC 和 PC‘ 都不能满足,符号执行就会在对应的位置终止。例如,程序 lines 7 执行建立符号执行实例,路径约束 2 y 0 = x 0 2y_0 = x_0 2y0=x0 2 y 0 ≠ x 0 2y_0 ≠ x_0 2y0=x0;程序 lines 8 继续建立两个实例,分别是 ( x 0 = 2 y 0 ) ( x 0 > y 0 + 10 ) (x_0 = 2y_0) ^ (x_0 > y_0 + 10) (x0=2y0)(x0>y0+10) ( x 0 = 2 y 0 ) ( x 0 ≤ y 0 + 10 ) (x0 = 2y0) ^ ( x_0 ≤ y_0 + 10 ) (x0=2y0)(x0y0+10)

符号执行遇到 exit 或者 error(比如程序崩溃或者违反断言),当前实例将会终止。此时利用约束求解器对当前路径约束求解,得到测试输入;如果程序输入这些实际值,就会在同样路径结束。

传统符号执行的缺陷

如果代码包含递归或者循环,且终止条件符号化,可能会产生无数条路径(路径爆炸),例如

void testme_inf() {
	int sum = 0;
	int N = sym_input();
	while(N > 0) {
		sum = sum + N;
		N = sym_input();
	}
}

这段程序执行路径包含两种状态:无数的 trues 加上一个 false,或者是无数的 trues。第一种状态
( ⋀ i ∈ [ 1 , n ] N i > 0 ) Λ ( N n + 1 ≤ 0 ) ({\bigwedge \atop i\in[1,n]} N_i > 0) \Lambda (N_{n+1} \leq 0) (i[1,n]Ni>0)Λ(Nn+10)
其中每个 Ni 都是一个新的符号值,执行结束的符号状态为 N → N n + 1 , s u m → ∑ i ∈ [ 1 , n ] N i {N \rightarrow N_{n+1}, sum \rightarrow \sum_{i\in[1,n]} N_i} NNn+1,sumi[1,n]Ni。在实践中我们需要通过一些方法限制这样的搜索,比如限制时间或者路径数量。

A key disadvantage of classical symbolic execution is that it cannot generate an input if the symbolic path constraint along an execution path contains formulas that can-not be (efficiently) solved by a constraint solver.

传统符号执行一个关键缺陷是,当符号路径约束包含了不能通过约束求解器求解的公式时,就不能得到输入值。现在假设我们的约束求解器不能解决非线性运算,如果把 twice 换成如下函数

int twice(int v) {
	return (v * v) % 50;
}

这时符号路径约束 y 0 ∗ y 0 m o d 50 = x 0 y_0 *y_0 mod 50 = x_0 y0y0mod50=x0 y 0 ∗ y 0 m o d 50 ≠ x 0 y_0 * y_0 mod 50 ≠ x_0 y0y0mod50=x0无法求解。或者 twice 本身就不是一个具体的函数,如引用的外部函数,则路径约束 t w i c e ( y 0 ) = x 0 twice(y_0) = x_0 twice(y0)=x0 t w i c e ( y 0 ) ≠ x 0 twice(y_0) ≠ x_0 twice(y0)=x0 同样无法通过计算得到输入值。

现代符号执行

很快大家发现在分析过程中,如果能加入具体值进行分析,将大大简化分析过程,降低分析的难度和提升效率,无法避免的还是需要将各种条件表达式,进行符号化抽象后变成约束条件参与执行。将程序语句转换为符号约束的精度,对符号执行所达到的覆盖率以及约束求解的可伸缩性会产生重大影响。所以如何做好混合具体(Concrete)执行和符号(Symbolic)执行的能力的平衡,就成为现代符号执行的关键点。

One of the key elements of modern symbolic execution techniques is their ability to mix concrete and symbolic execution.

现代符号执行最显著的特点是将实际执行和符号执行结合起来。

在这里插入图片描述

Concolic Testing

混合符号执行由 K. Sen(库希克·森)于2005年提出,混合符号执行核心思想是在程序运行过程中,判断哪些代码需要符号化执行,哪些代码可以直接执行,代表工具有 EXE、CUTE 和 DART 等。

在这里插入图片描述

EGT

执行生成测试(Execution-Generated Testing)。在执行每个操作前,动态检查每个相关值是否是具体的,如果是具体的,就按照正常源码执行,如果至少有一个变量是符号化的,就符号化执行,并为当前路径更新符号路径约束 PC。

传统符号执行中,因为外部函数或者无法进行约束求解造成的问题通过使用实际值得到了解决。但同时因为在执行中使用了实际值,固定了某些执行路径,由此将造成路径完成性的缺失。

挑战和方案

路径爆炸 (Path Explosion)

符号执行面临最直接的问题就是路径爆炸,即在一段循环或者递归代码中,由于同一个路径约束,可能会造成程序无限循环下去,导致产生无数条路径分支。从宏观角度讲

  • 限制符号执行时间
  • 限制循环迭代次数

都可以限制路径数量,从具体方法来讲

  • 启发式地优先探索当前最有效的路径
    • static control-flow graph (CFG)
    • interleave symbolic ex-ploration with random testing
  • 使用可靠的程序分析技术降低路径探索的复杂性
    • select expressions
    • 通过缓存和重用底层函数的计算结果,减小分析的复杂性

约束求解 (Constraint Solving)

虽然近几年约束求解器的能力有明显提升,但依然是符号执行的关键瓶颈之一。因此,实现约束求解器的优化十分重要。

  • 不相关约束消除
  • 增量求解

通过重用以前相似请求得到的结果,可以提升约束求解的速度,这种方法被运用到了 CUTE 和 KLEE 中。

内存建模 (Memory Modeling)

将程序语句转换为符号约束的精度对符号执行实现的覆盖率以及约束解决的可伸缩性有很大的影响。例如,使用一个内存模型近似固定宽度的整数变量与实际数学整数可能更有效,但另一方面可能导致不精确的分析代码取决于角落算术溢出等情况下,这可能导致符号执行遗漏路径或探索不可行。

其实,内存建模可以引申为其他方面的挑战,主要是符号执行如何模拟具体的系统环境。

如何处理代码中的系统调用? 一些系统函数调用无法用数学表示,如 fopen 等系统调用会截断符号化赋值过程。现代符号执行引擎会对系统函数进行重新建模。比如在新模型中,fopen函数返回一个数组,fgets函数修改为从一个变量中获取内容(实际在对系统函数进行建模的时候,会要处理更复杂的情况)。

如何处理系统环境问题? 引擎使用模型模拟系统调用,其优点是,能够得到正确的符号执行结果;缺点是需要实现和维护许多可能用到的系统调用模型。

总结

从 1976 年符号执行的提出,到 1980 年之后的三十年里无人问津,再到 2005 年跨时代意义的混合符号执行出现,符号执行每个重大转折点无不伴随着技术上的重大突破,包括 2014 年 AFL 的横空出世,后续的符号执行与相应的漏洞挖掘技术如雨后春笋相继涌现。

与 Fuzzing 技术的融合

TitleConferenceInstitutionSummary
Pangolin:Incremental Hybrid Fuzzing with Polyhedral Path Abstraction, 2020IEEE S&P香港科技大学作者将约束求解后对信息重用起来,实现 Constrained Mutation 和 Guided Constraint Solving,从而提升混合 fuzz 效率。(混合fuzzing)
Angora: Efficient Fuzzing by Principled Search, 2018IEEE S&P上海交通大学Angora 是一个基于突变的模糊器。Angora 的主要目标是通过在没有符号执行的情况下解决路径约束来增加分支覆盖率。(已开源)
Learning to Fuzz from Symbolic Execution with Application to Smart Contracts, 2019ACM CCS苏黎世联邦理工学院Jingxuan He 等人提出了一种从符号执行中学习 fuzzer 的新方法,将其应用于智能合约中。
HFL: Hybrid Fuzzing on the Linux Kernel, 2020NDSS俄勒冈州立大学新兴混合 fuzz 工具。据作者所属,HFL 代码覆盖率分别比 Moonshine 和 Syzkaller 高出15%和26%,并发现 20+ 个内核漏洞。(未开源)
QSYM : A Practical Concolic Execution Engine Tailored for Hybrid Fuzzing, 2018USENIX Security佐治亚理工学院作者设计了一种快速的,称为 QSYM 的 Conolic 执行引擎,支持混合 fuzzing。(已开源)
One Engine to Fuzz 'em All: Generic Language Processor Testing with Semantic Validation, 2021IEEE S&P佐治亚理工学院一个通用 fuzzing 框架(s3team/Polyglot ),目的是为了探索不同编程语言的处理器而生成高质量的模糊测试用例(已开源)

二进制符号执行的经典工具是 2009 年谷歌 Candea 团队开发的 S2E,开创选择符号执行的先河;2012 年 David Brumley 团队提出 Mayhem,并于 2016 年的 CGC 中获得冠军。

相关工具

ToolTargetURLCan anybody use it/ Open source/ Downloadable
VerifastC, Javahttps://people.cs.kuleuven.be/~bart.jacobs/verifastyes
Tritonx86 and x86-64https://triton.quarkslab.comyes
SymJSJavaScripthttps://core.ac.uk/download/pdf/24067593.pdfno
SymDroidDalvik bytecodehttp://www.cs.umd.edu/~jfoster/papers/symdroid.pdfno
Symbolic PathFinder (SPF)Java Bytecodehttps://github.com/SymbolicPathFinderyes
S2Ex86, x86-64, ARM / User and kernel-mode binarieshttp://s2e.systems/yes
RubyxRubyhttp://www.cs.umd.edu/~avik/papers/ssarorwa.pdfno
RosetteDialect of Rackethttps://emina.github.io/rosette/yes
pysymemux86-64 / Nativehttps://github.com/feliam/pysymemu/yes
Pex.NET Frameworkhttp://research.microsoft.com/en-us/projects/pex/no
Pathgrind[10]Native 32-bit Valgrind-basedhttps://github.com/codelion/pathgrindyes
Oyente-NGEthereum Virtual Machine (EVM) / Nativehttp://www.comp.ita.br/labsca/waiaf/papers/RafaelShigemura_paper_16.pdfno
OtterChttps://bitbucket.org/khooyp/otter/overviewyes
MythrilEthereum Virtual Machine (EVM) / Nativehttps://github.com/ConsenSys/mythrilyes
MProEthereum Virtual Machine (EVM) / Nativehttps://sites.google.com/view/smartcontract-analysis/homeyes
MayhemBinaryhttp://forallsecure.comno
Manticorex86-64, ARMv7, Ethereum Virtual Machine (EVM) / Nativehttps://github.com/trailofbits/manticore/yes
KudzuJavaScripthttp://webblaze.cs.berkeley.edu/2010/kudzu/kudzu.pdfno
KLEELLVMhttps://klee.github.io/yes
KiteLLVMhttp://www.cs.ubc.ca/labs/isd/Projects/Kite/yes
KeYJavahttp://www.key-project.org/yes
JPFJavahttp://babelfish.arc.nasa.gov/trac/jpfyes
jCUTEJavahttps://github.com/osl/jcuteyes
JBSEJavahttps://github.com/pietrobraione/jbseyes
JaVerTJavaScripthttps://www.doc.ic.ac.uk/~pg/publications/FragosoSantos2019JaVerT.pdfyes
janala2Javahttps://github.com/ksen007/janala2yes
Jalangi2JavaScripthttps://github.com/Samsung/jalangi2yes
FuzzBALLVineIL / Nativehttp://bitblaze.cs.berkeley.edu/fuzzball.htmlyes
ExpoSEJavaScripthttps://github.com/ExpoSEJS/ExpoSEyes
crucibleLLVM, JVM, etchttps://github.com/GaloisInc/crucibleyes
BINSECx86, ARM, RISC-V (32 bits)http://binsec.github.ioyes
BE-PUMx86https://github.com/NMHai/BE-PUMyes
angrlibVEX based (supporting x86, x86-64, ARM, AARCH64, MIPS, MIPS64, PPC, PPC64, and Java)http://angr.io/yes

如果你想了解更多,可以参考如下文献

参考文献

  • All You Ever Wanted to Know about Dynamic Taint Analysis and Forward Symbolic Execution (but Might Have Been Afraid to Ask)
  • Symbolic execution for software testing: three decades later
  • Playing with Dynamic symbolic execution
  • 玩转动态符号执行
  • K. Sen, D. Marinov, and G. Agha. CUTE: A concolic unittesting engine for C. InESEC/FSE’05, Sep 2005.
  • Cadar, Cristian, and Koushik Sen. “Symbolic execution for software testing: three decades
    later.” Communications of the ACM 56.2 (2013): 82-90.
  • https://en.wikipedia.org/wiki/Symbolic_execution (符号执行相关工具集)
  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江下枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值