CVE-2021-40399
An exploitable use-after-free vulnerability exists in WPS Spreadsheets ( ET ) as part of WPS Office, version 11.2.0.10351. A specially-crafted XLS file can cause a use-after-free condition, resulting in remote code execution. An attacker needs to provide a malformed file to the victim to trigger the vulnerability.
WPS Office 11.2.0.10351
WPS Office - https://www.wps.com/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-416 - Use After Free
WPS Office previously known as a Kingsoft Office is a suite of tools used for productivity in both a corporate environment as well as by end-users. It offers a range of tools that can be used for various purposes. Such as WPS Spreadsheets for spreadsheets, WPS Writer for document editing, and so on.
A specially-crafted XLS file written in a proper form of HTML/XML tags can lead to a use-after-free vulnerability and remote code execution. Let us run our malformed xls file in ET.exe using debugger:
(6a4.1674): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 6E147E:0
eax=00000000 ebx=0d4eeeb8 ecx=00000000 edx=00000000 esi=1ccb5dcb edi=5f06dfb8
eip=0228282b esp=0d4eedb0 ebp=0d4eee58 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
0228282b 006200 add byte ptr [edx],ah ds:002b:00000000=??
0:011> kb
# ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0d4eee58 0dd5b0b8 00000000 06dbd130 06dbd170 0x228282b
01 0d4eef20 5f0b0a75 1ccb432f 07b5f1a0 00000000 0xdd5b0b8
02 0d4ef310 5f0afba4 07b6b670 00000000 1ccb45df html2!html2::HtmlParser::parseStream+0x75
03 0d4ef5e0 5f2fd0e0 00f6d814 00000001 0d4ef694 html2!html2::HtmlParser::parse+0x2a4
04 0d4ef8b4 5f2d41ef 00f6d814 06d81ea8 00000000 ethtmlrw2!html2::StrmUtf8Converter::~StrmUtf8Converter+0x42b0
05 0d4ef8f0 5f2d4bf9 06bdbd40 5f2d5f3f f114ab06 ethtmlrw2!chart::KETChartDataSourceProvider::getContextOOXML+0xbf
06 0d4ef920 75a64f9f 07a63158 45279ebd 75a64f60 ethtmlrw2!chart::KETChartDataSourceProvider::getContextOOXML+0xac9
07 0d4ef958 776ffa29 075b0798 776ffa10 0d4ef9c4 ucrtbase!thread_start<unsigned int (__stdcall*)(void *),1>+0x3f
08 0d4ef968 77847a9e 075b0798 02cb572f 00000000 KERNEL32!BaseThreadInitThunk+0x19
09 0d4ef9c4 77847a6e ffffffff 77868a4e 00000000 ntdll!__RtlUserThreadStart+0x2f
0a 0d4ef9d4 00000000 75a64f60 075b0798 00000000 ntdll!_RtlUserThreadStart+0x1b
Looks like execution flow has been redirected into a non-executable area:
0:011> !address 0228282b
Usage: <unknown>
Base Address: 02270000
End Address: 022da000
Region Size: 0006a000 ( 424.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 02270000
Allocation Protect: 00000001 PAGE_NOACCESS
Let’s check the memory content: 0:011> db 02282828 02282828 74 00 61 00 62 00 6c 00-65 00 00 00 74 00 62 00 t.a.b.l.e…t.b. 02282838 6f 00 64 00 79 00 00 00-74 00 66 00 6f 00 6f 00 o.d.y…t.f.o.o. 02282848 74 00 00 00 74 00 68 00-65 00 61 00 64 00 00 00 t…t.h.e.a.d… 02282858 6c 00 6f 00 63 00 6b 00-00 00 00 00 70 00 61 00 l.o.c.k…..p.a. 02282868 74 00 68 00 00 00 00 00-73 00 6b 00 65 00 77 00 t.h…..s.k.e.w. 02282878 00 00 00 00 67 00 72 00-6f 00 75 00 70 00 00 00 ….g.r.o.u.p… 02282888 6f 00 76 00 61 00 6c 00-00 00 00 00 63 00 75 00 o.v.a.l…..c.u. 02282898 72 00 76 00 65 00 00 00-72 00 67 00 62 00 28 00 r.v.e…r.g.b.(. 0:011> du 02282828 02282828 “table”
We can clearly see that indeed program execution ended in a non executable area (data). When we take a few steps back to see the moment of a code execution redirection we see the following code:
0:011> r
eax=0228bea0 ebx=0228bed0 ecx=0228bed0 edx=013e0000 esi=0dd5bd78 edi=0d4eee58
eip=5f06dfb5 esp=0d4eed94 ebp=0d4eedf4 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
html2!html2::HtmBoxRefOperator::imitateBoxFlags+0x365:
5f06dfae 8b5d08 mov ebx, dword ptr [ebp+8]
5f06dfb1 8bcb mov ecx, ebx
5f06dfb3 8b03 mov eax, dword ptr [ebx]
5f06dfb5 ff5034 call dword ptr [eax+34h] ds:002b:0228bed4=02282828
Looks like a typical call to one of the virtual functions. It is highly probable that the object has been released before and a vftable pointer 0228bed0
has been overwritten.
Setting a write access breakpoint on 0228bed0
, let’s execute our software again:
0:011> g-
Breakpoint 0 hit
Time Travel Position: 6E1064:A7
eax=0228bea0 ebx=0228bed0 ecx=00a6e000 edx=0000000b esi=01437c70 edi=06cd5a98
eip=6a437c7d esp=0d4eec08 ebp=0d4eec14 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
kso!mfxGlobalFree2+0x5d:
6a437c7d 8b4704 mov eax,dword ptr [edi+4] ds:002b:06cd5a9c=00000055
0:011> kb
# ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0d4eec14 5f06d818 0000000b 00000030 1ccb5c03 kso!mfxGlobalFree2+0x5d
01 0d4eec3c 5f0b649f 1ccb5c53 0d4ef078 07ba5174 html2!html2::HtmCreator::createXmlNodesRef+0x4f8
02 0d4eec6c 5f0c22c0 07ba5174 0d4eef38 0d4eef38 html2!html2::StrIdSet::gainLower+0xbf
03 0d4eec90 5f0c8d34 022812e0 07b60f01 5f0c90f0 html2!html2::ParserContext::urlStack+0xac00
04 0d4eeca4 5f0c8a47 022812e0 00000000 07b60f01 html2!html2::ParserContext::urlStack+0x11674
05 0d4eecd0 5f0c6dfc 022812e0 07b60f01 0d4eef38 html2!html2::ParserContext::urlStack+0x11387
06 0d4eecf8 5f0c8a96 00020000 00000001 0d4ef3f4 html2!html2::ParserContext::urlStack+0xf73c
07 0d4eed20 5f0c6dfc 022812f0 07b60f01 0d4eef38 html2!html2::ParserContext::urlStack+0x113d6
08 0d4eed48 5f0c7c40 00084404 00000000 00000000 html2!html2::ParserContext::urlStack+0xf73c
09 0d4eed70 5f0c4d7c 022812f0 00000000 00000001 html2!html2::ParserContext::urlStack+0x10580
0a 0d4eed90 5f0b24da 022812f0 00000000 07ba6ff0 html2!html2::ParserContext::urlStack+0xd6bc
0b 0d4eedc8 5f0b32f0 07b6b670 07b15e70 0d4ef668 html2!html2::HtmDocument::topBoxs+0x197a
0c 0d4eee00 5f0b2271 1ccb5e07 07b6b670 0d4eef38 html2!html2::HtmDocument::topBoxs+0x2790
0d 0d4eee38 5f0cb8c5 00000003 0074683c 0d4eef38 html2!html2::HtmDocument::topBoxs+0x1711
0e 0d4eef20 5f0b0a75 1ccb432f 07b5f1a0 00000000 html2!html2::ParserContext::urlStack+0x14205
0f 0d4ef310 5f0afba4 07b6b670 00000000 1ccb45df html2!html2::HtmlParser::parseStream+0x75
10 0d4ef5e0 5f2fd0e0 00f6d814 00000001 0d4ef694 html2!html2::HtmlParser::parse+0x2a4
11 0d4ef8b4 5f2d41ef 00f6d814 06d81ea8 00000000 ethtmlrw2!html2::StrmUtf8Converter::~StrmUtf8Converter+0x42b0
12 0d4ef8f0 5f2d4bf9 06bdbd40 5f2d5f3f f114ab06 ethtmlrw2!chart::KETChartDataSourceProvider::getContextOOXML+0xbf
13 0d4ef920 75a64f9f 07a63158 45279ebd 75a64f60 ethtmlrw2!chart::KETChartDataSourceProvider::getContextOOXML+0xac9
14 0d4ef958 776ffa29 075b0798 776ffa10 0d4ef9c4 ucrtbase!thread_start<unsigned int (__stdcall*)(void *),1>+0x3f
15 0d4ef968 77847a9e 075b0798 02cb572f 00000000 KERNEL32!BaseThreadInitThunk+0x19
16 0d4ef9c4 77847a6e ffffffff 77868a4e 00000000 ntdll!__RtlUserThreadStart+0x2f
17 0d4ef9d4 00000000 75a64f60 075b0798 00000000 ntdll!_RtlUserThreadStart+0x1b
Our hypothesis has been confirmed. What’s important for us here is that the object has been released via a call to kso!mfxGlobalFree2
.
If we track its allocation :
dx -r1 -g @$cursession.TTD.Calls("kso!mfxGlobalAlloc2").Where( x => x.ReturnValue == `0x0228bed0`)
We get additional information that our object represents a table:
.text:5F06CEF0 public: static struct html2::HtmTable * __cdecl html2::HtmCreator::createHtmTableAlt(void) proc near
.text:5F06CEF0 push 30h ; '0'
.text:5F06CEF2 call mfxGlobalAlloc2
.text:5F06CEF7 mov dword ptr [eax], offset const html2::HtmTableAltImpl::`vftable'
.text:5F06CEFD mov dword ptr [eax+4], 0
.text:5F06CF04 mov dword ptr [eax+8], 0
.text:5F06CF0B mov dword ptr [eax+0Ch], 0
.text:5F06CF12 mov dword ptr [eax+10h], 0
.text:5F06CF19 mov dword ptr [eax+14h], 0
.text:5F06CF20 mov dword ptr [eax+18h], 0
.text:5F06CF27 mov dword ptr [eax+1Ch], 0
.text:5F06CF2E mov dword ptr [eax+20h], 0
.text:5F06CF35 mov dword ptr [eax+24h], 0
.text:5F06CF3C mov dword ptr [eax+28h], 0
.text:5F06CF43 mov word ptr [eax+2Ch], 0
.text:5F06CF49 retn
0:011> r
eax=`0228bed0` ebx=07ba51b4 ecx=0228bed0 edx=00000037 esi=07ba51b4 edi=079b1810
eip=5f06cef7 esp=0d4eebf0 ebp=0d4eec68 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
html2!html2::HtmCreator::createHtmTableAlt+0x7:
5f06cef7 c700b0be105f mov dword ptr [eax],offset html2::HtmTableAltImpl::`vftable' (5f10beb0) ds:002b:0228bed0=0228bf00
Proper heap grooming can give an attacker full control of this use-after-free vulnerability and as a result could allow it to be turned into arbitrary code execution.
(6a4.1674): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 6E147D:7D
eax=0228bea0 ebx=0228bed0 ecx=0228bed0 edx=013e0000 esi=0dd5bd78 edi=0d4eee58
eip=5f06dfb5 esp=0d4eed94 ebp=0d4eedf4 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
html2!html2::HtmBoxRefOperator::imitateBoxFlags+0x365:
5f06dfb5 ff5034 call dword ptr [eax+34h] ds:002b:0228bed4=02282828
0:011> g
(6a4.1674): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 6E147E:0
eax=00000000 ebx=0d4eeeb8 ecx=00000000 edx=00000000 esi=1ccb5dcb edi=5f06dfb8
eip=0228282b esp=0d4eedb0 ebp=0d4eee58 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
0228282b 006200 add byte ptr [edx],ah ds:002b:00000000=??
0:011> kb
# ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0d4eee58 0dd5b0b8 00000000 06dbd130 06dbd170 0x228282b
01 0d4eef20 5f0b0a75 1ccb432f 07b5f1a0 00000000 0xdd5b0b8
02 0d4ef310 5f0afba4 07b6b670 00000000 1ccb45df html2!html2::HtmlParser::parseStream+0x75
03 0d4ef5e0 5f2fd0e0 00f6d814 00000001 0d4ef694 html2!html2::HtmlParser::parse+0x2a4
04 0d4ef8b4 5f2d41ef 00f6d814 06d81ea8 00000000 ethtmlrw2!html2::StrmUtf8Converter::~StrmUtf8Converter+0x42b0
05 0d4ef8f0 5f2d4bf9 06bdbd40 5f2d5f3f f114ab06 ethtmlrw2!chart::KETChartDataSourceProvider::getContextOOXML+0xbf
06 0d4ef920 75a64f9f 07a63158 45279ebd 75a64f60 ethtmlrw2!chart::KETChartDataSourceProvider::getContextOOXML+0xac9
07 0d4ef958 776ffa29 075b0798 776ffa10 0d4ef9c4 ucrtbase!thread_start<unsigned int (__stdcall*)(void *),1>+0x3f
08 0d4ef968 77847a9e 075b0798 02cb572f 00000000 KERNEL32!BaseThreadInitThunk+0x19
09 0d4ef9c4 77847a6e ffffffff 77868a4e 00000000 ntdll!__RtlUserThreadStart+0x2f
0a 0d4ef9d4 00000000 75a64f60 075b0798 00000000 ntdll!_RtlUserThreadStart+0x1b
0:011> lmDvmet
Browse full module list
start end module name
00d20000 00e6b000 et (export symbols) et.exe
Loaded symbol image file: et.exe
Mapped memory image file: c:\Users\icewall\AppData\Local\Kingsoft\WPS Office\11.2.0.10351\office6\et.exe
Image path: c:\Users\icewall\AppData\Local\Kingsoft\WPS Office\11.2.0.10351\office6\et.exe
Image name: et.exe
Browse all global symbols functions data
Timestamp: Sat Oct 23 14:16:30 2021 (6173FD1E)
CheckSum: 00153DB1
ImageSize: 0014B000
File version: 11.2.0.10351
Product version: 11.2.0.10351
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 0.0 Unknown
File date: 00000000.00000000
Translations: 0000.04b0
Information from resource tables:
CompanyName: Zhuhai Kingsoft Office Software Co.,Ltd
ProductName: WPS Office
InternalName: et
OriginalFilename: et.exe
ProductVersion: 11,2,0,10351
FileVersion: 11,2,0,10351
FileDescription: WPS Spreadsheets
LegalCopyright: Copyright©2021 Kingsoft Corporation. All rights reserved.
For international edition: https://www.wps.com/office/windows/ For domestic personal edition: https://official-package.wpscdn.cn/wps/download/WPS_Setup_11691.exe For enterprise edition: https://wps-cn-ep.ks3-cn-beijing.ksyun.com/wps/download/ep/WPS2019/WPSPro_11.8.2.11542.exe If you need to get the WPS official disclosure about the vulnerability, you can visit this link: https://security.wps.cn/notices/28
https://official-package.wpscdn.cn/wps/download/WPS_Setup_11691.exe
2021-11-18 - Vendor disclosure
2021-12-15 - 30 day follow up
2022-01-07 - 60 day follow up
2022-01-13 - Reissued copies of advisories to vendor per request
2022-04-02 - Talos granted disclosure extension
2022-05-02 - Vendor patched
2022-05-09 - Public Release
Discovered by Marcin "Icewall" Noga of Cisco Talos.