../
Looking for a senior C++ dev? I'm looking for work. Hire me!

Summary

I spent some time investigating how I can detect virtualization from within a user-mode application. I was looking for a solution that could fulfil the following criteria:

Unfortunately, it turns out this cannot be done. Yes, you read that correctly. Here are my findings:

Red Pill

This is the method that gained headlines back in late 2004. The code is often shown like this:

int swallow_redpill () { unsigned char m[2+4], rpill[] = "\x0f\x01\x0d\x00\x00\x00\x00\xc3"; *((unsigned*)&rpill[3]) = (unsigned)m; ((void(*)())&rpill)(); return (m[5]>0xd0) ? 1 : 0; }
Please note the segmentation fault problems.

Details behind Joanna Rutkowska's Matrix reference can be found at Invisible Things. Basically, this takes advantage of a CPU instruction that usually does not need to be called from ring3. In a VM, this instruction returns a pointer to a different location than when running natively.

The problem with this code...is mostly that we're no longer working in 2004. Two things have happened that render this test useless:

  1. Virtualization products have gotten better at doing what they do.
  2. The data returned by the CPU instruction is actually a per-CPU or per-core table vector. This means that even in non-virtualized environments, if you have multiple cores or CPUs, this test now returns false-positives. For example, on a quad-core system running natively, and assuming even distribution across all cores, 75% of the time this test will tell you it is running in a virtual environment.

This last problem is described in greater detail in a document [1, 2] comparing Red Pill and No Pill.

This method of virtual environment detection no longer works.

No Pill

The No Pill variation by Offensive Computing [1, 2] took Red Pill's SIDT CPU instruction, and replaced it with SGDT. While SGDT returns the same information for all CPUs, I've personally observed that with modern virtualization software, the vector returned is identical regardless of whether it is running natively or virtualized. Like Red Pill, the test has been obsoleted by better virtualization software.

Just like Red Pill, the source code examples I found on the web often suffer from the segmentation fault problems.

This method of virtual environment detection no longer works.

SLDT Instruction

This is yet another variation on the SIDT (Red Pill) and SGDT (No Pill) techniques. The idea behind these three tests was to find an instruction that can be called from ring3 and which returns a special value. But what I personally observed with Ubuntu 9.10-64bit is that whether running natively or through VirtualBox, SLDT was always returning a value of zero.

Of special interest, please see my comment on the segmentation fault problems.

This method of virtual environment detection no longer works.

SRT Instruction

A paper from Alfredo Andrés Omella [1, 2] describes using the SRT instruction, somewhat similar to the Red Pill/No Pill/SLDT technique. However, I've observed no discernible difference when running this natively or virtualized. I can only guess that better virtualization software has rendered this test ineffective in recent years.

This method of virtual environment detection no longer works.

Jerry

The Jerry method is specific to VMWare, and thus not a valid solution for what I need. Basically, the Jerry code shows how to call into VMWare from an application running in the guest OS. Think back to the MS Windows 2.x and 3.x days where DOS INT 2Fh could be used to tell if Windows was running. (Yes, I had dig out my ancient "PC Interrupts" bible to look up the interrupt...!) Turns out that VMWare listens for I/O on port 0x5658. Depending on how registers are setup, this can be used to get things like the VMWare product, version, or the amount of (available?) memory.

This method of virtual environment detection is specific to VMWare (and may not work since there is a way to disable the VMWare back door).

Scoopy Doo and ScoopyNG

Note the name, this is not a typo -- it isn't your friendly children's cartoon Scooby Doo, but actually Scoopy Doo.

Scoopy is a collection of short tests in a single .C source file that attempt to detect virtualization. [1, 2] Some of these tests are Windows-specific, some are VMWare-specific:

These methods of virtual environment detection no longer work, or are specific to VMWare.

MAC OUI

Inspecting the MAC OUI is an oft-mentioned technique on blogs and various web sites such as StackOverflow. [1 and 2]

While the Organizationally Unique Identifier may provide insight into whether or not an environment is virtualized:

This method of virtual environment detection is strongly discouraged.

DMI BIOS strings

The DMI bios strings, like the MAC OUI, may indicate the presence of a virtualized environment:

$ sudo dmidecode | grep -E "VirtualBox|innotek" Vendor: innotek GmbH Version: VirtualBox Manufacturer: innotek GmbH Product Name: VirtualBox

However, the problems are also similar to those preventing us from using the MAC OUI:

  1. It would be a never-ending game of chasing down new or updated virtualization products to keep track of DMI text strings.
  2. There is no requirement that virtualization products must provide any DMI BIOS tables.
  3. Some virtualization products (such as VirtualBox) provide the ability to edit the text strings in the DMI table.
  4. The dmidecode command must run as root since it needs access to /dev/mem.

This method of virtual environment detection is strongly discouraged.

PCI devices

The list of PCI devices reported by the virtualized hardware may contain names (or raw PCI device identifiers) that can be used to determine when running in a virtualized environment.

$ lspci -nn | grep VirtualBox 00:02.0 VGA compatible controller [0300]: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter [80ee:beef] 00:04.0 System peripheral [0880]: InnoTek Systemberatung GmbH VirtualBox Guest Service [80ee:cafe]
$ lspci -nn | grep VMware 00:07.7 System peripheral [0880]: VMware Inc Virtual Machine Communication Interface [15ad:0740] (rev 10) 00:0f.0 VGA compatible controller [0300]: VMware Inc Abstract SVGA II Adapter [15ad:0405] 00:11.0 PCI bridge [0604]: VMware Inc PCI bridge [15ad:0790] (rev 02) 00:15.0 PCI bridge [0604]: VMware Inc Device [15ad:07a0] (rev 01) [...cut 31 more instances of pci device 15ad:07a0...] 02:02.0 USB Controller [0c03]: VMware Inc Abstract USB2 EHCI Controller [15ad:0770]

However, this suffers from some of the same problem as the MAC OUI:

  1. It would be a never-ending game of chasing down new or updated virtualization products to keep track of PCI identifiers.
  2. Some virtualization software may allow devices, their names, or their IDs to be user-selectable or user-editable.

This method of virtual environment detection is discouraged.

Drivers

Similar to PCI devices, looking at the list of installed device drivers depends both on the device services offered by the virtualization software, and possibly also on optional installation steps ("VMWare Tools", or "VirtualBox Guest Additions") that are not guaranteed to have taken place.

$ lsmod | grep vbox vboxvfs 35616 0 vboxvideo 3040 1 drm 193856 2 vboxvideo vboxguest 170956 8 vboxvfs

This method of virtual environment detection is strongly discouraged.

Segmentation Faults with SIDT ("Red Pill") and SGDT ("No Pill")

Almost all of the sample code I found on the web that attempted to demonstrate how to use Red Pill and No Pill actually had one or both of the following problems:

  1. The SIDT and SGDT CPU instructions expect to write some data into a memory location. A large amount of the sample code I found allocated 2 bytes on the stack, typically with char[2]. The problem is that at the very least, when running in i386, both of these CPU instructions require 6 bytes, not 2 bytes. The lower 2 bytes is the table limit, while the upper 4 is the base IDTR or GDTR address.
  2. the SIDT and SGDT CPU instructions need 6 bytes in i386 mode, but in my case I'm running in AMD64. Looking up these instructions in AMD's documents [1, 2, pages 299-300] you'll note that in AMD64 you need 10 bytes of memory, not 6 bytes. So at the very least, your code will need some sort of #if statements to detect at compile-time how to allocation or interpret the results of SIDT and SGDT.

Lastly, something else worth mentioning is that some example code of the SLDT detection method incorrectly attempts to handle the output the same way as it is done with SIDT and SGDT, as a 48-bit size+base address structure. Instead, please note that SLDT returns a single 16-bit selector regardless of whether running in i386 or AMD64. For details, see page 303 of the AMD documentation.

Additional documents to which I have not already linked

Last modified: 2010-01-04
Stéphane Charette, stephanecharette@gmail.com
../