CVE-2019-0576:分析Jet Database Engine补丁问题

 

0x00 前言

截至2019年7月,微软已经在Jet Database Engine修复了大约43个bug。McAfee向官方报告了一些bug,到目前为止,我们获得了微软官方收录的10个CVE。在之前的一篇文章中,我们与大家讨论了CVE-2018-8423的漏洞根源。在分析这个CVE的原理以及官方提供的补丁时,我们发现这个补丁可以被绕过,再次造成系统crash。我们向微软反馈了这个情况,官方在1月19日的周二补丁日中修复了这个漏洞,新的漏洞编号为CVE-2019-0576。我们建议用户及时安装补丁,遵循适当的补丁管理策略,保证Windows系统处于最新状态。

在本文中我们将分析CVE-2019-0576的根源。为了利用该漏洞,攻击者需要使用社会工程学技巧,诱导受害者打开一个JavaScript文件,该脚本使用ADODB连接对象访问恶意的Jet Database文件。一旦访问恶意的Jet Database文件,脚本就会调用msrd3x40.dll中存在漏洞的函数,触发漏洞。

 

0x01 相关背景

在上一篇文章中我们提到,攻击者可以使用恶意的Jet Database文件触发CVE-2018-8423,根据漏洞分析结果,该问题位于索引编号字段中。如果索引编号过大,程序就会在如下位置crash:

这里ecx中包含恶意的索引编号。打上微软提供的CVE-2018-8423补丁后,当打开这个恶意文件时,我们可以看到如下错误信息,表明这个问题已被修复,不会再出现crash。

 

0x02 分析补丁

我们决定深入分析,澄清官方如何解决这个问题。在分析msrd3x40!TblPage::CreateIndexes函数时,可以看到代码会判断IndexNumber是否大于0xFF(即256),如下图所示:

这里ecx包含索引编号值,为00002300,并且这个值远大于0xFF。如果观察代码逻辑,可以看到一个跳转指令。跟随这个跳转指令,我们可以到达如下位置:

可以看到,代码会调用msrd3x40!Err::SetError函数,这意味着如果索引值大于0xFF,那么程序就不会解析恶意文件,弹出“Unrecognized database format”错误信息,结束运行。

 

0x03 补丁问题

分析补丁后,我们发现如果索引值大于0xFF,程序就会结束运行。然而我们决定使用00 00 00 20这个索引值来试一下,这个值小于0xFF。此时我们在msrd3x40!Table::FindIndexFromName中触发了另一个crash,如下图所示:

 

0x04 新问题根源

前面提到过,如果任意索引值小于0xFF,那么就会在msrd3x40!Table::FindIndexFromName函数中触发crash,因此我们决定深入分析,澄清问题原因。

崩溃点位于如下位置:

似乎程序会尝试访问[ebx+eax*4+574h]这个地址,但该地址不可访问,这意味着这是一个越界读取(Out of Bound Read)问题。

这个crash情况与我们在CVE-2018-8423中看到的非常类似,不同点在于当时是一个越界写入(Out of Bound Write)问题。如果我们观察eax,可以看到其中的值为0055b7a8,这个值乘以4后会变成一个非常巨大的值。

我们提供的文件如下所示:

如下图所示,如果我们解析该文件,00 00 00 20(上图中的低字节序)这个值表示是ParentIDName的索引编号:

在调试器中观察crash时的状态,可以看到ebx+574h指向的是某个内存位置,并且eax中包含一个索引值,这个值会被乘以4。现在我们需要澄清如下两个问题:

1、哪个eax值会导致crash?我们知道这个值应该小于0xFF,但需要找到最小的值;

2、这个问题的根本原因?

msrd3x40!Table::FindIndexFromName上设置断点,将索引编号修改为0000001f(虽然这个值不会造成crash,但可以帮助我们调试,理解程序执行流),我们可以看到edx中包含指向某个索引名(ParentIdName)的指针:

继续调试,可以看到eax值来自于[ebp],而ebp值来自于[ebx+5F4h],如下图所示:

查看ebx+5F4地址时,我们可以看到如下信息:

可以看到ebx+5F4包含文件中所有索引的索引编号。在我们的测试案例中,这个文件包含2个索引,对应的编号为00 00 00 01以及00 00 00 1f。如果仔细观察这片内存,可以发现这里最多能存储的索引数为0x20(即32):

起始位置:00718d54
每个索引编号为4字节长,因此0x20*4 + 00718d54 = 00718DD4

之后,如果我们观察ebx+574+4,可以看到该位置包含指向索引名的一个指针:

因此,整个内存结构如下所示:

EBX+574处只有0x80128)可用字节能够保存索引名指针。每个指针都会在索引编号位置保存,比如对于索引编号为1,则会保存到EBX+574+1*4,对于索引编号为2,则会保存到EBX+574+2*4,以此类推(索引编号从0开始)。

在这种情况下,如果我们给出的索引编号大于31,那么程序就会覆盖超过0x80字节大小的数据,也就是会从EBX+5F4位置开始覆盖数据,而这个值正是来自恶意文件的索引编号。因此,这种情况下如果我们不使用00 00 00 1f,换成00 00 00 20,那么程序就会覆盖EBX+5F4位置的索引编号,如下图所示:

现在程序会在msrd3x40!Table::FindIndexFromName中尝试执行这条指令:

Mov ecx, dword ptr [ebx+eax*4+574h]

这里eax中包含索引编号(应当为00 00 00 01),然而由于这个值已经被0055b7a8所覆盖,这是一个内存地址,当乘以4后会变成一个非常庞大的编号,然后还会再加上574h。因此如果这个内存区域并不存在,而程序尝试从该位置读取数据,那么我们就会得到一个访问冲突错误。

因此我们可以回答前面提出的两个问题:

1、如果给定的值小于0xFF并且大于0x31,并且通过[ebx+eax*4+574h]得到的内存位置不可访问,那么就会触发crash;

2、这个问题的根源在于索引编码会被内存位置所覆盖,导致出现无效内存访问操作。

 

0x05 官方解决方法

微软在1月19日补丁中修复了这个问题,我们决定再次分析补丁,看官方对这个问题的修复方法。前面提到过,只要给定值大于等于0x20(即30)就会触发crash,因此补丁应该检查这个值。微软在19日发布的补丁中新增了这个检查步骤,如下图所示:

在上图中,eax保存的是索引值,会与0x20进行比较。如果大于等于0x20,程序就会跳转至72fe1c00。如果跟踪该地址,我们可以看到如下信息:

如上图所示,程序会调用析构函数,然后调用msrd3x40!Err::SetError函数并返回。因此,程序会弹出一个消息,提示“Unrecognized database format”然后结束运行。

 

0x06 总结

我们在2018年10月份向微软报告了这个问题,官方在1月19日发布了相应补丁,该漏洞对应的编号为CVE-2019-0576。我们建议用户保持Windows系统处于最新状态,定期安装补丁。

 

0x07 参考资料

(完)