2023年2月,微软修复了Microsoft Word中的一个关键漏洞,编号CVE-2023-21716,CVSS评分9.8,攻击者可在未经身份验证的情况下远程执行代码。该漏洞同样影响了Outlook预览窗格,这意味着仅预览文件即可触发漏洞。尽管微软发布了补丁和临时解决方案,但并未披露相关细节。3月6日,发现该漏洞的研究员Joshua J. Drake(@jduck)在Twitter上分享了概念验证(PoC)。
CVE-2023-21716源于Microsoft Word处理富文本格式(RTF) 文件的方式,特别是**\fonttbl控制字,该控制字使用\f** 格式定义文档中的字体。wwlib.dll中的**\fonttbl**会在堆中分配一定空间(这是因为堆分配通常用于动态数据,例如解析可能因输入而大小变化的较大结构体,如字体表)。当字体数量超过限制(PoC验证为32760)时,会发生缓冲区溢出。超出分配的堆空间将导致覆盖返回指令指针(RIP),从而引发应用程序崩溃。
强烈建议用户安装微软的最新安全补丁,已解决此问题。对于无法升级的用户,微软也提供了若干临时解决方案,详见:https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-21716。
使用WinDbg,我们可以更详细地了解崩溃原因。过程如下:
当崩溃发生时,WinDbg会自动中断,以便我们进一步分析。首先,输入'k'查看调用堆栈:
0:000> k
# ChildEBP RetAddr
00 012f5134 77a5feb5 ntdll!RtlReportCriticalFailure+0x4b
01 012f5170 77a5dda9 ntdll!RtlpReportHeapFailure+0x2f
02 012f5170 77a66220 ntdll!RtlpHpHeapHandleError+0x89
03 012f5188 77a5dab7 ntdll!RtlpLogHeapFailure+0x43
04 012f51ec 779b400d ntdll!RtlpAnalyzeHeapFailure+0x281
05 012f5348 779f806d ntdll!RtlpFreeHeap+0x24d
06 012f53a4 779b3d66 ntdll!RtlpFreeHeapInternal+0x783
07 012f53c4 6ea4aa24 ntdll!RtlFreeHeap+0x46 //ntdll下的这些函数与堆损坏的错误处理相关
08 012f53d8 6ea4a9cb mso20win32client!Ordinal1068+0xab
09 012f53e8 6ea4a995 mso20win32client!Ordinal1068+0x52
0a 012f53f4 6cccda52 mso20win32client!Ordinal1068+0x1c
0b 012f5410 03657fea mso!Ordinal1387+0x24
0c 012f5424 03990115 wwlib!PTLS7::FsUpdateFinitePage+0x7e26f //wwlib负责处理字体表
0d 012f5670 034ea593 wwlib!PTLS7::LsDestroyContext+0x245f90
0e 012f6f5c 033a68ef wwlib!PTLS7::FsUpdateBottomlessPage+0x17494
0f 012f7484 035054ed wwlib!PTLS7::LsAssert+0x2bd1c
10 012f888c 03503d3b wwlib!PTLS7::FsUpdateBottomlessPage+0x323ee
11 012f8910 0400be52 wwlib!PTLS7::FsUpdateBottomlessPage+0x30c3c
12 012f9e9c 0390013a wwlib!wdGetApplicationObject+0xdf8a0
13 012faf48 03ebf20e wwlib!PTLS7::LsDestroyContext+0x1b5fb5
14 012faf90 0410ab2a wwlib!DllCanUnloadNow+0xcc314
15 012fcfe0 03752fb4 wwlib!wdGetApplicationObject+0x1de578
16 012ff538 03385b93 wwlib!PTLS7::LsDestroyContext+0x8e2f
17 012ff578 75e6173b wwlib!PTLS7::LsAssert+0xafc0
18 012ff5a4 75e57eaa USER32!_InternalCallWinProc+0x2b
19 012ff68c 75e57666 USER32!UserCallWinProcCheckWow+0x33a
1a 012ff6c4 75e55e8b USER32!CallWindowProcAorW+0x7f
1b 012ff6dc 7500ae9d USER32!CallWindowProcW+0x1b
.....
.....
两个关键库是wwlib.dll(核心Word库,管理字体、文本、布局等文档内容)和ntdll.dll(处理内存管理、错误报告等底层系统任务)。崩溃发生在Word处理RTF文件中大量字体时。当ntdll.dll检测到Word试图访问或释放无效内存(很可能因为字体溢出导致损坏)时,会引发堆损坏错误,导致崩溃。
要深入了解,我们可以在wwlib库的最后一部分设置断点,观察字体如何加载到内存中,并理解触发问题的原因:
bc * //删除所有其他断点
bp wwlib!PTLS7::FsUpdateFinitePage+0x7e26f //创建断点
现在得到一个访问冲突:
(ba4.1a14): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=004f1ce4 ebx=00000001 ecx=000004e4 edx=ffff7ffc esi=13474fe8 edi=00008002
eip=02b300d5 esp=004f1c3c ebp=004f1c48 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210206
wwlib!PTLS7::FsUpdateFinitePage+0x7635a:
02b300d5 66894c5604 mov word ptr [esi+edx*2+4],cx ds:002b:13464fe4=???? //中断发生在此处
由于我们提前停止了程序,在wwlib.dll首次尝试访问或修改无效内存(在FsUpdateFinitePage中)时,可以看到最初的访问冲突(导致内存损坏),这让我们有机会在ntdll.dll检测到堆损坏错误之前检查问题所在。
更准确地说,程序因访问冲突(代码c0000005)在指令mov word ptr [esi+edx*2+4],cx(位于wwlib!PTLS7::FsUpdateFinitePage+0x7635a函数内)处停止。该错误表明Word试图访问无效内存位置,具体是在尝试将值(cx)存储到由esi+edx*2+4计算的内存位置时。
进一步查看ESI寄存器,可以看到它保存了字体数据:
每个16位值(两个字节)以小端格式表示一个字体条目,格式为**\fA;**
ESI中表示的最后值是f8 7f,对应{\f32760A;}(在到达由29-00表示的结束标志之前)。

现在字体表被加载到ESI寄存器指向的内存位置,由于处理大量字体,计算出的内存地址(esi + edx*2 + 4)很可能超出了为字体表分配的内存范围。这导致越界写入,引发访问冲突错误。 ntdll.dll检测到该访问冲突,负责内存管理和错误报告。因此,ntdll.dll调用了检测堆损坏的函数,最终导致应用程序崩溃。