检测不良OpenSSL使用模式
OpenSSL是最流行的加密库之一;即使您不使用C/C++,您编程语言的主要库很可能也使用OpenSSL绑定。由于其低级API的设计,OpenSSL也非常容易出错。然而,许多这些错误属于易于识别的模式,这提高了自动化检测的可能性。
作为过去冬季和春季实习的一部分,我一直在开发一个名为Anselm的工具原型,该工具允许开发人员描述和搜索不良行为模式。Anselm是一个LLVM pass,意味着它在源代码和编译之间的中间表示上操作。Anselm相对于静态分析的主要优势是,它可以操作任何编译为LLVM位码的编程语言,或任何可以反向转换的闭源机器代码。Anselm可以针对任意函数调用序列,但其最初目的是检查OpenSSL使用,因此让我们从这里开始。
OpenSSL
OpenSSL的设计使得初学者难以理解和使用。其库中有各种不一致的命名约定,并为每个原语提供了几个( arguably too many)选项和模式。例如,由于库的演变,存在高级(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支持这些类型的实习——非常有趣!