Talos Vulnerability Report

TALOS-2017-0368

VMware VNC Dynamic Resolution Request Code Execution Vulnerability

December 19, 2017
CVE Number

CVE-2017-4933

Summary

An exploitable code execution vulnerability exists in the remote management functionality of VMware . A specially crafted set of VNC packets can cause a heap overflow resulting in heap corruption. An attacker can create a VNC session to trigger this vulnerability.

Tested Versions

Vase, Linux/Windows

Product URLs

https://my.vmware.com/web/vmware/info/slug/desktopendusercomputing/vmwareworkstationpro/120

CVSSv3 Score

9.0 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer

Details

VMware's VNC implementation is used for remote management, remote access, and automation purposes in VMware products, such as Workstation, Player, and ESXi, which share a common VMW VNC code base between them all.

Along with the standard VNC messages that all VNC servers are required to serve, as specified in the RFB RFC's, VMware uses a custom and proprietary VNC extension, designated by the byte ì\x7fî, which I will refer to as the vmw-vnc protocol during the course of this write-up. It should be noted that the vmw-vnc protocol reimplemented a few different VNC features, including MouseActions and KeyActions, the benefits of which could not be ascertained. But it also has some interesting messages, including ìVMWAudioî, ìVMWTouchEventî, however these were not supported within the vmw-vnc at the present. VMware VNC messages all follow the general format:

offset|bytes  <== Explanation 
--------------------------------------------------------------------------------------
0x0  | \x7f  |     <=Designates VMW (Virtual Machine Window) message
0x1  | \xAB        <= Which VMW message (0x0-0xB seen so far)
0x2  | \x00\x08   <= Length of total VMW message 
0x4  | [Where the message specific bytes begin]

It should also be noted that the size field just has to be larger than the message type's minimum. For example, the VMWClientAck request is as such:

\x7f\x04\x00\x08\x00\x00\xAB\xCD

With ì\x00\x00\xAB\xCDî being the duration of the ACK, but the size ì\x00\x08î just needs to be greater than a hardcoded 0x8.

This specific vulnerability takes place within the VMWDynResolution request. Not surprisingly, this is one of the few messages that causes the VNC server to read in a user-supplied amount of bytes. The VMWDynResolution request is as follows:

0x0  | \x7f         [Header]
0x1  | \x0a     [Type]
0x2  | \x00\x06    [Size]
0x4  | \xZZ\xZZ  [NumRectangle]
0x6  |  [Begin rectangle data] 

These rectangles contribute to dynamically sizing the resolution and each rectangle consists of four dwords. These seem to refer to the bounds of the rectangle:

buf += struct.pack(">I",self.left)
buf += struct.pack(">I",self.top)
buf += struct.pack(">I",self.right)
buf += struct.pack(">I",self.bottom)

The location of the crash occurs when the VNC server handles the data. When the server allocates space the ìVNCChannelî object, there is a statically sized space (0xac0) for the contents of any given VNC packet. It only reads in 0xaaa bytes at any given time, so the buffer never overflows, however the NumRectangle field is never validated, leading to a situation where the heap can still be corrupted.

When formatting the rectangle data, a pseudo python example is shown below:

for x in range(0,NumRects):
    rect = rectangle(input_data[x:x+16])
    byteSwap(rect.left)     #[rsi+0]
    byteSwap(rect.top)      #[rsi+4]
    byteSwap(rect.right)        #[rsi+8]
    byteSwap(rect.bottom)   #[rsi+C]
if rect.right < 0 or rect.bottom < 0:
    return Bad

As noted above, since there's no check on the number of rectangles, and there's also a static buffer size, even though the buffer cannot be overflowed (fixed-size read), the heap can be corrupted by causing the server to treat heap metadata as a rectangle. While it does do some validation, such that you cannot go all the way down the heap, curiously, it only checks two of the co-ordinates for a valid/positive value, and it also doesn't validate before swapping the bytes. It should also be noted that the server does not error completely upon heap corruption, it will keep reading in VNC packets in a loop until there is no more data.

An attacker could use this vulnerability to corrupt the heap, which could lead to code execution.

Crash Information

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x5f ('_')
RCX: 0xffffffffffffffff 
RDX: 0x6 
RSI: 0x164e2 
RDI: 0x164db 
RBP: 0x7f0da5fc7ba0 --> 0x7f0da5fc7bb0 ("00007f0d70075110")
RSP: 0x7f0da5fc7808 --> 0x7f0daa1b0448 (<__GI_abort+328>: mov rdx,QWORD PTR fs:0x10)
RIP: 0x7f0daa1af067 (<__GI_raise+55>: cmp rax,0xfffffffffffff000)
R8 : 0x3031313537303037 ('70075110')
R9 : 0x0 
R10: 0x8 
R11: 0x3206 
R12: 0x7f0da5fc79b0 --> 0x0 
R13: 0x7 
R14: 0x5f ('_')
R15: 0x7
EFLAGS: 0x3206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7f0daa1af05d <__GI_raise+45>: movsxd rdi,ecx
0x7f0daa1af060 <__GI_raise+48>: mov eax,0xea
0x7f0daa1af065 <__GI_raise+53>: syscall 
=> 0x7f0daa1af067 <__GI_raise+55>: cmp rax,0xfffffffffffff000
0x7f0daa1af06d <__GI_raise+61>: ja 0x7f0daa1af08d <__GI_raise+93>
0x7f0daa1af06f <__GI_raise+63>: repz ret 
0x7f0daa1af071 <__GI_raise+65>: nop DWORD PTR [rax+0x0]
0x7f0daa1af078 <__GI_raise+72>: test ecx,ecx
[------------------------------------stack-------------------------------------]
0000| 0x7f0da5fc7808 --> 0x7f0daa1b0448 (<__GI_abort+328>: mov rdx,QWORD PTR fs:0x10)
0008| 0x7f0da5fc7810 --> 0x20 (' ')
0016| 0x7f0da5fc7818 --> 0x0 
0024| 0x7f0da5fc7820 --> 0x0 
0032| 0x7f0da5fc7828 --> 0x0 
0040| 0x7f0da5fc7830 --> 0x0 
0048| 0x7f0da5fc7838 --> 0x0 
0056| 0x7f0da5fc7840 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGABRT
0x00007f0daa1af067 in __GI_raise (sig=sig@entry=0x6) at ../nptl/sysdeps/unix/sysv/      
linux/raise.c:56
56 ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
<(^.^)># bt
#0 0x00007f0daa1af067 in __GI_raise (sig=sig@entry=0x6) at ../nptl/sysdeps/unix/sysv/   
linux/raise.c:56
#1 0x00007f0daa1b0448 in __GI_abort () at abort.c:89
#2 0x00007f0daa1ed1b4 in __libc_message (do_abort=do_abort@entry=0x1, 
fmt=fmt@entry=0x7f0daa2e2210 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/   
libc_fatal.c:175
#3 0x00007f0daa1f298e in malloc_printerr (action=0x1, str=0x7f0daa2de326 "free():  
invalid pointer", ptr=<optimized out>)
at malloc.c:4996
#4 0x00007f0daa1f3696 in _int_free (av=<optimized out>, p=<optimized out>,     
have_lock=0x0) at malloc.c:3840

Mitigation

An important factor in this vulnerability is that it requires a successful VNC authentication beforehand, but by default, VMware does not require a username/password for VNC sessions. Turning on VNC authentication should mitigate this, turning it from a no-auth bug to a single-auth one.

Timeline

2017-07-12 - Vendor Disclosure
2017-12-19 - Public Release

Credit

Discovered by Lilith Wyatt of Cisco Talos.