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
处只有0x80
(128
)可用字节能够保存索引名指针。每个指针都会在索引编号位置保存,比如对于索引编号为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系统处于最新状态,定期安装补丁。