CLFuzz: Vulnerability Detection of Cryptographic Algorithm Implementation via Semantic-aware Fuzzin

CLFuzz:通过语义感知模糊测试检测加密算法实现中的漏洞

摘要:加密学是许多安全应用的核心组成部分,而隐藏在其实现中的缺陷可能会影响功能完整性,或者更严重地,威胁到数据安全。因此,保证实现的正确性是非常重要的。然而,语义特征(例如多样的输入数据和复杂的功能转换)给传统的程序验证技术(例如静态分析和动态模糊测试)带来了挑战。本文提出了CLFuzz,一种用于加密算法实现漏洞检测的语义感知模糊测试工具。CLFuzz首先提取目标算法的语义信息,包括其特有的加密约束和函数签名。基于这些信息,CLFuzz能够自适应地生成高质量的输入数据,从而有效触发易出错的情形。此外,CLFuzz还采用了创新的逻辑交叉检查方法,增强了其逻辑漏洞检测能力。我们在54种广泛使用的加密算法实现上对CLFuzz进行了评估,结果表明其优于当前最先进的加密模糊测试工具。例如,与Cryptofuzz相比,CLFuzz的覆盖速度提高了3.4倍,并且最终覆盖率提高了14.4%。此外,CLFuzz还发现了8种加密算法中12个以前未被发现的实现缺陷(例如OpenSSL中的CMAC和SymCrypt中的Message Digest),其中大部分是安全关键性的漏洞,并已成功收录到国家漏洞数据库(其中7个已在NVD/CNVD中列出),并且获得了微软漏洞赏金计划的奖励(2个漏洞各奖励$1,000)。

1 引言

    加密算法在保障在线信息传输安全中起着至关重要的作用。为了满足不同应用的多样化需求,已经提出了多种加密算法,包括消息认证码 [7]、对称加密与解密 [20]、椭圆曲线密码学 [35] 等。它们实现了不同的功能并处理不同的数据结构。现有的加密算法实现形式多样,尽管它们共享一些共同的功能。考虑到数据隐私的重要性以及加密算法应用场景的普遍性,算法实现中的缺陷可能会导致严重后果。

    在加密算法实现漏洞检测的各种方法中,模糊测试作为一种有前景的技术脱颖而出。然而,大多数现有的模糊测试方法,如AFL、Libfuzzer及其衍生工具,并不适合用于加密算法的测试。这些工具缺乏对加密算法输入规范的必要理解,也缺乏能够触发此类实现执行的测试引擎。然而,已经有一些尝试对加密实现进行模糊测试。例如,Cryptofuzz [32] 是基于 Libfuzzer [42] 的差异模糊测试工具,支持大多数主流算法,并发现了许多漏洞。它提供了自定义生成器,并根据不同算法的输入数据字段构造种子。它对同一算法的不同实现版本的结果进行差异测试。CDF [5] 是另一个差异模糊测试工具,用于检测加密算法中的实现问题。CDF 已应用于多个算法,包括伪随机函数(PRF)[64]、对称加密与解密 [20]、数字签名算法(DSA)[38] 和 ECDSA [37] 函数。它还通过使用无效参数进行边界测试,触发并检测常见的漏洞。

    这些模糊测试方法已被广泛采用,用于提高加密算法实现的正确性,并且取得了显著的进展。然而,它们对输入数据的特定要求以及对实现版本的依赖,给深入的漏洞检测带来了某些挑战。

    第一个挑战是,不同类别的加密算法对输入数据有不同的语义要求,这使得生成能够满足所有这些要求的通用测试输入变得困难,除非采用自适应生成策略。如果输入要求没有得到满足,验证阶段将会中断执行,算法的主要逻辑将无法触发。例如,在测试高级加密标准128(AES 128)[19]分组密码时,它要求密钥长度为128位,如果提供的密钥长度不是128位,则会导致数据检查失败并返回空结果。如果这种情况反复发生,就会导致测试效率低下。

    第二个挑战在于加密算法实现中涉及的复杂功能转换和数据操作。传统的随机输入生成或基于覆盖率的变异策略难以有效地触发易出错的情形。边界条件,来自于输入的极端长度或特殊值,就是这样的例子。虽然这些条件在日常使用中可能不常发生,但它们有可能由于缺失或错误的处理触发应用程序中的异常行为。然而,传统的基于覆盖率的变异策略生成的输入测试数据,往往无法有效触发这些边界条件。

    第三个挑战是,一些加密算法的实现有限,使得进行有效的差异测试以检测漏洞变得困难。对于某些算法,存在多种计算模式,每种模式的实现都不同。几乎不可能让每个实现都覆盖所有这些模式。例如,针对对称加密和解密,有超过一百种分组密码算法 [22]。OpenSSL [51] 实现了其中超过140种,而 wolfCrypt [66] 仅支持约50种。因此,在对那些仅有少数实现的模式进行模糊测试时,结果的数量不足以进行差异测试,从而导致漏洞检测中的假阴性。

    为了应对上述挑战,我们提出了CLFuzz,一种用于加密算法实现漏洞检测的语义感知模糊测试工具。它充分考虑了加密算法的特性,进行自适应的测试输入生成和漏洞检测。首先,CLFuzz 提取了每个算法的语义信息,包括加密特有的约束和函数签名。然后,CLFuzz 实现了不同数据结构输入字段的自适应测试输入构造策略,以提高触发边界情况的概率。此外,除了差异测试和运行时监控,CLFuzz 还基于算法之间的逻辑关系,在多个测试回合中进行加强的逻辑交叉检查,从而实现更全面、高效的漏洞检测。

    我们在54种广泛使用的加密算法实现上对CLFuzz进行了漏洞检测和代码覆盖率评估。结果表明,CLFuzz优于现有的最先进工具Cryptofuzz和CDF。具体而言,与Cryptofuzz相比,CLFuzz的覆盖速度提高了3.4倍,最终覆盖率提高了14.4%。与CDF相比,CLFuzz的最终覆盖率提高了538.5%,并且在大多数情况下,CLFuzz达到CDF最终覆盖所需的时间不到1分钟,而CDF需要超过4小时。我们还进行了消融研究,展示了CLFuzz的输入生成和漏洞检测策略的贡献。CLFuzz在8种算法中发现了12个以前未被发现的实现漏洞(例如,OpenSSL中的CMAC和SymCrypt中的Digest [46]),其中大部分是安全关键性的漏洞,并已成功收录到国家漏洞数据库(7个在NVD/CNVD中)并获得微软漏洞赏金计划的奖励(2个漏洞各奖励1,000美元)。

总之,本文做出了以下贡献:

(1) 我们提出了一种语义感知模糊测试方法,用于深入检测加密算法实现中的漏洞。

(2) 我们在广泛使用的加密算法实现上设计并实现了CLFuzz。CLFuzz基于提取的加密特有的约束和函数签名自适应地生成高质量的测试输入,并通过逻辑交叉检查检测更多漏洞。

(3) 我们对CLFuzz进行了全面评估,展示了其有效性。CLFuzz在覆盖率、速度和漏洞检测能力方面优于现有的最先进工具Cryptofuzz和CDF。它已发现了许多安全关键性漏洞,并已开源供实际使用。

    文章的其余部分组织如下:第2节介绍了基于生成的模糊测试和加密算法的背景。第3节通过一个示例说明了我们的动机,并描述了现有工作面临的挑战。第4节正式介绍了CLFuzz的设计。第5节与现有的最先进工具对CLFuzz进行了评估。第6节进一步讨论了CLFuzz的一些特点和局限性, 第7节介绍了相关工作。第8节给出了结论。

2 背景

2.1 基于生成的模糊测试

    模糊测试起源于1990年,当时Miller等人[47]应用一种随机测试工具来评估UNIX工具的可靠性。如今,模糊测试已成为一种流行且有前景的动态测试方法,用于发现软件中的实现漏洞。模糊测试的核心是持续地发送大量输入,试图让程序以未预期的方式运行,这可能导致内存崩溃、极端资源使用、错误的输出结果等问题。已有许多学术综述[41, 45]对当前模糊测试领域进行了统一的总结。

    基于生成的模糊测试[48]是模糊测试的主要类别之一。典型的例子包括Peach [49]和Sulley [1],它们根据描述测试用例要求的规范或数据模型生成测试输入。如今,研究人员将基于生成的模糊测试框架与特定领域的语法要求相结合,引入了基于语法的模糊测试,解决了某些场景中严格输入格式的问题。语法可以手写,也可以通过程序特征分析进行挖掘。像ProFuzzer [69]、CodeAlchemist [33]、NAUTILUS [4]等工具使用高度结构化的配置生成语法和语义上有效的模糊输入。此外,CSmith [67]和LangFuzz [36]使用控制流图(CFG)生成有效的测试输入。Zest [55]通过确定性的参数化生成器增强了基于生成的模糊测试工具,来找到语义有效的输入。ISLa [62]利用像Z3这样的求解器,在开发人员提供的配置上解决语法结构上的语义约束和谓词。SLF [68]通过AFL [26]的输入检查来挖掘有效输入。总体而言,测试输入的质量直接影响模糊测试器的有效性。为了创建一个设计良好的测试输入生成策略,基于生成的模糊测试器通常需要进行大量的前期工作来探索输入规范。

2.2 加密算法

    加密算法的实现通常被封装为库,并通过API函数调用提供对每个支持特性的访问。如今,加密技术已经从仅供政府和军事机构使用的领域,发展成为全球开发者广泛使用的技术。因此,许多专有和开源的加密算法实现逐渐涌现。这些实现被个人和企业广泛使用,作为安全应用中的核心组件,用于进行安全的在线交易、通过安全电子邮件通信以及B2B(企业对企业)交易[63]。因此,加密算法实现的安全性和健壮性对于确保应用程序的安全至关重要。

    加密算法使用多种编程语言实现,包括C、Go、JavaScript等。相同算法的不同实现虽然提供相似的功能,但在实现方式上各有差异。例如,SSL和TLS算法分别由OpenSSL [51]和WolfSSL [66]实现,它们提供网络连接安全的功能。然而,它们使用了不同的数据结构和不同的依赖项来实现相同的算法逻辑。尽管加密算法本身在理论上是可靠的,但要确保所有实现的正确性仍然非常困难。

3 动机

3.1 加密算法实现的漏洞

    由于加密算法的多样性和不断更新,避免实现漏洞变得非常困难。图1展示了OpenSSL中EVP_DecryptUpdate()算法的一个漏洞。这是一个近期发现的漏洞,并已被分配了CVE编号:CVE-2021-23840 [18]。从第1行到第16行的代码将变量fix_len的值设置为1。在第17行,函数evp_EncryptDecryptUpdate()根据参数inl计算*outl的值。如第20行所示,如果fix_len的值为1,则*outl的值会被固定。如果*outl的原始值加上b超过了INT_MAX,*outl将溢出,并在第21行呈现负值,而函数仍然返回1,这意味着函数成功执行。这可能导致应用程序行为不正常或崩溃。开发人员通过在计算之前添加额外的检查来修复此问题,检查代码如第7行到第10行所示。

图1 OpenSSL中的溢出错误及其修复。如果输入长度接近整数的最大允许长度,则变量*outl将发生溢出。

3.2 检测这类bug的挑战

    触发此漏洞对于现有工具来说具有挑战性。该函数基于预定义配置执行基于块加密的对称加密或解密。在主算法执行之前,会进行严格的检查以确保输入元素符合语法要求。以上面的例子为例,漏洞的位置在函数EVP_DecryptUpdate()中。在此函数的开始部分,会对第一个参数ctx进行详细检查,因为它是包括密钥、IV [40]等在内的配置的核心。为了形成合格的ctx,如OpenSSL提供的官方示例[52]所示,用户需要调用EVP_DecryptInit_ex()来设置配置,包括密钥、IV、加密模式等。EVP_DecryptInit_ex()的输入结构如图2所示。在EVP_DecryptInit_ex()中,如果密钥或IV的长度与加密算法的语义限制不一致,则在初始化过程中会发生栈溢出运行时错误。这是异常的,并会中断执行。因此,用户必须确保密钥和IV的长度符合规范。为了触发图1中的代码执行,模糊测试器必须生成符合所使用的加密模式语义规范的测试输入。

图2. 函数EVP_DecryptInit_ex的输入结构。它需要五个参数,包括*ctx、*cipher、*impl、*key和*iv。

    对于Cryptofuzz来说,触发这个漏洞是具有挑战性的,因为随机生成的密钥和IV很难满足长度要求。图3是Cryptofuzz生成的一个示例输入。对于加密模式AES_192_CBC,要求的密钥长度是24字节,要求的IV长度是16字节。

图3. Cryptofuzz生成的测试输入。密钥和IV的长度与规范不匹配,且密文的长度无法触发错误条件。

    然而,图3中显示的输入具有4字节的密钥和8字节的IV。因此,执行此输入时将在数据检查过程中被中断,无法触发含有漏洞的代码段。基于Libfuzzer,Cryptofuzz继承了覆盖反馈机制,能够保留有效输入,并经过较长的学习阶段逐步学习输入规范。然而,它的学习阶段导致了低效的测试,并且学习后的测试效率仍然无法达到提前设定标准的水平。此外,只有当密文的长度(如图1所示的inl)接近整数的最大允许长度时,才能触发漏洞。由于随机达到这一条件的概率较小,Cryptofuzz很难生成满足这一要求的输入。

    为了有效地发现此类漏洞,模糊测试器需要克服三个挑战:(1)它应当探索每个加密算法的语义限制,以构造有效的输入进行执行。具体来说,它需要获取目标算法的参数约束,并生成符合检查的输入。(2)它需要为边界条件生成输入,这些输入可能基于每种数据类型的边缘情况触发错误。对于加密函数,边界条件通常与极端值有关,如整数、长整数或零的最大值。图4展示了一个可以触发上述漏洞的期望输入。如第6行和第7行所示,生成的密钥长度和IV长度符合目标计算模式的语法规则,因此可以通过数据检查。此外,如第3行所示,输入密文的长度接近最大整数,这是明文的典型边界条件,会导致异常行为。(3)除了差异测试,还需要更多的漏洞检测机制。有些算法只有少量实现,无法进行足够的差异测试。因此,应该设计新的验证机制来检测逻辑漏洞,如本例所示。

图4. 一个可以触发漏洞的期望输入。IV的长度为16字节,密钥的长度为24字节,密文的长度极长。

4 CLFUZZ设计

    CLFuzz旨在全面测试加密算法的实现。图5展示了CLFuzz的整体框架。它基于提取的语义信息生成高质量的测试输入,然后采用各种漏洞检测技术,包括异常监控、差异测试和逻辑交叉检查,以检测潜在的漏洞。在本节中,我们将介绍核心设计的详细内容:语义信息提取、测试输入生成和漏洞检测。

图5. CLFuzz的整体框架:(1)CLFuzz接收目标算法和模式;(2)CLFuzz进行自动语义信息提取,提取加密特定的约束和函数签名;(3)CLFuzz根据语义信息开发输入生成策略,构造测试输入的各个字段,然后将它们序列化为字节串;(4)测试输入触发执行,CLFuzz收集结果进行异常监控、差异测试和逻辑交叉检查;(5)CLFuzz输出结果并生成漏洞报告。

4.1 语义信息提取

    CLFuzz执行语义信息提取,以获取关于目标加密算法输入的各种规范。因此,CLFuzz能够生成高质量的测试输入,这些输入足够高效,可以触发算法核心过程的执行,并且足够有效,能够触发易出错的情况。CLFuzz关注两种类型的语义信息:加密特定的约束和函数签名。接下来,我们将分别详细介绍它们的提取方法。

    4.1.1 加密特定的约束。为了确保安全性和完整性,加密算法在核心计算过程之前会对输入数据进行严格的有效性检查。加密特定的约束是指在使用加密算法时必须遵循的规则和要求。CLFuzz提取这些领域特定的约束,以生成能够通过检查并触发更深层执行逻辑的高质量测试输入。

    CLFuzz考虑了四种典型的加密特定约束:

    — 密钥长度:加密函数中使用的密钥长度应满足所需的长度,以确保算法的功能。例如,在进行对称加密时,可以使用多种分组加密算法,包括AES、SM4、ARIA等。它们对密钥长度有不同的限制。AES和ARIA根据计算模式支持128位、192位或256位,而SM4仅支持128位。

    — 初始化向量(IV)长度:在一些加密算法中,如使用分组链接(CBC)模式的分组加密算法,需要使用初始化向量(IV)。IV的长度要求与分组大小相等,而分组大小在不同的加密模式中是不同的。

    — 分组大小:在加密学中,分组大小指的是由分组加密算法处理的固定大小的数据块。它直接决定了基于分组加密的对称加密输出密文的长度。因此,相应解密算法的输入密文必须满足此长度要求。

    — 哈希大小:在加密学中,哈希大小指的是哈希函数生成的输出的大小。在基于哈希函数的算法中,如密钥派生函数,支持的输出长度通常受到底层哈希函数大小的限制。

    CLFuzz 覆盖了 144 种分组密码和 18 种哈希函数的测试,这些算法具有上述提到的各种约束条件。对于密钥长度、IV 长度和块大小,CLFuzz 利用加密库提供的公共 API,自动查询这些特定于加密算法的约束。由于特定算法的功能在不同库中保持一致,我们选取了 OpenSSL 作为提取源,因为它支持最完整的算法。图 6 显示了接收分组密码类型并返回相应约束条件的 API。对于没有 API 支持的哈希长度,CLFuzz 会使用所有可用的哈希函数加密任意明文,并记录输出的长度。

图6. 用于查询密钥长度、IV长度和块大小的API

    4.1.2 函数签名函数签名指的是函数的特征,包括参数的数量和数据类型。CLFuzz 提取这些针对算法的函数签名,以生成语法有效的测试输入,覆盖广泛的场景,同时有意引入错误以测试代码的错误处理能力。

    我们通过图 7 中的例子来说明 CLFuzz 如何提取函数签名,该例子展示了 OpenSSL 中对称加密的实现。加密过程由三个子过程组成:EVP_EncryptInit 初始化对称加密算法的加密上下文;EVP_EncryptUpdate 多次调用以逐块加密输入的明文并生成相应的密文;EVP_EncryptFinal 通过加密剩余的明文数据并生成剩余的密文来完成加密过程。

图 7. CLFuzz 提取 OpenSSL 对称加密函数签名的工作流程

    CLFuzz对目标算法的声明进行函数签名提取,以获取整个对称加密过程所需的输入参数的关键信息。

    在此示例中,有五个参数由测试输入指定,包括 EVP_CIPHER *cipher、unsigned char *key、unsigned char *iv、unsigned char *in 和 int inl。CLFuzz主要关注参数的数量和数据类型。参数数量指导CLFuzz确定为每个参数分配适当的长度,确保测试输入的总长度在可接受的范围内,同时涵盖不同的参数组合。数据类型提供了关于其语法格式和边界条件的信息,包括它们可以接受的最小值和最大值,以及可能触发函数中特定行为的特殊值。通过提取这些签名,CLFuzz生成高质量的测试用例,全面覆盖各种场景,并有效地识别目标算法中的潜在问题。

4.2 输入生成

    在本节中,我们介绍了CLFuzz如何生成高质量的测试输入。CLFuzz采用逐字段的方法进行测试输入生成。CLFuzz首先应用选定的策略来构造每个字段,字段代表目标算法的一个参数。然后,这些字段被组合并序列化为字节串,接着传递给测试引擎执行。

    CLFuzz根据提取的语义信息建立生成策略。对于那些具有密码学特定意义的输入字段,CLFuzz考虑其提取的密码学特定约束,以生成语义上有效的输入,确保测试的有效性。此外,CLFuzz还应用混沌策略生成一小部分无效输入,专门用于测试目标实现的错误处理能力。对于其他没有密码学特定约束的字段,CLFuzz根据其数据类型签名选择生成策略。CLFuzz根据数据类型的语法格式和边界条件设计了不同的数据类型的特定策略。总体来说,字段生成策略有六种:

    (1) 语义有效:字段应符合密码学特定约束。 (2) 极小值:字段达到其能接受的最小值。对于字符串,长度应接近于0;对于无符号整数,其值应接近于0。 (3) 极大值:字段达到其能接受的最大值。最大接受的长度或整数值基于配置或执行平台。 (4) 特殊内容:字段的内容由无符号字节的极端值组成,如0x00和0xff。 (5) 空值:输入字段为空或未定义,表示没有值,例如NULL。 (6) 随机:输入字段的长度和内容是随机生成的。

    在提取所有目标加密算法的函数签名后,CLFuzz根据输入字段的角色和数据类型将其分为六类:

(1) 明文:明文表示需要加密的文本。在CLFuzz中,它以一定长度的字节串形式显示。

(2) 密文:密文表示加密算法的输出密文,反过来,它也是解密算法的输入字段。在CLFuzz中,它以一定长度的字节串形式显示。与明文不同,密文的长度与加密算法密切相关。

(3) 密钥:加密算法中的密钥。在CLFuzz中,它以一定长度的字节串形式显示。密钥通常有两种类型:公钥和私钥。它们在算法中承担不同的功能,但在数据层面上表现相似。

(4) IV(初始化向量):IV广泛用于块密码中。包括CBC(密码块链接)[23]、密码反馈(CFB)[22]、输出反馈(OFB)[22]等多种块密码模式都需要初始向量来参与第一个块的加密。在CLFuzz中,它以一定长度的字节串形式显示。

(5) 数字:数学数字在加密算法中广泛使用。例如,在椭圆曲线算法中,私钥是一个大数字。因此,大数字通常参与加密算法中的数学运算。一些加密算法还提供接口用于大数字的基本数学运算。在CLFuzz的测试输入中,它以字符串形式显示,并在执行过程中转换为整数。

(6) 模式:某种类型的加密算法可能具有多个计算模式。例如,消息摘要算法支持多种哈希计算模式,如MD5、SHA256、SM3等。此外,椭圆曲线算法有多种曲线,且对称加密支持多种块密码模式。每个测试输入都针对某个特定的算法模式。

    对于每个类别,CLFuzz定制了其生成策略,以更好地针对特定特征,实现高效测试,并同时彻底测试代码的错误处理能力。表1展示了每个输入字段的生成策略。

表1. 六种输入字段的生成策略

    对于明文(Plaintext)和数字(Number),通常没有加密语义上的长度或内容约束。然而,它们的长度或内容的边界条件可能在极端情况下触发错误,因此在生成这些输入时应用相应的生成策略至关重要。对于密钥(Key)、初始化向量(IV)和密文(Ciphertext),加密算法通常对其长度有严格的语义约束,因此在测试输入生成时必须覆盖其特定特性。此外,一些极端值可能触发异常行为,因此这些值也应包含在内。模式(Modes)仅在目标实现支持时有效。大多数模式根据其语义约束进行选择,在某些情况下,使用空值来覆盖极端情况。对于所有其他输入字段,保留随机策略以涵盖一般情况。

    图8展示了CLFuzz如何创建测试输入,并详细分解了其组成过程。首先,选择目标算法及其计算模式(如适用),并提取其语义信息。例如,在本例中,CLFuzz测试基于AES_128_CBC的对称加密,其语义信息已如图7所示提取。接下来,对于每个参数,CLFuzz使用特定的生成策略构造其内容,同时考虑从语义信息中提取的约束和边界条件。对于密码、密钥和IV,CLFuzz应用语义有效策略;对于in和inl,CLFuzz应用空值和极小策略来测试边界条件。为了便于后续的字段拆分过程,每个字段的长度会附加在其内容前面。最后,CLFuzz将所有生成的字段组合成一个序列化的字节串,并添加标识信息,包括总长度、目标算法和模式,以完成测试输入的构建。生成的输入随后被传递给模糊测试引擎,在支持目标算法的每个实现上执行。

图8. CLFuzz的测试输入生成过程。此输入针对基于AES_128_CBC块密码的对称加密。

4.3 漏洞检测

逻辑交叉检测:

·  多实现对比:通过将目标加密算法与其多个不同的实现进行对比,检查它们的执行结果是否一致。这些实现可能来自不同的加密库或版本,或是不同的计算模式等。

·  功能一致性检查:通过执行一系列已知的测试用例,验证算法在不同输入条件下是否能提供一致且正确的输出。这不仅有助于发现可能的逻辑错误,还能帮助确保算法的稳定性和安全性。

·  状态一致性验证:在一些加密算法中,算法的状态(如加密上下文或缓存)可能会影响最终的结果。通过对比不同状态下的输出,可以检查算法是否在状态转换过程中存在逻辑错误。

·  异常行为检测:通过对加密算法的异常输入进行测试,观察算法是否能适当处理这些输入并给出合理的错误提示。逻辑交叉检测能够有效地发现潜在的漏洞,避免算法在异常情况下执行不当或崩溃。

    CLFuzz进行全面的三阶段漏洞检测过程。为了检测逻辑漏洞,CLFuzz定制了特定的预言机进行逻辑交叉检查,验证目标算法的功能完整性和健壮性。此外,在执行过程中,CLFuzz密切监控资源使用和执行时间等运行时指标,以检测任何异常行为。执行后,CLFuzz收集输出结果进行差异化测试,检查执行结果的正确性。我们将在以下部分详细介绍这三种漏洞检测方法。

    逻辑交叉检测。加密算法建立在其功能之间的逻辑属性基础上,这为CLFuzz提供了通过不同算法之间的多轮测试进行逻辑交叉检测的新方法。利用不同算法之间的领域特定关系,CLFuzz使用相应的测试oracle(检测工具)来检查加密功能是否按预期工作,并产生有效的结果。

    具体来说,CLFuzz引入了以下几种oracle(检测工具):

(1)加密-解密: CLFuzz通过生成明文输入数据,使用给定的算法和密钥进行加密,然后尝试使用相同的密钥和算法解密结果的密文,来测试对称加密和解密功能。这个oracle验证解密后的数据是否与原始的明文输入匹配,从而确保加密和解密功能的正确性。

(2)密钥生成-验证: 密钥生成是创建新密钥的过程,而密钥验证涉及检查密钥的有效性并使用该密钥进行加密或解密。CLFuzz使用密钥衍生函数(KDF)的输出作为密钥验证的输入,以确保生成的密钥是安全的、随机的,并且能够正确地进行数据加密和解密

(3)签名-验证: 数字签名使用私钥为给定的输入生成数字签名,而验证过程则使用相应的公钥检查签名的真实性。CLFuzz生成输入数据并用私钥对其进行签名,然后使用相应的公钥验证签名,以确保数据没有被篡改,并且生成的签名是有效的

    图9展示了CLFuzz如何在多个测试回合中进行逻辑交叉检测的示例。(a) 加密-解密: CLFuzz记录下特定的配置,包括用于SymmetricEncrypt的密钥和加密模式。然后,它将相同的配置应用于SymmetricDecrypt函数,并将解密结果与输入的明文进行比较。如果结果匹配,则逻辑交叉检测成功。(b) 密钥生成-验证: 在第1回合,CLFuzz执行KDF生成私钥。然后,在第2回合,CLFuzz将私钥作为输入传递给ECC_GeneratePublicKey,以获得公钥对。最后,在第3回合,CLFuzz将公钥对作为ECC_ValidateKey函数的测试输入,并检查验证结果。(c) 签名-验证: CLFuzz首先与(b)中的第1和第2回合相同的过程生成ECDSA_Sign的密钥对。然后,CLFuzz使用私钥对消息进行签名,并使用相应的公钥对签名进行验证,使用ECDSA_Verify函数进行验证。如果验证通过,则检测成功。逻辑交叉检测能够揭示加密实现中的隐藏缺陷,例如生成无效的密钥、无法验证的签名等问题。

图9。多个测试回合中逻辑交叉检测的示例。CLFuzz将前一个测试回合的输出作为后续测试的输入。

    为了进行交叉检查,CLFuzz 使用一个 oracle 回收池模型来回收之前测试回合的输出结果,并将其结构化为指定的数据格式。这些格式由多个结构体定义,结构体包含特定数据集的字段和关键配置。例如,为了回收为椭圆曲线算法生成的公钥对,数据结构应包括以下字段:(1)曲线 ID,用于指定曲线类型;(2)私钥,用于生成此公钥的私钥值;(3)公钥,公钥对的值。相同结构的数据被收集到池中以进行保留。为了确保收集到的数据能够充分利用并持续更新,CLFuzz 采用了位置循环数据添加和提取的方式来维护池。它使用位置指针来标记下一个添加和提取的位置,每次操作后指针移动到下一个位置。此过程确保提取数据的有效性,并使 CLFuzz 能够充分利用每一条数据。

    由于 oracle 回收池模型可以暂时存储前面测试回合的结果,CLFuzz 可以在随后的任何回合中选择性地从池中检索并利用存储的数据来生成新的测试输入。这样,CLFuzz 无需考虑 oracle 所需的多个执行顺序,而只需专注于当前回合的测试。例如,在使用签名验证 oracle 时,CLFuzz 执行第一回合生成签名,但不会立即验证签名。相反,它将相关参数和结果存储到池中。当 CLFuzz 决定以后进行签名验证功能的模糊测试时,它从池中检索签名,并生成新的输入,而无需关注第一回合何时执行。这种方法消除了多个回合之间严格连续性的需求。因此,在集成新的 oracle 实现(例如针对新 KDF 模式的密钥生成验证 oracle)时,CLFuzz 不需要重新实现整个执行链。它可以简单地重用现有池,允许高效地集成额外的 oracle,而不会带来显著的开销。

    此外,CLFuzz 可以验证中间结果并对其进行强烈变异,从而实现更多变种的 oracle。首先,CLFuzz 对多个执行过程中获得的中间结果进行异常监控和差异检查。此外,在重新使用池中的回收数据时,CLFuzz 提供了灵活的数据利用方式。它可以直接使用原始的回收数据,或者在输入生成过程中故意修改数据。这使得 CLFuzz 可以探索不同的变种和场景,以测试目标算法的鲁棒性。例如,在从密钥派生函数生成密钥时,CLFuzz 假设原始生成的密钥是有效的,并应通过后续的验证检查。如果验证失败,则 CLFuzz 识别为潜在的漏洞。然而,如果修改后的输入或“污染输入”仍然成功通过验证,CLFuzz 会识别出未能识别无效性。这使得 CLFuzz 能够彻底测试目标算法的错误识别和处理能力,探测潜在的弱点或漏洞。

    运行时监控。为了监控加密实现的执行行为并检测异常的运行时活动,CLFuzz 使用了包括 AddressSanitizer [28] 和 UndefinedBehaviorSanitizer [30] 在内的工具。这些工具提供程序异常行为和安全状况的实时反馈。在触发极端情况的测试输入下,CLFuzz 捕获运行时错误,例如异常的资源消耗、崩溃、异常终止和内存损坏,从而全面评估所测试的加密实现的可靠性和鲁棒性。

    差异化测试。对于每一轮测试,一旦测试输入被执行,CLFuzz 收集所有目标加密实现的输出结果并进行差异检查。通过比较结果,任何与其他结果不同的输出都被识别为潜在的错误。这种方法基于一个事实:特定加密算法的功能在不同实现中保持一致。因此,对于相同的测试输入,输出结果应该高度一致。对于支持多种实现的测试输入,输出结果通常足以突出任何异常结果。然而,如果输出结果过少,无法确定哪个结果异常,则需要进一步分析。在这种情况下,结果可以通过错误报告手动进行分析。

6 讨论

6.1 新目标的可扩展性

    目前,CLFuzz 已经被应用于测试 54 种加密算法的广泛实现。为了不断提升其普遍性和完整性,我们需要持续补充新的算法或新的实现来丰富测试元素。因此,可扩展性对其可持续发展尤为重要。CLFuzz 已经为新的成员提供了标准化的工作流程。为了结构化扩展,已经定义了一个类模块,如图 16 所示。

图 16. 用于测试新算法实现的类模块结构。

    要添加一个新的算法进行测试,首先需要实现相应的驱动程序,利用目标实现提供的API来执行功能。

    图17展示了如何将当前的CLFuzz扩展到yescrypt库中KDF_PBKDF算法的实现。首先,我们为yescrypt初始化一个新模块,并在第1-5行声明KDF_PBKDF函数。然后,根据文档和测试文件,我们获取yescrypt执行KDF_PBKDF的过程,并在第7-26行实现驱动程序。我们将CLFuzz生成的测试输入解析为API的参数。在第10-13行,CLFuzz解析输入参数,包括密码、密钥长度、盐值等。接着,在第15-17行,CLFuzz进行yescrypt的语义数据检查。最后,在第19-22行,我们执行目标API,获取输出结果,并以统一的输出格式返回,供后续检查使用。

Fig. 17. yescrypt中KDF_PBKDF函数的示例驱动程序

    此外,我们通过两步来补充相应的测试输入生成策略。首先,提取关于该算法的新语义信息。其次,探索其输入字段并为不同的数据类型应用指定的生成策略。之后,CLFuzz便可以支持新的目标算法。为了向测试中添加新的实现,只需实例化该类模块的新实例,并覆盖相应的虚函数。

6.2 Bug检测的完整性

    加密算法种类繁多,且新的算法或计算模式不断涌现。因此,加密算法的实现也在不断更新。不同的实现支持不同范围的算法,其中一些实现仅专注于某一类算法。在测试过程中,全面覆盖实现中的所有算法是具有挑战性的。

    一个主要原因是,某些算法或模式仅由少数实现支持,导致差异化测试中输出结果不足。尽管CLFuzz已经采用了逻辑交叉检测,利用算法的逻辑特性来丰富漏洞检测方法,但并不是所有的加密算法都有可用的语义信息。另一个原因是,不同实现暴露的功能接口层次各不相同。为了方便用户,一些实现仅提供顶层接口,并在其中自行定义了执行过程;而一些其他实现则提供了更为详细的接口。在这种情况下,同时对顶层接口和详细接口进行全面测试是很困难的。

7 相关工作

7.1 加密算法测试技术

    目前,各种测试方法已被应用于加密算法或API的检测,包括静态和动态测试。CryptoGuard [59] 是静态漏洞检测的典型案例,专注于在大规模Java项目中检测加密API误用。对于每种常见的加密漏洞,CryptoGuard定义了相应的代码规则,扫描项目中的漏洞。此外,它通过识别语言特定的无关元素,如常见的编程习惯和语言限制,来精细化程序切片,从而减少误报。它取得了客观的成果,并在大规模的Apache项目和Android应用中发现了多个漏洞。

    在动态测试方面,也有一些现有的成果。由Google开发的Project Wycheproof [27],通过定向方法使用量身定制的测试向量,基于加密理论和历史弱点对加密算法进行测试。DifFuzz [50]则使用资源引导启发式方法来检测侧信道漏洞,通过寻找能最大化两个程序版本之间资源消耗差异的输入来进行测试。另一个例子是CDF [5],一个动态测试工具,结合了模糊测试和已知漏洞以及边界情况的无状态专用测试向量,用于加密算法的测试。此外,Cryptofuzz [32]是一个差异化模糊测试项目,分析加密算法并比较其输出,以发现实现上的差异。它还通过使用Sanitizers [29]来检测内存漏洞。Cryptofuzz支持广泛的算法,并在加密算法测试方面取得了显著成果。

7.2 传统模糊测试

    模糊测试是一种有效且有前景的检测算法实现漏洞的测试方法。传统的模糊测试器可以根据种子生成原理分为两种类型:基于生成的模糊测试器和基于变异的模糊测试器 [48]。

    典型的基于生成的模糊测试器包括Peach [49]、Peach* [44]和Sulley [1]。它们主要针对具有严格输入格式要求的目标。基于用户预定义的格式规范,它们为每轮测试生成高质量的种子,并根据执行结果引导进一步的模糊测试过程。

    基于突变的模糊测试器使用初始种子或之前生成的种子,通过对其进行字节或位级的修改来生成新的种子。其中之一是American Fuzzy Lop (AFL) [26],它是一款因其在bug检测方面的卓越性能而闻名的覆盖引导模糊测试器。为了提升AFL的性能,已经开发了一系列的工具家族 [10, 24, 57, 58]。与AFL相比,AFL++ [24]实现了更快的速度、更好的突变策略以及更多的仪器化支持。AFLSmart [10]将AFL的灰盒模糊测试与高层次的结构化种子结合,以探索新的输入域,同时保持种子的有效性。此外,Libfuzzer [42]是一款进程内的、覆盖引导的、进化型模糊测试引擎。在正常情况下,它使用多种策略来进行种子突变。此外,它还为用户提供了定制突变或生成策略的接口。通过使用这个接口,Libfuzzer可以生成符合待测软件规范的结构化种子。

    然而,大多数现有的传统模糊测试方法无法应用于加密算法的测试。主要原因是它们缺乏对加密算法输入规范的理解,以及无法触发其实现执行的测试引擎。

7.3 基于属性的测试

    除了传统的漏洞检测方法,还有一些当前的工作在漏洞检测阶段考虑了目标算法的语义信息。它们设计了程序应始终满足的属性,并将这些属性作为预言机来检测意外行为。这种方法被称为基于属性的测试(PBT)。

    基于属性的测试(PBT)由Haskell库QuickCheck [15]推广开来。QuickCheck要求程序员提供程序的规范,以函数应满足的属性形式,并通过大量随机生成的案例来测试这些属性是否成立。FsCheck [25]采用相同的方法来测试.NET程序。此外,一些工作优化了PBT的随机输入生成策略,并提出了针对性基于属性的测试(TPBT)。例如,Zest [55]将QuickCheck的随机输入生成器扩展为确定性的参数生成器,以更好地探索测试程序的语义分析阶段。Target [43]通过搜索策略指导PBT。PropEr [56]将Erlang的类型和函数规范与PBT结合起来。除此之外,变换测试(MT)也是一种基于属性的测试形式。它是基于变换关系(MRs)进行的,变换关系是目标函数或算法针对多个输入及其期望输出的必要属性。变换测试最早由陈太元 [13]在1998年提出,作为一种软件验证技术,已广泛应用于各种类型的软件质量评估的实际应用中。文献 [14, 61]中的调查总结了变换测试的研究成果和应用实践。一些应用示例包括Web服务 [11]、图像处理软件测试 [31]、自动驾驶车辆 [34]以及自动驾驶模型 [21]。PBT的主要优点是能够发现传统测试方法可能遗漏的逻辑实现错误。它还可以帮助识别和隔离缺陷,使调试和修复问题变得更加容易。

7.4 主要差异

    与现有工具不同,CLFuzz作为一种专门针对加密算法的生成型模糊测试工具脱颖而出。它充分利用加密算法的特定约束,生成高质量的测试输入,有效地针对加密算法实现中的脆弱组件进行测试。CLFuzz生成的输入不仅满足必要的数据检查,还能够触发容易出错的场景,全面测试这些实现的鲁棒性。

    此外,CLFuzz在错误检测阶段利用了加密算法中独特的逻辑关系。现有工具无法解决加密算法测试中的挑战。CLFuzz首先提取并设计了54种目标算法之间的逻辑关系,并建立了精确覆盖加密算法特性的加密特定预言机。然后,为了将这些预言机与高效的模糊测试相结合,CLFuzz利用预言机回收池模型暂时存储前几轮测试的数据。这消除了多轮逻辑交叉检查之间严格的连续性要求,从而减轻了配置不同预言机的负担,增强了其扩展性。此外,CLFuzz还可以验证中间结果并对其进行强度突变,从而实现更多预言机变种的应用。

8 结论

    在本文中,我们提出了CLFuzz,一种用于加密算法漏洞检测的语义感知模糊测试工具。具体来说,CLFuzz提取了加密算法的语义信息,包括它们的加密特定约束和函数签名。基于上述信息,CLFuzz构造出高质量的测试输入,这些输入符合复杂的输入限制并能触发容易出错的边界情况,从而显著提高了测试效果。此外,CLFuzz还基于算法的逻辑属性,在多个测试轮次中进行逻辑交叉检测,这增强了其逻辑漏洞检测能力和效率。我们的评估结果表明,CLFuzz超越了其他先进的工具,并在广泛使用的流行加密算法实现中发现了许多安全关键的漏洞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值