Talos Vulnerability Report

TALOS-2021-1412

WPS Office HtmTableAlt use-after-free vulnerability

May 9, 2022
CVE Number

CVE-2021-40399

Summary

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.

Tested Versions

WPS Office 11.2.0.10351

Product URLs

WPS Office - https://www.wps.com/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-416 - Use After Free

Details

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.

Crash Information

(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.

Vendor Response

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

Timeline

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

Credit

Discovered by Marcin "Icewall" Noga of Cisco Talos.