概述
CVE-2023-21716是Microsoft Word 的 RTF 解析器在处理 rtf 文件字体表(\fonttbl)中包含的过多字体(\fN)时产生的堆损坏漏洞。
为什么要开启页堆?
开启页堆是为了方便进行堆相关漏洞的分析和调试。在开启页堆的情况下,当程序在进行堆分配时,操作系统会使用单独的物理内存页来存储分配的堆。这些页是以4KB的大小进行分配的,这使得堆内存的分配和释放操作更加精确。
开启页堆后,程序会在执行堆分配操作时向操作系统请求更多的物理内存页,这些页仅用于存储分配的堆。同时,操作系统还会记录每个分配的页的相关信息,如分配时间、分配的大小、分配的堆地址等等,这些信息可以用于堆分析和调试。
在进行堆漏洞分析时,开启页堆可以让分析人员更加容易地追踪堆分配和释放操作,同时还可以提供更详细的堆栈回溯信息,从而更容易地定位和修复漏洞。
开启页堆
要对winword.exe开启页堆进行栈回溯,可以按照以下步骤操作:
1. 确认你的系统已经开启了页堆调试选项(heap debugging option)。可以在命令行中输入以下命令,来启用页堆调试选项:
gflags.exe /i winword.exe +hpa
2. 打开WinDbg(Windows调试器)或者IDA Pro等反汇编工具。
3. 附加到winword.exe进程,可以使用以下命令:
windbg –p <pid_of_winword>
4. 设置符号路径(symbol path),以便调试器可以正确解析符号信息。可以使用以下命令:
.symfix
.reload
5. 开始记录栈回溯信息,可以使用以下命令:
!heap -p -a <heap_address>
其中,<heap_address>为winword.exe开启的页堆地址。该命令可以显示与该页堆相关的分配和释放操作,并且会在输出中包含每个分配的堆栈回溯信息。
你可以使用以下命令过滤输出,只显示与winword.exe相关的栈回溯信息:
!heap -p -a <heap_address> -f winword.exe
或者只显示当前线程的栈回溯信息:k
该命令将显示当前线程的栈回溯信息,其中包含了调用栈中的每个函数和它们的参数。
6. 根据栈回溯信息分析漏洞原因,并进行修复。
分析
用windbg附加winword,然后打开rtf漏洞文档,触发崩溃,得到栈回溯如下:
[0x0] ntdll!NtTerminateProcess + 0x14 [0x1] KERNELBASE!TerminateProcess + 0x30 [0x2] ucrtbase!_invalid_parameter + 0x12d [0x3] ucrtbase!invalid_parameter_noinfo_noreturn + 0x9 [0x4] wwlib!PTLS7::LsDestroyContext + 0x105177 [0x5] wwlib!PTLS7::FsUpdateFinitePage + 0x72358 [0x6] wwlib!PTLS7::FsUpdateFinitePage + 0x817cb [0x7] wwlib!PTLS7::LsDestroyContext + 0x305ff0 [0x8] wwlib!PTLS7::LsDestroyContext + 0x305972 [0x9] wwlib!PTLS7::LsDestroyContext + 0x8acda [0xa] wwlib!PTLS7::FsValidateReuse + 0x53bb4 [0xb] wwlib!PTLS7::LsdnFinishSimple + 0x1cb5b [0xc] wwlib!PTLS7::LsdnFinishSimple + 0x1e2ac [0xd] wwlib!PTLS7::LsdnFinishSimple + 0x1df6b [0xe] wwlib!PTLS7::LsdnFinishSimple + 0x1be98 [0xf] wwlib!PTLS7::LsdnFinishSimple + 0x1c13d [0x10] wwlib!PTLS7::LsdnFinishSimple + 0x1db39 [0x11] wwlib!PTLS7::LsdnFinishSimple + 0x7245 [0x12] wwlib!DllCanUnloadNow + 0x2cfb48 [0x13] mso + 0xef361 [0x14] mso + 0xef27a [0x15] mso + 0x24807 [0x16] mso + 0xe9a69 [0x17] mso40uiwin32client + 0x3cbd0 [0x18] mso40uiwin32client + 0x3b0cc [0x19] mso40uiwin32client + 0x42b0e [0x1a] mso40uiwin32client + 0x41b9f [0x1b] mso40uiwin32client + 0x373c1 [0x1c] mso40uiwin32client + 0x35ddb [0x1d] mso40uiwin32client + 0x1b08b1 [0x1e] mso40uiwin32client + 0x35e56 [0x1f] mso40uiwin32client + 0x19e124 [0x20] mso40uiwin32client + 0x2f076 [0x21] mso40uiwin32client + 0x8e538 [0x22] mso40uiwin32client + 0x8e761 [0x23] mso40uiwin32client + 0x96d59 [0x24] mso40uiwin32client + 0x2a61c [0x25] mso40uiwin32client + 0x1348d2 [0x26] mso40uiwin32client + 0x134fd7 [0x27] mso40uiwin32client + 0x2ef33 [0x28] mso40uiwin32client + 0x8e538 [0x29] mso40uiwin32client + 0x8e761 [0x2a] mso40uiwin32client + 0x90d7f [0x2b] mso40uiwin32client + 0x11e92a [0x2c] mso40uiwin32client + 0x1104d8 [0x2d] mso40uiwin32client + 0x947eb [0x2e] mso40uiwin32client + 0x98603 [0x2f] USER32!UserCallWinProcCheckWow + 0x2f8 [0x30] USER32!DispatchMessageWorker + 0x249 [0x31] wwlib!PTLS7::LsAssert + 0x7c5cf [0x32] wwlib!PTLS7::LsAssert + 0x7c03c [0x33] wwlib!FMain + 0x61 [0x34] WINWORD + 0x1230 [0x35] WINWORD + 0x1519 [0x36] KERNEL32!BaseThreadInitThunk + 0x14 [0x37] ntdll!RtlUserThreadStart + 0x21
问题
1. 在WinDbg中输入!gflag命令后,输出内容为Current NtGlobalFlag contents: 0x00000000
如果在WinDbg中输入!gflag命令后,输出内容为Current NtGlobalFlag contents: 0x00000000,这表示当前系统的NtGlobalFlag标志位设置为0,即没有启用任何调试标志。
NtGlobalFlag是一个系统级别的调试标志位,可以通过设置不同的标志位来启用或禁用系统的调试功能。这些标志位的具体含义和作用不同,可以用于调试不同类型的问题,如堆栈溢出、内存泄漏、死锁等。
如果要使用调试标志位来分析程序的问题,可以通过以下步骤启用NtGlobalFlag:
- 在WinDbg中输入.reload /f命令,强制重新加载符号文件。
- 输入.restart命令,重启程序。
- 在程序崩溃或出现其他问题时,使用!gflag命令查看当前NtGlobalFlag的设置。
- 根据需要设置或更改标志位,例如,可以使用以下命令启用gflag中的堆栈跟踪标志:!gflag +ust
- 重启程序并等待出现问题。此时,WinDbg应该会输出更多的调试信息,帮助你分析问题。
请注意,在使用调试标志位时,需要谨慎地设置和使用不同的标志位,并且应该遵循一定的调试流程和规范,以避免干扰程序的正常执行和产生其他问题。