开源库中的漏洞发现:分析 CVE-2020-11863
开源项目是任何软件开发过程的基础。随着越来越多的产品使用开放源代码,整体攻击面的增长是不可避免的,尤其是当开放源代码在使用前未经审核时。因此,建议彻底测试它的潜在漏洞,并与开发人员合作修复它们,最终缓解攻击。研究人员还指出,正在研究Windows和Linux中的图形库,并报告Windows GDI和Linux矢量图形库libEMF中的多个漏洞。审核许多其他Linux图形库,因为它们是旧代码,以前没有经过严格的测试。
在第1部分中,通过概述libEMF库中报告的漏洞,详细描述了开源研究的重要性。我们还强调了使用内存清理器编译代码的重要性以及它如何帮助检测各种内存损坏漏洞。总而言之,地址清理器(ASAN)截取了诸如malloc()/ free()之类的内存分配/释放函数,并使用各自的填充字节(malloc_fill_byte / free_fill_byte)填充了内存。它还监视对这些内存位置的读取和写入,从而帮助检测运行时的错误访问。
研究人员对报告的漏洞之一CVE-2020-11863进行了更详细的分析,该漏洞是由于使用了未初始化的内存而引起的。此漏洞与CVE-2020-11865有关,CVE-2020-11865是libEMF中GlobalObject :: Find()函数中的全局对象向量,超出了内存访问范围。但是,崩溃调用堆栈原来是不同的,这就是为什么我们决定进一步研究这一问题并制作此深入探讨博客的原因。
ASAN提供的信息足以在模糊器外部重现漏洞崩溃。从ASAN信息来看,该漏洞似乎是空指针取消引用,但这不是实际的根本原因,正如研究人员将在下面讨论的那样。
查看调用堆栈,看来应用程序在动态转换对象时崩溃了,这可能有多种原因。在似乎可能的那些可能原因中,应用程序试图访问不存在的虚拟表指针,或者从函数返回的对象地址是应用程序崩溃时访问的通配地址。获取有关此崩溃的更多上下文,我们在调试时遇到了一个有趣的寄存器值。下面显示了反汇编中的崩溃点,指示不存在内存访问。
如果我们查看崩溃点处的寄存器状态,则特别有趣的是,注意到寄存器rdi的异常值是0xbebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebe 我们想更深入地研究一下该值如何进入寄存器,从而实现对内存的访问。由于有了库的源代码,我们可以立即检查该寄存器在访问内存中对象方面的含义。
参考Address Sanitizer文档,事实证明ASAN 默认情况下会将0xbe写入新分配的内存,这实际上意味着已写入此64位值,但未初始化内存。ASAN将此称为malloc_fill_byte。它也通过在释放内存时用free_fill_byte填充内存来实现相同的目的。这最终有助于识别内存访问错误。
ASAN的这一特性也可以在这里的libsaniizer源代码中得到验证。下面是源文件的摘录。
查看崩溃点处的堆栈跟踪,如下所示,崩溃发生在SelectObject()函数中。该部分代码负责处理增强型元文件(EMF)文件的EMR_SELECTOBJECT记录结构,传递给该函数的图形对象句柄为0x80000018。我们想研究代码流,以检查这是否直接来自输入EMF文件,并且可以由攻击者控制。
在SelectObject()函数中,在处理EMR_SELECTOBJECT记录结构时,将GDI对象的句柄传递到GlobalObjects.find(),如上面的代码片段所示,该代码片段又通过掩盖较高的顺序来访问全局库存对象向量GDI对象句柄中的第1位并将其转换为索引,最终使用转换后的索引号从对象向量返回库存对象参考。库存对象枚举指定可以在MS文档中记录的图形操作中使用的预定义逻辑图形对象的索引。例如,如果对象句柄为0x8000018,则将其与0x7FFFFFFF进行“与”运算,得出0x18,它将用作全局库存对象向量的索引。然后,将这个库存对象引用动态地转换为图形对象,然后调用EMF :: GRAPHICSOBJECT成员函数getType()确定图形对象的类型,然后,在此函数的后面,将其再次转换为合适的图形对象(画笔,笔,字体,调色板,扩展名),如下面的代码片段所示。
EMF :: GRAPHICSOBJECT是从EMF :: OBJECT派生的类,并且EMF :: OBJECT类的继承图如下所示。
但是,如前所述,我们很想知道作为参数传递给SelectObject函数的对象句柄是否可以由攻击者控制。为了获得相关信息,让我们看一下EMR_SELECTOBJECT记录的格式,如下所示。
正如我们在这里注意到的,ihObject是4字节无符号整数,用于指定库存对象枚举的索引。在这种情况下,库存对象参考保存在全局对象向量中。在这里,对象句柄0x80000018表示索引0x18将用于访问全局库存对象向量。如果在此期间对象向量的长度小于0x18,并且在访问对象向量之前未进行长度检查,则将导致超出范围的内存访问。
下面是处理EMR_SELECTOBJECT图元文件记录的直观表示。
在调试此问题时,我们在GlobalObjects.find()处启用了一个断点,并继续操作,直到获得对象句柄0x80000018; 本质上,我们到达了上面突出显示的EMR_SELECTOBJECT记录的处理点。如下所示,对象句柄被转换为索引(0x18 = 24)以访问大小为(0x16 = 22)的对象向量,从而导致了超出范围的访问,我们将其报告为CVE-2020-11865。
进一步进入代码,它进入STL矢量库stl_vector.h,该库实现std :: vectors的动态扩展。由于此时的对象向量只有22个元素,因此STL向量会将向量扩展为突出显示的参数所指示的大小,并通过传递的索引访问向量,并返回该对象引用处的值,如图所示。下面的代码段,被ASAN填充为0xbebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebe
该代码使用std:allocator来管理主要用于内存分配和释放的向量内存。通过进一步分析,可以发现在这种情况下返回的值0xbebebebebebebebebebe不存在是库存对象的虚拟指针,该对象在动态转换期间被取消引用,从而导致崩溃。
结论
虽然在产品中使用第三方代码肯定可以节省时间并提高开发速度,但潜在地,漏洞数量也会随之增加,尤其是当代码未经审核并未经任何测试集成到产品中时。对使用的开放源代码库进行模糊测试非常关键,这可以帮助发现开发周期中的漏洞,并提供在产品交付之前对其进行修复的机会,从而减轻了攻击。必须加强漏洞研究人员与开放源代码社区之间的协作,以继续负责任的披露,使代码的维护者能够及时解决这些问题。
