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
中包含的值为0x00002300
。edx
为指向内存的某个指针,后面我们会涉及到。最终这两个寄存器会与十六进制值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