In this series we’ll document a novel and as-yet-undocumented Virtual Machine detection trick for each month of 2021. These detection tricks will be focused on 64-bit Windows 10 or Windows Server 2019 guests, targeting a variety of VM platforms.
Physical memory resource maps
By far the most ubiquitous source of discrepancies between VMs and real hardware is the virtualized hardware façade that is presented by the VM platform. To kick off this series, we’re going to take a look at system resource maps. These lists are available to unprivileged users via the Windows registry, in the following paths:
HKLM\Hardware\ResourceMap\System Resources\Loader Reserved\
HKLM\Hardware\ResourceMap\System Resources\Physical Memory\
HKLM\Hardware\ResourceMap\System Resources\Reserved\
The DACLs on these keys are permissive, allowing read access by everyone, including low integrity processes.
If you take a look at the values in these keys, in regedit, you might notice they are of an unfamiliar type.
The REG_RESOURCE_LIST
key type isn’t a general-purpose data type. It is specifically for device driver resource lists.
A convenient way to query these, without needing to know what the value of the REG_RESOURCE_LIST
constant is on your particular target system, is to call RegQueryValueEx on the registry value with the lpData
parameter set to NULL
. This causes the type constant and length to be returned in the lpType
and lpcbData
parameters respectively:
This tells you what the REG_RESOURCE_LIST
type constant is, and how much space you need to allocate when reading the value.
The structure of this data type is defined in CM_RESOURCE_LIST:
Digging down a few levels of nested structures, we eventually find that the meat of the information is stored in an array of CM_PARTIAL_RESOURCE_DESCRIPTOR structures.
This structure describes general-purpose descriptor that may contain a number of different fields, depending on the specific type of resource being described. The type, and by extension the union member of the struct we should look at, is specified by the Type field at the start of the structure.
The type of descriptor we are interested in is CmResourceTypeMemory
(numeric value 3). This is used to describe physical memory resource regions. The structure is simple:
By iterating through all of the descriptors, we can extract all of the memory resource map entries.
At this point we can extract and compare the memory resource maps from various physical hosts and VMs. The initial results are as follows.
Type | OS/Platform | Assigned RAM | Physical Memory Translated | Reserved Translated | Loader Reserved Raw |
Host 1 | Win10 x64 | N/A | 00001000 – 0003e000
5acf6000 – 66e71000 |
00001000 – 0003e000
00203000 – 00207000 00600000 – 00800000 |
00000000 – 00101000
002f3000 – 002f8000 02600000 – 02800000 5a8cb000 – 5acf6000 fd000000 – fe800000 |
Host 2 | Win10 x64 | N/A | 00001000 – 0009d000
cb52d000 – cb98c000 |
00001000 – 0009d000 | 00000000 – 000a0000
cb526000 – cb52d000 fec00000 – fec01000 |
Host 3 | Win10 x64 (w. Hyper-V) |
N/A | 00001000 – 0009d000 | 00001000 – 0009d000
001f5000 – 001fe000 002fe000 – 003fe000 |
00000000 – 000a0000
001f5000 – 001fe000 00293000 – 00297000 00875000 – 0095a000 0364c000 – 03675000 6929d000 – 7fa00000 |
Host 4 | Win10 x64 | N/A | 00001000 – 00058000
b83ab000 – c8bed000 |
00001000 – 00058000 | 00000000 – 00100000
00207000 – 0020b000 f0000000 – f8000000 |
VM1 | Win10 x64 (Hyper-V) |
Dynamic | 00001000 – 000a0000
f7fff000 – f8000000 |
00001000 – 0001a000 | 00000000 – 0001a000
f6ecc000 – f6f1b000 |
VM2 | Win10 x64 (Hyper-V) |
Dynamic | 00001000 – 000a0000 | 00001000 – 000a0000 | 00000000 – 000a0000
7eee9000 – 7ef1b000 |
VM3 | Win10 x64 (Hyper-V) |
2GB | 00001000 – 000a0000 | 00001000 – 000a0000 | 00000000 – 000a0000
7eee9000 – 7ef1b000 |
VM4 | Win10 x64 (VirtualBox) |
4GB | 00001000 – 0009f000 | 00001000 – 00018000 | 00000000 – 00018000
00102000 – 00103000 |
VM5 | Win10 x64 (VirtualBox) |
4GB | 00001000 – 0009f000 | 00001000 – 00017000 | 00000000 – 00017000
00102000 – 00103000 |
VM6 | Win8.1 x64 (VirtualBox) |
2GB | 00001000 – 0009f000 | 00001000 – 0000e000 | 00000000 – 0000e000
000f0000 – 00100000 |
VM7 | Win8.1 x64 (VirtualBox) |
10GB | 00001000 – 0009f000 | 00001000 – 0000e000 | 00000000 – 0000e000
000f0000 – 00130000 |
VM8 | Win7 x64 (VirtualBox) |
4GB | 00001000 – 0009f000 | 00001000 – 00008000 | 00000000 – 00008000
00110000 – 00140000 |
VM9 | Win7 x86* (VirtualBox) |
2GB | 00001000 – 0009f000
00100000 – 7fff0000 |
00001000 – 00005000
00030000 – 00040000 |
00000000 – 00005000
00030000 – 00040000 0009f000 – 000a0000 000f0000 – 00100000 7fff0000 – 80000000 fec00000 – fec01000 fee00000 – fee01000 fffc0000 – 100000000 |
The first clear finding is that VMs tend to have fewer map entries, particularly in the loader reserved key. The clear exception is the Win7 x86 VM – the only x86 entry in the table – which has many more regions than the rest in the rightmost column. This count distinction is unfortunately insufficient as a detection technique, as there is sufficient variation between hardware and platform to make this prone to false positives.
Looking a little more carefully, we can start to notice some more useful patterns:
- All Hyper-V VMs have a Physical Memory Translated region matching 00001000 – 000a0000.
- All VirtualBox VMs have a Physical Memory Translated region matching 00001000 – 009f000.
- None of the host machines have Physical Memory Translated regions matching these addresses.
- On both VirtualBox and Hyper-V, the lowest Reserved Translated memory region always perfectly overlaps the lowest Loader Reserved Raw memory region (e.g. 00001000 – 0000e000 becomes 00000000 – 0000e000).
By combining the overlapped region test with the known fixed region test for Hyper-V and VirtualBox, we can determine the status of the current system with confidence.
This check can be implemented in C as follows:
That’s it for this month’s instalment of VM Detection Tricks. Stay tuned for part two in February.