检测不良OpenSSL使用模式 - Trail of Bits博客
William Wang, UCLA
2020年5月29日
密码学, 实习项目, 程序分析
OpenSSL是最流行的密码学库之一;即使您不使用C/C++,您所用编程语言的主要库很可能也使用了OpenSSL绑定。由于其底层API的设计,它也非常容易出错。然而,许多这些错误都属于容易识别的模式,这为自动化检测提供了可能。
作为过去冬季和春季实习的一部分,我一直在开发一个名为Anselm的工具原型,它允许开发者描述和搜索不良行为模式。Anselm是一个LLVM pass,意味着它在源代码和编译之间的中间表示上操作。Anselm相对于静态分析的主要优势是它可以操作任何编译为LLVM位码的编程语言,或任何可以反向转换的闭源机器代码。Anselm可以针对任意函数调用序列,但其最初目的是检查OpenSSL使用,因此让我们从这里开始。
OpenSSL
OpenSSL的设计使得初学者难以理解和使用。其库中有各种不一致的命名约定,并为每个原语提供了多个(可以说太多)选项和模式。例如,由于库的演变,存在高级(EVP)和低级方法可用于完成相同的任务(例如DSA签名或EC签名操作)。更糟糕的是,它们的文档可能不一致且难以阅读。
除了难以使用之外,其他设计选择使得该库使用危险。API不一致地返回错误代码、指针(有和没有所有权),并表现出其他令人惊讶的行为。如果不严格检查错误代码或防御空指针,可能会出现意外的程序行为和进程终止。
那么Anselm可以检测哪些类型的错误?这取决于开发者指定的内容,但可能包括从错误管理OpenSSL错误队列到重复使用初始化向量的任何内容。同样重要的是要记住这些是启发式方法,误识别良好和不良行为总是可能的。现在,让我们深入了解该工具的工作原理。
函数调用
虽然该项目的主要动机是针对OpenSSL,但库本身实际上并不重要。可以将OpenSSL使用视为一系列API调用,例如EVP_EncryptUpdate和EVP_EncryptFinal_ex。但我们可以轻松地用任何其他名称替换这些名称,想法保持不变。因此,不良行为是任何函数调用(不仅仅是OpenSSL的)的模式,我们希望检测到。
我的主要方法是通过函数中的所有可能执行路径进行搜索,寻找不良的API调用序列。在本文中,我将在示例中使用OpenSSL的对称加密函数。让我们考虑EVP_EncryptUpdate(加密数据块)和EVP_EncryptFinal_ex(在最终加密前填充明文)。自然,它们不应乱序调用:
|
|
这也应该被标记,因为不良序列仍然可能:
|
|
我使用LLVM BasicBlocks工作,它表示始终一起执行的指令列表。BasicBlocks可以有多个后继,每个反映不同的执行路径。因此,函数是许多BasicBlocks的有向图。有一个根节点,任何叶节点代表执行结束。
找到所有可能的执行相当于从根节点开始执行深度优先搜索(DFS)。但是,请注意图可以包含循环;这类似于代码中的循环。如果我们执行盲DFS,可能会陷入无限循环。另一方面,忽略先前访问的节点可能导致遗漏行为。我通过限制任何路径的长度来解决这个问题,之后Anselm停止进一步探索(这可以自定义)。
还有一个问题,即在整个代码库上执行DFS可能非常耗时。即使我们的确切模式很简单,它仍然需要与搜索生成的所有可能路径匹配。为了解决这个问题,我首先修剪任何不包含相关API调用的BasicBlock图。这是通过将任何不相关节点的前驱指向其每个后继来实现的,移除中间人。
在实践中,这显著降低了图的复杂性,以便更快地进行路径查找:整个if语句和while循环可以在没有任何后果的情况下被消除!这也使任何路径限制更加合理。
匹配值
虽然仅检查函数调用是一个好的开始,但我们可以做得更好。考虑OpenSSL上下文,它们由EVP_CIPHER_CTX_new创建,并在实际使用前必须用算法、密钥等初始化。在以下情况下,我们希望每个上下文都由EVP_EncryptInit_ex初始化:
|
|
EVP_EncryptInit_ex始终跟随EVP_CIPHER_CTX_new,但ctx2显然未正确初始化。更精确的模式是:“从EVP_CIPHER_CTX_new返回的每个上下文应稍后在EVP_CIPHER_CTX_new中初始化。”
我通过匹配参数和返回值来解决这个问题——检查它们是否指向内存中相同的LLVM Value对象。上下文是匹配值的主要情况,但我们可以使用相同的技术来检测重复的IV:
|
|
在内部,Anselm使用正则表达式捕获组执行此分析;每个执行路径是函数调用和Value指针的字符串,而不良行为由某个正则表达式模式定义。
模式格式
在实习结束时,我还定义了一种格式,供开发者指定不良行为,Anselm将其转换为(有些混乱的)正则表达式模式。每行以函数调用开始,后跟其返回值和参数。如果您不关心值,请使用下划线。否则,定义一个可以在其他地方使用的标记。因此,禁止重复IV的规则如下所示:
|
|
由于iv标记被重用,Anselm将其搜索限制为仅匹配在该参数位置包含相同Value指针的函数。
我还定义了一种语法来执行负向先行断言,这告诉Anselm寻找特定函数调用的缺失。例如,如果我想防止任何上下文在初始化前被使用,我会像这样添加感叹号:
|
|
用英语来说,此模式识别任何对EVP_CIPHER_CTX_new和EVP_EncryptUpdate的调用,这些调用之间没有EVP_EncryptInit_ex夹在中间。
最后说明
凭借其当前的工具集,Anselm能够解释各种函数调用模式并在LLVM位码中搜索它们。当然,它仍然是一个原型,还有改进的空间,但主要思想已经存在,我为项目的成果感到自豪。感谢Trail of Bits支持这些类型的实习——非常有趣!
如果您喜欢这篇文章,请分享:
Twitter LinkedIn GitHub Mastodon Hacker News