中文译名:SemFuzz:基于语义的概念验证漏洞自动生成
作者:wei you btw 二作和三作是 IIE,三作是陈凯
单位:印第安纳大学伯明顿分校
国家: #美国
年份: #2017年
来源: #ccs
关键字: #fuzzing #定向fuzzing #generate_poc
代码地址:
笔记建立时间: 2023-04-10 09:47
摘要
- 提出 semfuzz,利用漏洞相关文本(例如,CVE 报告和 Linux git 日志)来指导 PoC 的自动生成
- 基于 NLP 来进行信息提取
- 基于语义的模糊处理来生成 PoC
- SemFuzz 运行了过去五年报告的112个 Linux 内核缺陷,成功触发了其中的18个,并进一步发现了一个零日漏洞和一个未公开的漏洞。
怎么感觉成功触发的有点少啊
Instruction
漏洞的 CVE 报告,Linux git 日志、论坛和博客上发布的错误描述都可以用来帮助自动生成 PoC
- 从攻击方角度来说如何去利用这些信息
- 从防守方角度来说,如何控制这些信息的泄露
自动生成 exploit 的挑战
- 自动生成 exp 很困难,目前能够实现的都是针对一些简单的输入验证类的漏洞。其他类型的漏洞 (如不受控制的资源消耗、死锁、内存损坏等)的 exp 自动生成过于复杂,目前(2017 年前)还没人做。
- 目前的方法主要就是符号执行找到约束,求解约束
- 但是就算是简单的输入验证类漏洞,符号执行和约束求解也是困难的。因为现实世界的程序的漏洞的路径约束往往是非线性的,增大了求解难度
本文方法
- 利用与漏洞相关的非代码文本,特别是 CVE 报告和 Linux git 日志,来提取指南,这些指南被认为是有助于发现和触发一组深层错误的信息。
- 本文的技术是基于语义的模糊测试,自动分析错误报告,以在 Linux 内核漏洞上创建端到端的 PoC
- 首先利用 NLP 来分析 CVE 和 git 日志,利用这些信息来创建一个到达漏洞函数的调用序列
- 利用模糊测试迭代的调整各个调用的参数,以移动到函数内部的修补代码,直到触发漏洞。(我的理解就是被测程序实际上是没有这个修补代码的,已修补的程序修补代码的位置就是未修补程序漏洞的位置)
- SemFuzz 能够处理内核代码中的各种漏洞
DESIGN OVERVIEW
SemFUZZ 主要是两个步骤:
- 语义信息提取
- 基于语义的 fuzzing
上图是个示例,左上方是 CVE 描述,左下方是漏洞的 git 日志。semfuzz 首先启动内核准备 fuzz 环境,使用从描述中发现的概念 (即“MSG MORE”,“loopback”,“UDP”)构建种子输入——就是右上的 system call。在 fuzzing 过程中根据从补丁中提取到的信息(cfg)和监控关键变量的反馈对输入进行变异,直到触发漏洞。 说明。
语义信息的提取
- 基于正则表达式的字符串匹配等语法手段直接提取信息的效果不好,没有考虑单词之间互相的依赖性,没有考虑语句的语义。
- 采用 NLP、词性标记 POS、短语解析和句法解析,SemFuzz 构建了一个解析树来识别每个单词的 POS 标记,并识别句子中的句法子句以进行语义分析。
生成解析树
使用 NLP 工具 pyStatParser 从 Penn 树库学习概率上下文无关语法,并为 CVE 和 git 日志中的每个句子生成解析树。上图右下角展示的就是"the whole skb len is dangerous"的语法树,每个叶子的父节点是单词对应的 POS 标记。
提取受影响的版本
先利用正则表达式在 CVE 和 git 日志中识别版本号,然后在解析树中定位包含版本号的句子。定位句子主要是为了过滤信息,因为 SemFuzz 是对 Linux 内核的 fuzz,所以只找 Linux 的版本号,过滤其他应用程序的版本号。
提取漏洞类型
作者根据 CWE 为 SemFuzz 设置了 16 种漏洞候选类型,SemFuzz 在解析树中的 NP(名词短语)节点中寻找候选类型,如果没有找 到,就用 CVE 编号作为关键字去 NVD 中搜索 Technical Details 字段得到漏洞类型。
提取漏洞函数
SemFuzz 将未修补的 Linux 内核版本与修补过的 Linux 内核版本 (在 git 日志中表示)进行比较,并将修改过的函数定位为候选脆弱函数。 通过以下观察进一步定位真实的漏洞: (1)如果在 CVE 描述中还提到了一个修补过的函数,则该函数更有可能是漏洞函数;(2)如果 CVE 描述或补丁描述中提到的某个变量,则更有可能与该脆弱功能有关。 SemFuzz 首先在解析树中搜索修补函数的名称,并将发现的函数视为脆弱函数。如果没有发现,SemFuzz 将解析树中的名词与补丁函数中的变量进行比较。
提取关键变量
关键变量需要满足两个条件
- 出现在未被修补的脆弱函数中
- 在 CVE 或 git 日志中有所提及
SemFuzz 首先从未修补的脆弱函数中提取所有变量,构建一个包含变量和其类型信息的符号表。然后 SemFuzz 检查符号表中的任何变量是否存在在解析树的名词或形容词结点。
提取系统调用
因为 CVE 和 git 日志中可能不会显式的提到可以触发漏洞的系统调用,所以作者建立了一个知识库 (系统调用及其参数之间的关系),用于将 CVE 或 git 日志描述中的关键字与特定领域的概念关联起来。
作者基于 Linux 程序员手册 LPM 来构建知识库:
- LPM 的系统调用的 SYNOPSIS 字段进行了声明,若参数中有枚举类型,则 DESCRIPTION 字段列举了类型为枚举的参数的所有值,将这些枚举值与系统调用相关联。
- 系统调用的 see also 字段包含了其他相关信息,在 SEE ALSO 字段的其他 LPM 页面中也会存在相关的系统调用,识别 synopsis 字段的示例代码和 description 字段中的关键结构的特殊值,并将其与 LMP 名称关联
- 当一个系统调用的参数名等于另一个系统调用的返回变量名时,SemFuzz 桥接两个系统调用之间的关联。
在以这种方式分析了所有页面之后,SemFuzz 能够在使用 POS 标签 NN (即名词)在解析树的叶中识别关键字时检索系统调用及其参数。 使用这种方法,SemFuzz 自动分析1082个 LPM 页面,并将373个系统调用与2000多个关键字关联起来,这比仅使用系统调用名作为关键字的次数要多。从我们对112个 cve 的评估中,SemFuzz 可以成功检索其中96个 (86%)的系统调用,以进行进一步的模糊处理。
语义引导的模糊测试
SemFuzz 从 CVE 和 Linux git 日志中的非代码文本中提取必要的信息或指南,以指导模糊处理过程。特别是,检索到的“受影响版本”帮助 SemFuzz 设置正确的测试环境。然后 SemFuzz 使用检索到的“系统调用”生成第一个输入 (即种子输入)。在模糊处理过程中,SemFuzz 对输入执行粗突变,以找到可以将执行移向“脆弱函数”的系统调用序列。在此之后,SemFuzz 通过监视“关键变量”继续对系统调用序列执行细粒度突变,直到根据“漏洞类型”指定的攻击结果的标志发现目标漏洞被触发。
设置测试环境
- 加载一个易受攻击的 Linux 内核并进行一系列系统调用
- 在虚拟机内部构建 Linux 内核,并让 SemFuzz 从外部 (即在主机上)加载它
- 预先构建了103个 Linux 内核版本,以避免冗余构建以节省时间
- 当从 CVE 中检索到版本号时,SEMFUzz 的 out-box 加载器加载对应版本,然后 in-box 向内核发送一系列系统调用
- 观察内核的执行状态,包括执行的函数、关键变量的值和内核的异常事件
- 为了监控执行的函数,利用 KCOV(内核代码覆盖)来跟踪内核中执行的代码
- 为了跟踪关键变量,作者去观察内核函数的参数,而不是关键变量
- 原因
- 在源代码中对关键变量插桩不灵活,每次观察不同的变量都要重新编译
- 动态检测,在运行时定位关键变量,但是这样的变量可能会在编译的时候被优化掉
- 操作
- 对关键变量执行向后的过程内数据流和控制流做静态分析,找到关键变量依赖的参数
- 原因
- 为了捕获内核的异常时间,semfuzz 在虚拟机之外监视内核
实现
基于最先进的 Linux 系统调用 fuzzer Syzkaller 构建 SemFuzz。对于 in-box observer, Syzkaller 可以直接调用 KCOV 的 API,获取内核的执行状态。除此之外,Syzkaller 可以通过随机添加、删除或改变系统调用及其序列中的参数来执行模糊。至于 out-box observer,Syzkaller 监视内核是否崩溃或挂起,还检查内部内核错误检测器的输出 (例如,KASAN 用于检测内存错误,UBSAN 用于检测未处理的行为,如整数超过 (ow))。
生成种子输入
种子输入是通过以下两个步骤构建的。
- 所有检索到的系统调用 (以及检索到的参数值)被放在一起作为不完整的种子输入。
- 如果参数是一个结构,则填充结构中的每个字段。对于枚举字段,用从 LPM 学到的相关枚举值填充。对于其他字段,使用与其类型兼容的随机值填充。
- 其次,SemFuzz 将其他系统调用与检索到的系统调用关联起来,并将它们放入种子输入中。如第4节所述。
- 为什么要这么做的,因为一个系统调用可能无法正确执行,比如漏洞函数相关的系统调用是 send,但是光有 send 不行,send 之前还得有 bind,所以输入的种子得是一个系统调用序列
- 作者进一步将这种相关性扩展到共享相同类型的系统资源 (例如,文件,套接字)的系统调用。通过这种方式,所有相关的系统调用都放在种子输入中。虽然这可能会带来一些无用的系统调用来触发漏洞,但它增加了命中脆弱函数的概率。
粗粒度变异
这一步的目标是生成一个可以让执行到达易受攻击函数的输入 把每次使用输入的运行都称为一个模糊实例。然后,对于每个实例,我们通过 in-box observer 观察 Linux 内核的执行情况,并测量脆弱函数与模糊实例的执行轨迹之间的距离。选择与最短距离对应的输入作为新的种子输入进行下一轮模糊,直到达到任何脆弱函数。
- 将两个节点 dist (n1, n2)的距离定义为 n1到 n2之间最短路径上的节点数
- 基于距离,作者给出了优先级的计算公式
- 那么系统调用序列 s (输入)的优先级公式为:
- KCOV (s)是输入 s 在模糊实例中执行的函数集
- RCG (f)为 f 的反向调用图中的函数集
- 那么公式的意思就是输入 s 的优先集是 s 在模糊实例中执行的函数优先级的最大值
细粒度变异
在找到允许内核运行易受攻击函数的输入 I 之后, SemFuzz 通过监视关键变量的反馈来进行细粒度的变异。
细粒度的变异不会添加或删除输入中的系统调用,而是去修改参数值。
在找到允许内核运行易受攻击函数的输入 I 之后,SemFuzz 继续使用来自监视“关键变量”的反馈来更改输入。
使用基本块之间的距离来测量输入质量,从 b1到 b2的最短路径上的基本块的数量来测量两个基本块 (b1和 b2)之间的距离,记为 distB (b1, b2)。
则输入的优先级由以下两个公式确定:
- KCOVB (s)为系统调用序列 s 中覆盖的基本块的集合
- 函数 f 的修补代码在基本块集合 PATCH = {p1, p2,…, pn}
- e 是 entry
评估
效率
exp 生成的有效性
此处作者分析了 semfuzz 为什么没有给出其余 94 例 cve 的原因:
- 某些漏洞只有在输入 (特别是系统调用的参数)满足特定条件时才会触发,这是 SemFuzz 在有限的时间内难以生成的。
- 某些漏洞只有在出现竞态条件时才有可能出现。由于并发执行的不确定性,SemFuzz 仍然需要更多的时间来触发这些条件。
- 通过选择性符号执行和操纵线程调度来增强模糊的研究有助于进一步提高 SemFuzz 的性能。
信息取的有效性
精度高
性能
性能好
find
这里作者分析了一下 cve 和 git 日志中的内容对生成 exp 的影响。这部分我感觉很好,详实了论文内容,属于是锦上添花。
讨论
- 不能触发设备引起的漏洞,不能触发逻辑漏洞
- 可以将基于语义的 fuzz 推广到其他 fuzz 对象
- 除了 cve 和 git 日志还可以增加更多的信息来源
目的: 方法: 意义: 效果:
可以对 semfuzz 进行拓展,它只支持 Linux 内核 semfuzz 的内容提取部分比较简单,不够复杂 提取的时候哪里用到 NLP 了? 提取系统调用那里也有点简单? 生成种子的时候系统调用序列的顺序没有很好的去处理,并且有点冗余,影响性能了 粗粒度变异那里是如何衡量距离的 后向可达性分析了解一下 Effective of exploit generation 中分析的 poc 没有生成的原因是下一步研究的方向