CVE-2018-8423:Jet Database Engine漏洞分析

 

0x00 前言

2018年9月,ZDI公布了微软Jet Database Engine中某个漏洞的PoC,微软也在2018年10月份发布了修复补丁。为了全面保护客户安全,我们深入研究了漏洞细节,我们发现官方补丁中存在一些问题,及时向微软反馈,最终拿到了另一个漏洞编号(CVE-2019-0576),微软在2018年1月8日修复了该漏洞。

这是微软Jet Database Engine中的一个漏洞,微软在许多产品中(比如Access)都会用到Jet Database Engine。攻击者可以利用该漏洞执行代码,提升权限或者下载恶意软件。我们不清楚是否有攻击者实际利用了这个漏洞,但公众可以从网上自由下载相关PoC。

 

0x01 漏洞概要

为了利用该漏洞,攻击者需要使用社会工程学技术,诱骗受害者打开某个JavaScript文件,该恶意脚本会使用ADODB连接对象访问某个恶意的Jet Database文件。一旦成功访问恶意的Jet数据库文件,攻击脚本就会调用msrd3x40.dll中存在漏洞的某个函数,成功利用该漏洞。

虽然已公开的PoC只会导致wscript.exe崩溃,但实际上使用该DLL的所有应用都存在风险。

当成功触发漏洞后,可以看到如下错误信息:

该消息表明存在漏洞的DLL中出现了访问冲突错误。这是一个“越界写入”漏洞,可以通过OLE DB触发(微软许多应用会使用该API用来访问底层数据)。这类漏洞表明写入数据位于目标缓冲区之外,因此会出现崩溃现象。崩溃的罪魁祸首是恶意构造的Jet数据库文件,该文件利用了Jet数据库文件格式中的某个索引字段,赋予该字段超出预期的索引值,导致出现越界写入,最终导致程序崩溃。

漏洞利用的整体过程如下图所示:

 

0x02 漏洞利用

PoC中包含一个JavaScript文件(poc.js),该文件会调用第二个文件(group1,Jet数据库文件)。我们可以通过wscript.exe运行poc.js来触发崩溃:

在上图中,我们可以查看调试信息,发现崩溃的函数为msrd3x40!TblPage::CreateIndexes。此外,我们还可以看到程序会在尝试写入数据时失败,程序会使用esi寄存器向[edx+ecx*4+574h]处写入数据,但该处地址实际上无法访问。

我们需要理解该位置的构造方式,才能挖掘漏洞的根本成因。根据调试信息,寄存器ecx中包含的值为0x00002300edx为指向内存的某个指针,后面我们会涉及到。最终这两个寄存器会与十六进制值574相加,用来引用目标内存地址。根据这个信息,我们可以猜测存储在目标位置的数据类型。该数据似乎是一个数组,每个变量大小为4字节,数组起始位置为edx+574h。跟踪程序运行,我们发现0x00002300这个值来自于group1这个PoC文件。

现在我们知道程序会尝试执行越界写入操作,也知道写入的目的地址,我们需要澄清为什么程序会尝试往该地址写入数据。这里我们可以研究一下用户提供的0x00002300值。为了完成该任务,我们首先需要理解Jet数据库文件。

 

0x03 分析Jet数据库文件

之前已经有许多研究人员详细分析过Jet数据库文件结构,大家可以参考这两处资料了解详细信息:

这里简单总结一下,Jet数据库文件采用页面(page)这种组织结构,如下图所示:

头部(header)页中包含与文件有关的各种信息,如下图所示:

在头部页之后为126字节大小的数据,经过RC4加密,密钥为0x6b39dac7(每个JetDB文件都采用这个密钥)。对比PoC文件的键值,我们可知group1采用的是Jet Version 3版文件结构。

接下来是“Table Definition Pages”(表定义页面)部分,其中描述了与表有关的各种数据结构(参考此处详细信息)。

表定义数据中包含各种字段,其中我们需要注意两个字段:Index Count(索引数)以及Real Index Count(实际索引数):

我们可以在PoC文件中确定这些字段值。检查group1文件,我们可以看到如下信息:

在Index Count中总共有2个索引,当我们解析这两个索引时,可以看到非常熟悉的0x00002300值:

这里0x00230000为表中index2的索引号,而这个索引似乎非常巨大,最终导致崩溃。那么为什么这个索引会导致程序崩溃?进一步解析文件,我们可以看到两个索引的名称:

 

0x04 调试

附加调试器后,我们可以看到程序首先会调用msrd3x40!operator new函数,该操作会分配内存空间,用来存储eax中的内存指针地址:

分配完内存后,程序会创建新的索引:

程序会在后续执行过程中用到这个索引号。msrd3x40!Index::Restore函数会将索引号拷贝至索引地址 + 24h处。程序会对所有索引循环执行该操作。首先程序会调用new运算符,分配内存空间,然后在该地址处创建一个索引,将索引号移动到索引基址 + 24h处。我们可以在如下代码片段中看到这个移动操作,其中恶意索引值会被拷贝到新创建的索引中:

成功移动后,msrd3x40!NamedObject::Rename函数就会被调用,将索引名拷贝到索引地址 + 40h处:

如果查看esi寄存器,我们可以看到其指向的是索引的地址。ecx寄存器的值为[esi+24h],这就是索引号:

后面经过一些指令后,我们可以观察到最开始的崩溃指令。edx指向的是内存位置,ecx中包含来自于group1文件中非常大的一个值。程序会尝试访问[edx+ecx*4+574h]处的内存,而这会触发越界写操作,导致程序崩溃:

至于程序尝试写入的数据,如果我们观察指令,可以看到程序会尝试将esi的值写入[edx+ecx*4+574]。如果打印esi或者之前的值,可以看到其中包含索引名ParentIdName,我们也可以在group1中看到这个值:

最终,程序在尝试使用非常大的索引号来处理ParentIDName时就会崩溃,整个漏洞触发逻辑如下:

  • 分配内存空间,指针指向内存起始位置
  • 内存位置 + 574h处开始,程序将指针依次保存到索引名中,每个指针位置为4字节乘以文件中指定的索引号

如果索引号非常巨大(如PoC文件中构造的索引号),并且没有进行验证,那么程序就会尝试执行越界写入操作,最终崩溃。

 

0x05 总结

这个漏洞属于逻辑类错误,有时候我们很难捕捉到这类错误,许多开发者会采取额外的预防措施来避免代码中出现这类错误。如果这些错误会导致严重的安全问题(如CVE-2018-8423),那么显然是非常不幸的事情。当这些问题被发现并修复后,我们建议大家尽快部署厂商提供的补丁,以降低自身面临的安全风险。

大家可以从如下位置下载并安装微软提供的补丁:

CVE-2018-8423

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8423

CVE-2019-0576

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0576

 

0x06 参考资料

(完)