ICSE 2012的一篇关于定向符号执行的文章。
这篇文章的目的有点像Usenix 21的ARCUS。都是在不给定输入但是给定一些执行数据的情况下,复现崩溃报告。
文章的工具开源于:https://github.com/gatech/bugredux
背景
最近对Apache、Eclipse和Mozailla的开发者的调查显示根据漏洞报告复现漏洞是很重要的事情。然而,漏洞报告中的call stack或者mem dump的这些信息不足以复现漏洞。现有的方法要么是收集较少的信息,但是不有效,或者是收集了太多的信息,而不高效。
方法
文章的思路见下图。首先对程序进行插桩,当崩溃的时候,就能收集更多的额外信息比如调用序列(call sequence)和程序执行路径。然后分析器(analyzer)分析崩溃报告,并生成输入复现漏洞。
插桩是一个很常见的技术,所以文章不再详细说明了。简单说的话,就是在程序中插入探针,当程序运行触发漏洞时,就会触发探针生成你所需要的数据。插桩给的数据分为四类:漏洞崩溃点、调用栈、调用序列、程序执行完整的Trace。
文章的核心在analyzer部分。analyzer部分实际上是一种定向符号执行。给定一个目标行号集合,结合启发式的路径选择策略去到达目标行号,并生成输入。本质上还是把可达性问题转换为约束求解问题。
这里简单说下路径选择策略的部分。路径选择策略是优先选择离当前目标最近的状态。
评估
主要研究两个问题:
- BugRedux能够复现漏洞吗?
- 哪种类型的执行数据提供最好的权衡?
对于第二个问题,不需要插桩的两种数据(崩溃点,调用栈)的时间开销为0,空间开销也很小。而对于完整路径,很多程序的开销都很难让人接受。调用序列的开销相比完整路径要小得多。这就是为什么ARCUS需要用Intel PT去获取完整路径,从而有个较低的开销。
对于第一个问题的结果如下表所示。从结果来看,调用栈和崩溃点的效果差不多。调用路径的有效性是最好的。而完整路径的效果却是最差的。作者认为这有两个原因:
- 严格按照完整路径符号执行,可能导致SMT不能处理某些条件。
- KLEE使用libc的简化实现(hook,为了避免路径爆炸),就导致不可能和原来的执行一样沿着相同的路径走了。
总结
这篇文章是很经典的定向符号执行的工作。很疑惑之前自己为什么看了一会就没看了。最近写related work的时候,竟然看得很快,并且感觉文章很多地方都考虑得很严谨。或许是因为有目标导向?
这篇文章还带给了我一些新知:
- 给定完整的路径,为什么符号执行不能沿着其走下去?
- 方法部分可以重点介绍自己的核心工作。次要的工作可以几句话带过(比如文中的插桩)
- Tradeoff!