0x01 漏洞描述
WPS Office是适用于Microsoft Windows,macOS,Linux,iOS和Android的办公软件,由总部位于珠海的中国软件开发商金山软件公司开发。WPS Office由三个主要组件组成:WPS Writer,WPS Presentation和WPS Spreadsheet。个人基本版本可以免费使用,WPS Office软件中存在一个远程执行代码漏洞,是当Office软件在分析特制Office文件时不正确地处理内存中的对象时引起的。成功利用此漏洞的攻击者可以在当前用户的上下文中运行任意代码。漏洞可能会导致拒绝服务,易受攻击的产品WPS Office,影响版本11.2.0.9453。
0x02 漏洞分析
在WPS Office中用于图像格式解析的Qt模块中发现存在堆溢出。嵌入WPS office的特制图像文件可能会触发此漏洞。打开特制的文档文件时,触发访问冲突。EDX指向数组的指针,而EAX是指向数组的索引。
0:000>g (c50.b4):Accessviolation-codec0000005(firstchance) Firstchanceexceptionsarereportedbeforeanyexceptionhandling. Thisexceptionmaybeexpectedandhandled. eax=000000c0ebx=006f1c48ecx=cd2aefbcedx=cd2c6f80esi=2ed7ae18edi=0000001c eip=6ba13321esp=006f1b44ebp=006f1b44iopl=0nvupeiplnznapo;nc; cs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00210202 QtCore4!QMatrix::dy+0x48a8: 6ba133218b448210moveax,dwordptr[edx+eax*4+10h]ds:002b:cd2c7290=????????
崩溃是如何触发的?让我们看一下PNG标头格式。
00029E30FF89504E470D0A1A0A0000000D494844ÿ‰PNG........IHD 00029E405200000280000001C60403000000160AR...€...Æ....... 00029E5027FC0000000467414D410000B1889598'ü....gAMA..±ˆ•˜ 00029E60F4A600000030504C5445000000800000ô¦...0PLTE...€.. 00029E7000800080800000008080008000808080.€.€€...€€.€.€€€ 00029E808080C0C0C0FF000000FF00FFFF000000€€ÀÀÀÿ...ÿ.ÿÿ... 00029E90FFFF00FF00FFFFFFFFFF7B1FB1C40000ÿÿ.ÿ.ÿÿÿÿÿ{.±Ä..
从偏移量0x29E31开始-0x29E34是PNG文件格式的签名标头。PNG头文件的结构:
PNGsignature-->IHDR-->gAMA-->PLTE-->pHYs-->IDAT-->IEND
在这种情况下,当WPS Office Suite中使用的QtCore库解析PLTE结构并触发堆溢出时,该漏洞位于Word文档中的嵌入式PNG文件中。在偏移量0x29E82到0x29E85处,调色板的解析失败,从而触发了堆中的内存损坏。崩溃触发之前的堆栈跟踪:
0000ee17906b8143efQtCore4!path_gradient_span_gen::path_gradient_span_gen+0x6a71 0100ee17f06b814259QtCore4!QBrush::setMatrix+0x234 0200ee58d46b8249a4QtCore4!QBrush::setMatrix+0x9e 0300ee58ec6b80cc84QtCore4!QImage::rect+0x22b 0400ee59086b857cccQtCore4!QTransform::inverted+0xec8 0500ee629c6b81c55bQtCore4!QSvgFillStyle::setFillOpacity+0x1b59 0600ee64806b896844QtCore4!QPainter::drawPixmap+0x1c98 0700ee65746d1e0fbdQtCore4!QPainter::drawImage+0x325 0800ee65946d0dd155kso!GdiDrawHoriLineIAlt+0x11a1a
在QtCore4解析嵌入式图像之前,我们可以看到来自KSO模块的最后一次调用,试图处理图像kso!GdiDrawHoriLineIAlt。使用IDA Pro分解应用程序来分析发生异常的函数。最后的崩溃路径如下(WinDBG结果):
QtCore4!QMatrix::dy+0x48a8: 6ba133218b448210moveax,dwordptr[edx+eax*4+10h]ds:002b:cd2c7290=????????
在IDA Pro中打开时,我们可以按以下方式反汇编该函数:
.text:67353315pushebp .text:67353316movebp,esp .text:67353318movzxeax,byteptr[ecx+edx];crashhere .text:6735331Cmovecx,[ebp+arg_0] .text:6735331Fmovedx,[ecx] .text:67353321moveax,[edx+eax*4+10h] .text:67353325movecx,eax
使用crashs转储中的信息,我们知道应用程序在0x67353321(mov eax,[edx + eax * 4 + 10h])处触发了访问冲突。我们可以看到EAX寄存器由0xc0值控制。因此,从这里我们可以根据导致异常的指令对寄存器的状态进行一些假设。需要注意的重要一点是,在发生异常之前,我们可以看到ECX(0xc0)中包含的值被写入到以下指令所定义的任意位置:
movecx,[ebp+arg_0]
此外,我们注意到,在我们的故障指令之外,EBP的偏移量存储在ECX寄存器中。我们在前面提到的指令(偏移量为0x6ba1331c)上设置了一个断点,以观察内存。断点触发后,我们可以看到第一个值c45adfbc指向另一个指针,该指针应该是指向数组的指针。
Breakpoint0hit eax=0000000febx=004f1b40ecx=d3544100edx=0000001cesi=d1200e18edi=0000001c eip=6ba1331cesp=004f1a34ebp=004f1a34iopl=0nvupeiplnznapo;nc; cs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00200202 QtCore4!QMatrix::dy+0x48a3: 6ba1331c8b4d08movecx,dwordptr[ebp+8]ss:002b:004f1a3c=c45adfbc 0:000>dcebp+8 004f1a3cc45adfbc00000048000000006f13830f..Z.H..........o 004f1a4c004f5cc8000000000000000000000000.\O............. 004f1a5c00000000004f65a0004f662c00000000.....eO.,fO..... 004f1a6c779eae8e00000000000000013f800000...w...........? 004f1a7c3f8000003f31e4f83f8000003f800000...?..1?...?...? 004f1a8c3f8000003f31e4f83f8000003de38800...?..1?...?...= 004f1a9c3de388003d9e1c8a3c834080004f3c00...=...=.@.<. 004f1aac4101c71c6ba133153f8000004081c71c...A.3.k...?...@
从c45adfbc观察内存引用,发现另一个指针。第一个值ab69cf80始终表示为指向它所引用的任何地方的指针。指针ab69cf80基本上是我们指针的索引数组。
0:000>dcc45adfbc c45adfbcab69cf80d35441000000000300000280..i..AT......... c45adfcc0000055a00000012c0c0c0c01c3870e2Z............p8. c45adfdc40ad870e1c3870e240ad870e00000000...@.p8....@.... c45adfec00000000c0c0c0c16c1d12c000000000...........l.... c45adffcc0c0c0c0????????????????????????....???????????? c45ae00c???????????????????????????????????????????????? c45ae01c???????????????????????????????????????????????? c45ae02c???????????????????????????????????????????????? 0:000>dcab69cf80 ab69cf80000000010000001c0000001000000001................//0000001cisoverwrittenintheregisterEDXandEDIbeforewetriggercrash ab69cf90ff000000ff800000ff008000ff808000................ ab69cfa0ff000080ff800080ff008080ff808080................ ab69cfb0ffc0c0c0ffff0000ff00ff00ffffff00................//ffc0c0c0whereitwillbestoredinEAXaftercrash,atthemomentitonlytakes0xfvalueinEAX ab69cfc0ff0000ffffff00ffff00ffffffffffff................ ab69cfd0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................ ab69cfe0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................ ab69cff0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................
因为我们知道崩溃的路径,所以可以使用下面的命令简单地设置一个断点。该命令将获得指针值“ edx + eax * 4 + 10”,并检查其是否满足0xc0。
bp6ba13321".if(poi(edx+eax*4+10)==0xc0){}.else{gc}" 0:000>g eax=000000c0ebx=004f1b40ecx=c45adfbcedx=ab69cf80esi=d1200e18edi=0000001c eip=6ba13321esp=004f1a34ebp=004f1a34iopl=0nvupeiplnznapo;nc; cs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00200202 QtCore4!QMatrix::dy+0x48a8: 6ba133218b448210moveax,dwordptr[edx+eax*4+10h]ds:002b:ab69d290=????????
观察堆栈,可以看到以下执行:
004f1a386ba3cb98QtCore4!path_gradient_span_gen::path_gradient_span_gen+0x6a74 004f1a3cc45adfbc 004f1a4000000048 004f1a4400000000 004f1a486f13830fverifier!DphCommitMemoryForPageHeap+0x16f 004f1a4c004f5cc8 004f1a5000000000 004f1a5400000000 004f1a5800000000 004f1a5c00000000 004f1a60004f65a0 004f1a64004f662c 004f1a6800000000 004f1a6c779eae8entdll!RtlAllocateHeap+0x3e
如果我们反汇编6ba3cb98,则可以看到以下反汇编代码,真正的漏洞根本原因在于此代码。
6ba3cb898b96b4000000movedx,dwordptr[esi+0B4h] 6ba3cb8f8b4df4movecx,dwordptr[ebp-0Ch] 6ba3cb9252pushedx 6ba3cb938bd7movedx,edi 6ba3cb95ff5580calldwordptr[ebp-80h] 6ba3cb988b4e7cmovecx,dwordptr[esi+7Ch] Cpseudocode grad=*(&ptr_grad); if(grad>0.0099999998) { input_value=grad_size(check,size,input); ptr_grad=*(input); ...cuthere...
我们在6ba3cb89地址上设置断点并观察ESI + 0xB4,我们可以看到一个指针指向另一个位置:
0:000>r eax=00000000ebx=00791878ecx=00000005edx=00793938esi=cb07de18edi=0000001c eip=6ba3cb89esp=00791780ebp=00791870iopl=0nvupeiplnznapo;nc; cs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00200202 QtCore4!path_gradient_span_gen::path_gradient_span_gen+0x6a65: 6ba3cb898b96b4000000movedx,dwordptr[esi+0B4h]ds:002b:cb07decc=cf69afbc 0:000>dcesi+0B4h cb07decccf69afbcc0c0c0000000000000000100..i............. cb07dedcc0c0c0c0000000000000000000000000................ cb07deec00000000000000000000000000000000................ cb07defc00000000cf030fd00000000000000000................ cb07df0c00000000000000000000000000000000................ cb07df1cc0c0c0c0000000003ff0000000000000...........?.... cb07df2c00000000000000000000000000000000................ cb07df3c00000000000000003ff0000000000000...........?.... 0:000>dccf69afbc cf69afbcc88baf80d13261000000000300000280.....a2......... cf69afcc0000055f00000012c0c0c0c01c3870e2_............p8. cf69afdc40ad870e1c3870e240ad870e00000000...@.p8....@.... cf69afec00000000c0c0c0c16c1d12c000000000...........l.... cf69affcc0c0c0c0????????????????????????....???????????? cf69b00c???????????????????????????????????????????????? cf69b01c???????????????????????????????????????????????? cf69b02c???????????????????????????????????????????????? 0:000>dcc88baf80 c88baf80000000010000001c0000001000000001................ c88baf90ff000000ff800000ff008000ff808000................ c88bafa0ff000080ff800080ff008080ff808080................ c88bafb0ffc0c0c0ffff0000ff00ff00ffffff00................ c88bafc0ff0000ffffff00ffff00ffffffffffff................ c88bafd0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................ c88bafe0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................ c88baff0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................
从这里我们可以知道代码实际上没有从指针释放任何东西。一旦移至EDX,EDX将保留指向索引数组的指针:
eax=00000000ebx=00791878ecx=00000005edx=cf69afbcesi=cb07de18edi=0000001c eip=6ba3cb8fesp=00791780ebp=00791870iopl=0nvupeiplnznapo;nc; cs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00200202 QtCore4!path_gradient_span_gen::path_gradient_span_gen+0x6a6b: 6ba3cb8f8b4df4movecx,dwordptr[ebp-0Ch]ss:002b:00791864=d1326100 0:000>dccf69afbc cf69afbcc88baf80d13261000000000300000280.....a2......... cf69afcc0000055f00000012c0c0c0c01c3870e2_............p8. cf69afdc40ad870e1c3870e240ad870e00000000...@.p8....@.... cf69afec00000000c0c0c0c16c1d12c000000000...........l.... cf69affcc0c0c0c0????????????????????????....???????????? cf69b00c???????????????????????????????????????????????? cf69b01c???????????????????????????????????????????????? cf69b02c???????????????????????????????????????????????? 0:000>dcc88baf80 c88baf80000000010000001c0000001000000001................ c88baf90ff000000ff800000ff008000ff808000................ c88bafa0ff000080ff800080ff008080ff808080................ c88bafb0ffc0c0c0ffff0000ff00ff00ffffff00................ c88bafc0ff0000ffffff00ffff00ffffffffffff................ c88bafd0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................ c88bafe0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................ c88baff0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0................
崩溃后的堆栈跟踪:
0:000>kvL #ChildEBPRetAddrArgstoChild 00012f18d46ba3cb98cc53afbc0000004800000000QtCore4!QMatrix::dy+0x48a8 01012f19d06b8143ef00000000012f1b78012f1a5cQtCore4!path_gradient_span_gen::path_gradient_span_gen+0x6a74 02012f1a306b8142590000002e012f5bd000000000QtCore4!QBrush::setMatrix+0x234 03012f5b146b8249a40000003b012f5b68cc780e18QtCore4!QBrush::setMatrix+0x9e 04012f5b2c6b80cc840000003b012f5b68cc780e18QtCore4!QImage::rect+0x22b 05012f5b486b857ccc0000003b012f5b68cc780e18QtCore4!QTransform::inverted+0xec8 06012f64dc6b81c55b00000000003c000000000000QtCore4!QSvgFillStyle::setFillOpacity+0x1b59 07012f66c06b896844012f6724cc818ff00000001cQtCore4!QPainter::drawPixmap+0x1c98 08012f67b46d1e0fbd012f69ec012f66d4012f6864QtCore4!QPainter::drawImage+0x325 09012f67d46d0dd155012f6a54012f69ec012f6864kso!GdiDrawHoriLineIAlt+0x11a1a 0a012f67ec6d0c8d88012f69ec012f68e0012f6864kso!kpt::PainterExt::drawBitmap+0x23
堆分析:
0:000>!heap-p-acc53afbc addresscc53afbcfoundin _DPH_HEAP_ROOT@6731000 inbusyallocation(DPH_HEAP_BLOCK:UserAddrUserSize-VirtAddrVirtSize) cc36323c:cc53afa858-cc53a0002000 6f13ab70verifier!AVrfDebugPageHeapAllocate+0x00000240 77a9909bntdll!RtlDebugAllocateHeap+0x00000039 779ebbadntdll!RtlpAllocateHeap+0x000000ed 779eb0cfntdll!RtlpAllocateHeapInternal+0x0000022f 779eae8entdll!RtlAllocateHeap+0x0000003e 6f080269MSVCR100!malloc+0x0000004b 6f08233bMSVCR100!operatornew+0x0000001f 6b726c67QtCore4!QImageData::create+0x000000fa 6b726b54QtCore4!QImage::QImage+0x0000004e 6b7a0e21QtCore4!png_get_text+0x00000436 6b79d7a8QtCore4!QImageIOHandler::setFormat+0x000000de 6b79d457QtCore4!QPixmapData::fromFile+0x000002bf 6b725eb4QtCore4!QImageReader::read+0x000001e2 6d0ca585kso!kpt::VariantImage::forceUpdateCacheImage+0x0000254e 6d0c5964kso!kpt::Direct2DPaintEngineHelper::operator=+0x00000693 6d0c70d0kso!kpt::RelativeRect::unclipped+0x00001146 6d0c8d0ckso!kpt::VariantImage::forceUpdateCacheImage+0x00000cd5 6d451d5ckso!BlipCacheMgr::BrushCache+0x0000049a 6d451e85kso!BlipCacheMgr::GenerateBitmap+0x0000001d 6d453227kso!BlipCacheMgr::GenCachedBitmap+0x00000083 6d29bb92kso!drawing::PictureRenderLayer::render+0x000009b6 6d450fb1kso!drawing::RenderTargetImpl::paint+0x00000090 6d29b528kso!drawing::PictureRenderLayer::render+0x0000034c 6d2a2d83kso!drawing::VisualRenderer::render+0x00000060 6d2b8970kso!drawing::SingleVisualRenderer::drawNormal+0x000002b5 6d2b86a7kso!drawing::SingleVisualRenderer::draw+0x000001e1 6d2b945ekso!drawing::SingleVisualRenderer::draw+0x00000046 6d3d0142kso!drawing::ShapeVisual::paintEvent+0x0000044a 680a2b5cwpsmain!WpsShapeTreeVisual::getHittestSubVisuals+0x000068f1 6d0e36dfkso!AbstractVisual::visualEvent+0x00000051 6d3cbe97kso!drawing::ShapeVisual::visualEvent+0x0000018f 6d0eba90kso!VisualPaintEvent::arriveVisual+0x0000004e 0:000>dt_DPH_BLOCK_INFORMATIONcc780e18-0x20 verifier!_DPH_BLOCK_INFORMATION +0x000StartStamp:0xc0c0c0c0 +0x004Heap:0xc0c0c0c0Void +0x008RequestedSize:0xc0c0c0c0 +0x00cActualSize:0xc0c0c0c0 +0x010Internal:_DPH_BLOCK_INTERNAL_INFORMATION +0x018StackTrace:0xc0c0c0c0Void +0x01cEndStamp:0xc0c0c0c0
段中的最后一个堆条目通常是一个空闲块。堆块的状态指示为空闲块,堆块声明前一个块的大小为00108,而当前块的大小为00a30。前一块报告其自身大小为0x20字节,不匹配。位置为05f61000的堆块的使用似乎是该堆块的使用导致以下块的元数据损坏的可能性。堆块如下:
0:000>!heap-a05f60000 IndexAddressNameDebuggingoptionsenabled 1:05f60000 Segmentat05f60000to0605f000(00001000bytescommitted) Flags:00000002 ForceFlags:00000000 Granularity:8bytes SegmentReserve:00100000 SegmentCommit:00002000 DeCommitBlockThres:00000200 DeCommitTotalThres:00002000 TotalFreeSize:00000146 Max.AllocationSize:fffdefff LockVariableat:05f60258 NextTagIndex:0000 MaximumTagIndex:0000 TagEntries:00000000 PsuedoTagEntries:00000000 VirtualAllocList:05f6009c Uncommittedranges:05f6008c 05f61000:000fe000(1040384bytes) FreeList[00]at05f600c0:05f605b8.05f605b8 05f605b0:00108.00a30[100]-free Segment00at05f60000: Flags:00000000 Base:05f60000 FirstEntry:05f604a8 LastEntry:0605f000 TotalPages:000000ff TotalUnCommit:000000fe LargestUnCommit:00000000 UnCommittedRanges:(1) HeapentriesforSegment00inHeap05f60000 address:psize.sizeflagsstate(requestedsize) 05f60000:00000.004a8[101]-busy(4a7) 05f604a8:004a8.00108[101]-busy(107)Internal 05f605b0:00108.00a30[100] 05f60fe0:00a30.00020[111]-busy(1d) 05f61000:000fe000-uncommittedbytes. 0:000>dd05f60fe0 05f60fe0a9b3c8360300708705f6008c05f6008c 05f60ff005f6003805f6003805f61000000fe000 05f61000???????????????????????????????? 05f61010???????????????????????????????? 05f61020???????????????????????????????? 05f61030???????????????????????????????? 05f61040???????????????????????????????? 05f61050????????????????????????????????
0x03 披露时间表
该漏洞于2020年8月报告,披露时间表:
- 2020-08-04-将电子邮件发送到公开提供的WPS的各种邮件列表(销售和支持)。
- 2020-08-10-WPS团队回应该报告可以转发给他们。
- 2020-08-11-要求进一步的信息,例如向适当的渠道披露等。
- 2020-08-17-根据先前的要求与WPS团队进行跟进。
- 2020-08-18-通过电子邮件提供技术报告和概念验证(未加密)。
- 2020-08-25-WPS跟进报告进度。
- 2020-08-26-WPS更新说此问题已转发给开发团队。
- 2020-08-28-WPS发送了一封电子邮件,指出该问题已在最新的下载版本11.2.0.9403中得到解决。
- 2020-08-28-针对提供的PoC测试了新版本,并确认问题已解决。
- 2020-08-28-向WPS团队寻求咨询或更改日志更新。
- 2020-09-03-申请漏洞CVE。
- 2020-09-14-已分配CVE编号:CVE-2020-25291。
本文翻
转载请注明:IT运维空间 » 安全防护 » CVE-2020-25291:金山WPS Office远程堆溢出漏洞分析
发表评论