1. 引言
研究背景:随着物联网(IoT)的爆炸式增长,嵌入式设备(从家用电器到智能家居和个人小工具)的数量急剧增加。虽然物联网设备的好处随处可见,但其固有的漏洞确实会带来新的安全风险。这些漏洞使设备容易受到网络攻击,从而以危险的方式扰乱行业和经济。例如,Mirai僵尸网络在2016年底破坏了数百万台物联网设备,并通过大规模分布式拒绝服务(DDoS)攻击淹没了目标。嵌入式设备爆照增长且存在安全问题
为了促进开发,物联网设备制造商使用开源组件(OSC)来提供在固件上运行的必要和常见功能。从组件的角度来看,固件是在物联网设备上编程的集成组件包。然而,OSC 中的漏洞可能会被引入物联网设备并使其容易受到攻击。例如,Heartbleed 损害了数百万台设备,并利用它们进行缓冲区过度读取攻击来泄露敏感信息,这一点已在 OpenSSL 中报告。开源组件在嵌入式固件中的代码复用导致漏洞传播
现存问题:为了解决物联网设备面临的这些安全问题,制造商提供了新的固件版本进行更新,包括针对已知OSC漏洞的补丁。然而,由于碎片化设备种类繁多,制造商很难为每种类型的设备及时生成补丁。即使发布了修补固件,最终用户也可能会忽略更新固件以缓解漏洞。结果,攻击者进行了 N-Day 的漏洞攻击,而利用补丁信息使攻击变得更加容易。Synopsys 的 2021 年审计报告提到,超过 65% 的制造商发布的固件包含重复出现的 OSC 漏洞,其中许多漏洞是多年前披露的。N-Day 漏洞在嵌入式设备中泛滥,严重影响嵌入式设备安全
研究挑战:由于 N-Day 漏洞存在于特定的 OSC 版本中,通过识别 OSC 版本可以预测安全风险。人们已经提出了许多二进制版本识别方法。然而,当应用于物联网固件中的OSC版本识别时,它们存在自己的一组限制。
- 在固件场景中,不同架构和编译环境中的 OSC 可能会导致与控制流相关和操作码相关的功能显着不同。然而,ATVHUNTER 提取控制流图(CFG)和操作码序列作为识别版本的特征。这些功能的应用会导致显着的性能损失。
- OSSPolice 和 ATVHUNTER 旨在识别 Android 应用程序中的第三方原生库和库版本。这些工具基于 Android 的库依赖关系,无法很好的适应物联网固件研究,并且它们的特征提取方法与物联网固件不兼容。
确定 OSC 的版本后,可以通过查询国家漏洞数据库(National Vulnerability Database,NVD)等漏洞数据库来预测安全风险。 NVD 使用通用平台枚举 (Common Platform Enumeration,CPE) 格式来存储有关易受攻击版本的信息。 然而,某些 CVE 中的 CPE 缺失或与实际存在漏洞的版本范围不一致,这可能会导致修补出现重大延迟。 论文检查了 2016 年至 2018 年的所有 CVE,有 271个 CPE 缺少部分甚至全部易受攻击的版本,其中包括常用的 OSC。论文将物联网固件中 OSC 漏洞检测的挑战总结如下:
- 同一个 OSC 中存在多个类似版本的字符串(C1):许多 OSC 包含多个 "看起来像" 真实版本字符串的字符串(例如,OpenSSL 1.0.2g 中同时包含 1.0.0、1.0.1、1.0.1d 以及1.0.2g)。因此,单纯使用正则表达式匹配并不能找到真正的版本字符串来识别OSC版本。字符串混淆挑战
- 动态方法在物联网场景中不可扩展(C2):论文假设使用特定参数运行 OSC 可以生成 OSC 版本。然而,由于物联网设备的 CPU 架构和外设多种多样,这些 OSC 在没有设备的情况下很难运行。尽管 QEMU 可以对 OSC 进行简单的仿真,但它仍然无法大规模执行。可扩展性挑战
- 构建版本-漏洞关系数据库困难(C3):由于 CPE 与实际存在漏洞的版本范围不一致,需要构建精确的版本-漏洞关系数据库。 因此需要一种方法来自动、准确地从漏洞报告中提取存在漏洞的版本。版本-漏洞关系数据库构建挑战
研究内容:为了应对这些挑战,论文提出了 VERI,一种用于大规模物联网固件 OSC 的 N-Day 漏洞检测系统。VERI 利用轻量级符号执行和静态分析来运行 OSC 并识别其版本(以解决挑战C2)。具体来说,它首先恢复目标 OSC 的整个控制流,包括控制流图(CFG)和调用图(CG)。接下来,它定位入口点和版本输出点以在这两点之间执行 OSC。为此,VERI 确定主函数的起始块,以其输入参数作为入口点,并收集类似版本的字符串文字并定位读取它们作为候选版本点的基本块。然后利用深度优先搜索(DFS)算法和静态污点分析进行 CFG 剪枝,以找到到达版本点的潜在可行路径。最后,它利用输入驱动的符号执行来删除以错误版本输出点结尾的不可行路径(以解决挑战C1),并输出真实的版本字符串。利用轻量级符号执行和静态分析来运行 OSC 并识别其版本
VERI 维护一个 OSC 版本-漏洞关系数据库来检查 OSC 版本是否存在漏洞。由于漏洞描述文本是非结构化的,论文利用一种新颖的实体关系提取模型,该模型从描述结构中学习模式来识别 OSC版本范围(以解决挑战C3)。更具体地说,给定漏洞描述,论文将易受攻击的版本范围提取视为两个任务:(i)定位实体(OSC名称和版本)和(ii)提取名称和版本之间的关系。为了考虑两个任务之间的信息交互,论文采用联合神经网络模型来执行端到端关系提取。最后,将这些关系转换为统一的版本范围,构建版本-漏洞关系数据库。利用新颖的实体关系提取模型构建版本漏洞关系库
实验结果:论文实现了 VERI,并分别在 OSC 版本识别和实体关系联合提取模型中对其进行了评估。评估不同编译设置中的 OSC 版本识别,包括不同的指令集架构(ISA)和优化级别。论文编译不同 ISA 中的开源项目,构建具有 1044 个 OSC 的跨 ISA 数据集。VERI 在版本识别任务的数据集上实现了96.5% 的准确率。此外,论文构建了具有 1252 个 OSC 二进制文件的交叉优化级别数据集,VERI达到了 96.6% 的准确率。为了评估实体关系联合提取模型,论文从 NVD 收集了一个大型数据集,其中涵盖 23,410 个漏洞及其描述。其中,研究团队手动标记了 3822 个描述,用于模型训练、验证和测试。研究团队的模型在 OSC 实体识别中达到了 97.19% 的精确度和 96.56% 的召回率,在易受攻击的版本关系提取中达到了 94.29% 的精确度和 91.86% 的召回率。
论文还对现实世界的固件进行了大规模的 OSC 版本识别。结合版本-漏洞关系数据库,论文在28,890 个固件中检测到了 38,654 个易受攻击的 OSC,每个 MB 二进制文件平均需要 0.572 秒的极短时间。此外,我们对固件安全性有一个有趣的认识,即在固件迭代过程中,制造商没有及时更新 OSC 的易受攻击版本,平均过时天数为 473 天。
2. 背景知识
2.1 漏洞字符串混淆
论文使用现实世界的OSC tcpdump来说明研究团队对 OSC 版本识别的见解。Tcpdump 是一款免费的、功能强大的数据包分析器,广泛用于固件中。论文编译了 tcpdump 4.9.0 源代码(使用编译器 gcc 4.7.1 和优化选项 O0)来获取 OSC 二进制文件并获取部分清单中的代码作为动机示例。OSC 通常包含其版本字符串文字,由其版本输出函数引用。可以通过使用特定参数运行 OSC 来调用这样的函数,以便用户可以获得 OSC 版本来确定 OSC 更新。例如,Listing 第24行的函数print_program_info用于打印版本信息。
然而,OSC 中存在多个类似版本的字符串,这就带来了问题。首先,字符串文字的模式和上下文非常相似,因此使用正则表达式匹配将真实版本字符串与这些类似版本的字符串区分开来是具有挑战性的。例如,第26行引用了OSC tcpdump 4.9.0 的真实版本字符串。但类似地,第 18 行也引用了其他旧 OSC tcpdump 4.7.4 的字符串。同时,集成 OSC libpcap 的字符串 1.7.0 打印在函数pcap_find 中(第30行)。其次,由于这些版本字符串的代码是可能被执行的,因此不能使用数据流分析等经典的静态分析技术来修剪它们。例如,当 user_input 分别取值118、187和68时,可以访问 4.9.0、4.7.4 以及 1.7.0 字符串文字的打印代码。论文发现 user_input 的值由参数 argv 控制,该参数存储用户指定的命令选项。此外,论文发现许多导致假版本字符串输出代码的路径并不是由用户指定的命令选项决定的。受此观察的启发,论文利用用户指定的版本打印命令来修剪代码执行路径并执行真实版本打印功能。研究动机
2.2 漏洞版本不一致
在 2016 年至 2018 年的所有 CVE 中,有 271 个 CPE 缺少部分甚至全部易受攻击的版本,其中包括 OpenSSL 等常用的 OSC。CPE 记录受漏洞影响的软件名称和版本。研究团队通过从 NVD 中爬取 CPE 和漏洞描述,选择了 10 个具有不一致问题的不同 CVE 报告作为动机示例,如下表所示。对于这些报告中受影响的 OSC,论文从 GitHub 收集了它们的所有版本。根据报告中详细描述的漏洞和修补代码,研究团段通过将目标版本代码与漏洞版本和修补版本代码进行比较来手动确认实际的漏洞版本(ground-truth)。当然,通过将 CPE 的版本范围和描述与真实情况进行比较,研究团队发现存在 CPE 遗漏的所有不一致版本,如 CPE Missing 列中所示。纵观下表中的 10 个漏洞,论文发现 CVE 描述总是准确地描述了易受攻击的版本范围,而 CPE 则错过了易受攻击的版本。
鉴于上述发现的事实,论文可以从 CVE 描述中提取易受攻击的版本范围。现有的大量自然语言处理(NLP)研究用于从文本中提取信息。然而,它们在漏洞描述中的应用存在一定的局限性。主要原因如下:
- CVE描述文本是非结构化的,因此基于正则表达式的方法对此无效。
- CVE描述通常包含多头关系,其中多头关系表示与 OSC 名称具有不同关系的多个版本,如图1所示。
2.3 研究假设
由于开源组件的频繁迭代,OSC 通常在二进制文件中包含当前版本信息以供开发人员验证。 这项工作基于这样的假设:OSC 在二进制文件中包含其版本信息。 例如,清单 1 中的 tcpdump 包含其版本字符串 4.9.0。
3. 研究概述
在本节中,论文描述用于检测固件中 OSC N-Day 漏洞的 VERI 高级架构。 论文所提的系统 VERI的概述如下图所示,它由三部分组成。VERI的输入是整个固件映像,输出是固件的易受攻击的OSC 及其 N-Day 漏洞。
OSC 版本识别:VERI 使用轻量级符号执行和静态分析自动识别 OSC 版本。研究团队解压固件镜像,然后从中发现所有可能的 OSC,作为后续分析的对象。给定 OSC,研究团队恢复控制流图(CFG)和调用图(CG)。然后研究团队定位入口点和候选版本点。基于 CFG 与上述两个点,研究团队利用包括深度优先搜索(DFS)和静态污点分析在内的两阶段遍历策略,从 CFG 中删除不相关的节点和边。最后,研究团队使用输入驱动的符号执行来推理从入口点到候选版本点的路径是否可达。如果候选版本点可达,则相应的字符串文字被视为真实版本。
漏洞版本范围提取:研究团队从 NVD 广泛收集漏洞描述。 然后,论文训练一个实体-关系联合提取模型,从描述中识别易受攻击的版本范围,其中实体代表 OSC 名称和版本,关系代表OSC 及其对应版本之间的关系。 最后,论文将提取的信息转换为统一的版本范围,并维护版本-漏洞关系数据库。
OSC 漏洞检测:识别出OSC版本后,VERI 轮询版本-漏洞关系数据库,确认 OSC 版本是否包含在易受攻击的版本范围内,报告OSC的漏洞和安全风险 物联网固件。
4. OSC 版本识别
4.1 固件预处理
VERI 首先使用现成的固件解包实用程序 Binwalk 解包固件映像。Binwalk 可以从固件中提取文件系统,例如 SquashFs、JFFS2、YAFF 等。论文通过白名单从提取的文件系统中发现 OSC。为了构建白名单,论文从 IoT 设备 SDK 的集成组件(例如AWS IoT SDK)以及制造商提供的开源组件列表中收集所有可能的 OSC。
4.2 OSC 图恢复
为了帮助分析,论文恢复了两个 OSC 图:控制流图(CFG)和调用图(CG)。CFG 显示 OSC中所有可能的执行路径。在 CFG 中,节点表示基本块,边表示控制转移。研究团队使用 IDA Pro提供的 IDAPython 来构建每个二进制函数的基本块的 CFG。CG 是一个有向图,用于表示函数及其调用关系。研究团队使用 IDAPython 提取函数信息,并将 CFG 中的块与块之间的调用关系连接起来,创建二进制函数的 CG。
4.3 版本点定位
入口点:OSC 通常会在运行时使用特定参数输出版本信息,例如 "-v"。然而,不能简单地通过基于符号执行的参数来运行目标 OSC。运行 OSC 时会通过共享库的构造函数或预初始化程序执行,这对于符号执行来说是很繁琐的。因此,论文将函数 main 作为入口点以避免初始化程序。为了将参数传递给函数 main,需要将值设置到特定的内存位置。函数 main 的参数包括:
- argc:指定从命令行传递给可执行文件的参数数量
- argv:指向字符的指针数组,其中第一个字符串(argv[0])是可执行文件的名称,后续的每个字符串都是从命令行传递给可执行文件的参数。
基于上面的介绍,论文将 argc 设置为 2,表示参数的数量,将 argv 设置为包含特定参数的字符串数组。这些特定参数概括为 "v、V、version、h、help"。综上所述,论文为函数 main 设置了如下具体值:argc = 2,argv = [binary_name,candidate_argument]。
候选版本点:字符串文字以二进制形式存储在数据部分(例如 .rodata)中。在编译过程中,编译器会删除重复的字符串,以确保该节中仅存在一份字符串副本。论文通过正则表达式匹配从数据部分收集候选版本字符串。程序从数据段中检索字符串文字的两种方式包括:
- 指令直接读取.rodata段的内存地址。
- 存储在.data段中的全局变量被初始化并分配.rodata段的内存地址,指令会读取全局变量以获取字符串文字。
基于这两种方式,论文定位到读取候选字符串文字的指令,例如指令 "mov eax, off_8055120",其中 "off_8055120" 是 .data 段中的全局变量,分配了.rodata 段的内存地址。此外,在生成的可执行二进制文件的CFG中,论文通过确定指令所在的基本块来确定候选版本点。
4.4 CFG 剪枝
由于路径探索和耗时的约束求解,以符号方式执行整个二进制文件效率低下。论文不会执行整个二进制文件,而是专注于与二进制版本输出相关的二进制代码部分,更具体地说,是版本输出的执行路径。论文通过创建一个经过剪枝的 CFG 来隔离这部分代码,这是在两阶段遍历策略中实现的。算法1展示了剪枝CFG的方法。
深度有限搜索:在第一阶段,论文使用深度优先搜索(DFS)在 CFG 上遍历入口点和候选版本点之间的所有可能路径(第2行)。然后,论文修剪CFG中与路径无关的节点和边。为了处理循环情况,论文使用拓扑排序来识别路径中的循环,并删除循环中的最后一条边(第3行)。
污点分析:为了跳过路径中的与 argv 无关且成本高昂的路径前缀(例如,清单1中的第2行的初始化步骤),论文进行静态污点分析。污点分析会标记入口点的污点函数参数并追踪参数的数据流。通过污点传播的数据依赖性,论文确定不相关的调用者,并对它们进行剪枝以减少时间消耗。为了对 CFG 中的每个当前块进行污点分析(第4-18行),论文将各种架构中的二进制可执行文件映射到统一表示的VEX IR。
第一步是准确标记污点源,即函数的特定参数(主函数的第二个参数 argv)。不同架构的二进制可执行文件采用不同的调用约定和堆栈布局,而统一的 IR 代码则失去了这些特性。因此,论文根据固件的体系结构应用相应的调用约定。参数传递规则分为两种情况:
- 当参数与寄存器一起传递时,论文标记这些寄存器为污点源。
- 对于基于堆栈的参数传递,论文首先通过堆栈中的值和基指针确定调用者的堆栈范围,然后根据堆栈中的参数地址调整调用者的堆栈指针,最终标记相应的内存位置为污点源。
对于每个基本块中的指令,论文提取读取节点、写入节点以及它们之间的操作。通过存储和加载VEX IR 操作来访问内存。Put 和 Get 操作指示数据写入寄存器和从寄存器读取的数据。如果操作是 Put 或 Store,论文在读取节点和写入节点之间建立数据依赖关系。同时,根据这些依赖关系动态更新污点节点列表。
为了分析过程间调用,论文检查是否后续函数使用了污点节点作为参数,如果没有,认为调用是不必要的,然后在 CFG 中对其进行修剪。这种检查方法类似于污点源的定位。同时,为了避免修剪不可避免的调用者,PCG 中的调用者默认会被保留。
4.5 符号执行
在OSC中,有一个特定模块用于参数分析,其中包含给定正确版本触发参数。为了确保论证能够被准确解析,研究团队还开发了一个论证分析模块。在分析完参数后,论文利用正确的参数将执行引导到基于符号执行的版本输出代码,这一模块被称为OSC执行模块。
论证分析模块:论文手动检查了 138 个二进制文件中参数解析的方法,主要分为两种类型:
- 67个二进制文件通过getopt相关函数解析参数。getopt是标准C库中著名的函数。
- 71个二进制文件通过自定义代码解析参数。
为了完成预期功能而不是象征性地在复杂的getopt相关函数中执行,论文引入了函数钩子模块,自动钩入并用具体的简化例程替换它们,这样可以降低成本。
OSC 执行模块:通过修剪后的 CFG 和函数钩子,论文利用符号执行来推断哪个候选版本点是可达的。首先,论文使用特定函数输入所述的符号状态设置入口点。然后,VERI 执行每个块,并根据每个符号变量的具体值解决路径约束。例如,如果块的路径约束是 "a > 20",那么满足条件的符号变量a是可解的,路径是可行的。相反,如果a与条件冲突,则该路径是不可行的。论文选择可行的路径并更新符号状态,继续解决新路径上的约束。在路径执行期间,到达并执行打印真实 OSC 版本的候选版本点。论文收集由标准程序输出打印的字符串,然后可以从这些字符串中解析版本字符串。在两种情况下,伪造的候选版本点将无法访问:二进制文件提前退出执行而不进入其中。论文设置了执行时间上限,在此时间内候选版本点不会被访问。
运行示例:
这里是一个运行示例,简要概述论文的模块化分析过程。下面是清单中展示的正在运行的示例:
- 首先建立二进制文件 tcpdump 的 CFG 和 CG。
- 入口点为具有特定输入 "argc = 2,argv = ["tcpdump","V"]" 的块,候选版本点为包含候选版本字符串的块。
- 在 CFG 中应用 DFS 以获得入口点和候选版本点之间的可访问路径,然后去除路径之外的节点和边,确保只保留关键节点。
- 参数 argv 被设置为污点源,通过污点传播和分析,修剪与 argv 无关的函数,并保留与 argv 相关的函数。
- 函数 getopt_long 被挂钩并替换为简化例程,然后论文使用符号执行来确认候选版本点是否可达。
- 在符号执行过程中,经过路径约束求解,确认了特定的候选版本点,从而确定真实的OSC版本。
这一示例展示了论文在模块化分析过程中的几个关键步骤,包括构建CFG、污点分析、函数挂钩、符号执行等,以确定真实的OSC版本。
实际执行:论文的版本识别模块通过 2337 行的Python代码实现,同时整合了多个开源项目的API。论文使用 Binwalk 进行固件映像的解压,IDA Pro 提供的 IDAPython 用于创建二进制的 CFG和 CG。通过 Python 绑定 PyVEX 将二进制文件映射为统一表示的 VEX IR,同时选用 Angr 作为符号执行引擎。这些工具的整合使得我们的版本识别模块具有更强大和高效的功能。
5. 漏洞版本范围提取
论文的模型输入是漏洞描述中的单词序列,通过词嵌入将其转化为词向量。随后,论文将双向LSTM与自注意力机制相结合,以提取每个带有上下文的单词的更复杂表示。Softmax 层生成实体识别任务的输出:
- 实体标签(例如,对于令牌 OpenSSL,相应的输出是 B-COM,其中 B 表示实体中的第一个令牌,COM 表示作为 OSC 名称类型的令牌)。
- 一组元组,包括实体的最后一个令牌以及它们之间的关系(例如,对于令牌OpenSSL的输出是(1.0.1u,之前),表示OpenSSL和1.0.1u之间的关系是之前)。图示中展示了这一模型架构的示意图。
5.1 单词和字符嵌入
输入句子的漏洞描述由 n 个单词组成,表示为 w = [w1, ..., wn],其中每个单词 wi 由词嵌入层中的词向量 Ei 表示。具体来说,该模型利用字符级嵌入将单词视为字符序列(例如,单词 OpenSSL 对应于序列 [O, p, e, n, S, S, L])。字符级向量连接到单词级向量 Ei,然后被输入到 BiLSTM 层中。
5.2 BiLSTM 与 Self-Attention
长短期记忆(LSTM)是一种广泛应用于 NLP 任务的人工循环神经网络(RNN)架构。 BiLSTM 由两个 LSTM 组成,它们能够从左到右和从右到左对句子进行编码。
由于远距离相关词不容易关联,因此论文在 BiLSTM 之后引入 self-attention。 自注意力计算每个单词与所有其他单词之间的注意力分数,以获得它们的相关性,从而更好地关联上下文。
5.3 实体识别
实体识别任务的目标是从描述中识别 OSC 名称和版本。论文将实体识别任务视为顺序标记问题,使用 BIO(Begin、Inside、Outside)编码方案为每个标记生成标签。在模型中,论文有两种实体类型:NM(OSC名称)和VER(OSC版本)。论文将 B 类型标签分配给实体的第一个标记,将 I类型标签分配给实体的其他标记,对于不属于这些实体类型的标记,论文将其分配给 O 类型标签。在论文的示例中,论文为 "OpenSSL" 分配了 B-NM 标签,"1.0.1u" 分配了 B-VER 标签。使用Softmax 函数计算最有可能的实体标签。
5.4 关系提取
关系提取的任务是预测 OSC 名称和 OSC 版本之间的关系。每个 OSC 名称标记 wi 的关系表示为元组 (vi, ri ),其中 vi 是版本标记向量,ri 是关系标记向量。同时,论文将关系提取任务视为多头问题,这意味着一个标记有多个关系。
5.5 版本范围统一
论文使用连接提取模型来处理所有描述,以提取实体和关系。这些结果用于构建版本-漏洞关系数据库。鉴于表示关系的词汇多样性,论文在建立数据库之前对关系类型进行统一处理。论文将关系总结为 “开始” 和 “结束” 两种类型。举例来说,在描述 “OpenSSL before 1.0.1u” 中,论文得到关于 “OpenSSL” 的关系元组为(1.27.2,before),这表示 1.27.2 及之前的版本存在漏洞。论文将该元组转换为通用版本范围形式(最旧版本,1.27.2),同时记录是否包含或不包含边界版本1.27.2。综上所述,统一的漏洞版本范围记录包括 CVE 号、漏洞描述、OSC 名称、起始版本、结束版本、是否包含边界版本等信息。