Just Another VM Detection (was "VM Detection Combo")
http://my.opera.com/jaelanicu/blog/just-another-vm-detection-was-vm-detection-combo
Tuesday, 18. August 2009, 16:41:46
[edited article follows]
Here it is. Some of the long overdue result of the research for the remaining VM softwares I mentioned a year ago ( here ). But first of all, I would like to note that this result is several months old since now I have PC upgraded and use newer software versions. I would also like to apologize and correct the "Red Pill" method variant "Scoopy". In the previous blog, I misspelled its name as "Scooby". My bad... The Scooby Doo's picture in the "On the Cutting Edge: Thwarting Virtual Machine Detection" white paper sure made me do the mistake. The cartoon image shouldn't be there in the first place. The white paper is a serious resource and the picture makes "Scoopy" a triffling matter. Not to mention that those pictures are copyrighted and the paper's author don't mention their copyright notice. Nuff said... Let's go back to the main topic.
To make things simple, have a look at the table below. The bright red colored rows of the "VM Software" column indicates that the VM softwares in that configuration do not run at optimum speed or in compatibility mode. Please keep in mind that my standard for VM detection is that it must be runable under Microsoft Windows 9x/2000 and above in USER mode (ring 3) ONLY and does not require any third party software including device driver. Administrator privilege requirement is acceptable, but should be avoided. Safe mode Windows environment is fine with me. DOS based VM detection is acceptable (runnable in a DOS box) but is not the preferred method. Above all, the method should be able to detect at least the release build of the VM software. Detecting only a debug build of VM softwares are not acceptable since debug build are not available to all end-users.
VM Software | Backdoor | Long Opcode | TF+CF Bug |
Bochs | No | No | Yes |
Parallels (normal/high accel.) | Yes | Yes | No |
Parallels (no accel.) | Yes | Yes | Yes |
Qemu (with KQEMU) | No | Yes | No |
Qemu (no KQEMU) | No | Yes | Yes |
VirtualBox | No | No | No |
Virtual PC | Yes | Yes | No |
VMware (with directexec) | Yes | No | No |
VMware (no directexec) | Yes | No | Yes |
VM Software Versions Tested:
1. Bochs x86 2.3.6 (December 24, 2007) and x86-64 2.2.6 (January 29, 2006).
2. Parallels Workstation 2.2 build 2112.
3. Qemu 0.9.1 with and without KQEMU 1.3.0pre11. QVM86 is not tested since it's obsolete.
4. innotek VirtualBox 1.5.2.
5. Microsoft Virtual PC 2007 6.0.156.0.
Guest Operating Systems Used With Testings:
1. Microsoft Windows 98 SE.
2. Microsoft Windows XP SP2 32-Bit.
Host System Configuration Used With Testings:
1. Iwill P4S with Intel 845 chipset.
2. Intel Pentium 4 1.6GHz Willamette.
3. 512MB of SDRAM.
Detection Methods
1 | . Backdoor Detection |
Most backdoor detections are based from what I gathered from the internet except for the Parallels backdoor. The "Attacks on More Virtual Machine Emulators" white paper does include Parallels backdoor detection method, but it requires authentication code, or the paper doesn't provide sufficient information to create a working code. The Parallels backdoor I used here is based from reverse engineering the Parallels VM Tools and it's the first one that is used by the VM Tools. As far as I can understand, it retrieves the build number of the Parallels software. The backdoor uses port based method to communicate with the host system - the Parallels software. Take a look at below Delphi function code for the backdoors.
function ParallelsBackdoorCheck(var BuildNumber: integer): boolean;
begin
result:= false;
try
asm
push ebx
push esi
mov eax, $13 //function code?
mov ebx, $3141 //magic number
mov esi, 1 //sub function code?
mov dx, $e4
in eax, dx
cmp eax, $3141 //make sure eax is set to magic number
jne @done
cmp ebx, $3141 //make sure ebx is changed
je @done //i assume build number 12609 will not exists
or ebx, ebx //make sure build number is non-zero
setnz @result
mov eax, BuildNumber
mov [eax], ebx
@done:
pop esi
pop ebx
end;
except //not in VM if exception occured
end;
end;
Since I use Parallels 2.2 build number 2112, the above function will returns TRUE and the BuildNumber will be set to 2112. The above function will work regardless of the VM acceleration setting. There should be more backdoor functions that use port access, but I'm not interested in them since I already got what it needs to detect Parallels VM.
For Virtual PC backdoor detection code, I use a two part detection code in a single function. One with the invalid opcode bytes 0F,3F,07,0B and one with the 0F,C7,C8,01,00 opcode. If any one of the part detects Virtual PC VM, the function will returns TRUE.
function VirtualPCBackdoorCheck: boolean;
begin
try
asm
push ebx
mov eax, 1
xor ebx, ebx //zero or non-zero?
db $0f,$3f,$07,$0b
test ebx, ebx
setz @result
pop ebx
end;
except result:= false;
end;
if result then exit;
try
asm
push ebx
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
db $0f,$c7,$c8,$01,$00
pop ebx
end;
result:= true;
except result:= false;
end;
end;
For VMware backdoor detection, I use the one I mentioned in my previous blog. That is the "GetMemSize" backdoor function since it shouldn't be disabled, otherwise the BIOS won't be able to load the bootstrap code from the bootable media (the media indicator won't even blink).
function VMwareBackdoorCheck: boolean;
const
VMWARE_MAGIC = $564d5868; //"VMXh"
VMWARE_PORT_NUMBER = $5658; //"VX"
VMWARE_CMD_GETMEMSIZE= $14;
begin
result:= false;
try
asm
push ebx
mov eax, VMWARE_MAGIC
xor ebx, ebx //zero parameter
mov ecx, VMWARE_CMD_GETMEMSIZE
mov dx, VMWARE_PORT_NUMBER
in eax, dx
pop ebx
cmp Win32Platform, VER_PLATFORM_WIN32_NT
je @winnt
cmp eax, 16 //16mb minimum, else os won't run
jl @done
cmp eax, 4096 //1024mb maximum, else os won't run
jg @done //4096mb maximum with unofficial patch
jmp @detected
@winnt:
cmp eax, 32 //32mb minimum, else os won't run
jl @done
cmp eax, 2048 // 2tb maximum statistically
jg @done //32tb maximum theorically
@detected:
mov @result, 1
@done:
end;
except
end;
end;
As for VirtualBox backdoor... I have to say that I was frustated when trying to find its backdoor. It's difficult since VirtualBox Guest Additions doesn't complain when installed and run in a real hardware or under non VirtualBox VMs AND VirtualBox installation won't complain when installed and run under its own VM (VirtualBox in a VirtualBox). At this point I gave up finding it. One of anonymous person pointed out that I can use the SYSENTER instruction for the backdoor. I'm yet to research this and don't know yet if the method conforms to my standard of VM detection.
Other remaining VM softwares - which are Bochs and Qemu, don't seem to have any backdoor at all. However, for Qemu, as far as I know, there can be a backdoor, but only if it's a debug build. This conclusion is supported by the fact that these two don't have any guest addition nor guest VM tool, so I guess that they don't have any backdoor yet. But I'm sure they'll have one when they are matured enough.
2 . Long Opcode Instruction Limit DetectionThis method is more like a CPU compatibility check rather than a VM detection. This method is about checking the CPU limitation against executing a CPU instruction which has long opcode bytes. On Intel x86 compatible CPUs, the maximum opcode bytes for a single CPU instruction is 15 - that is 15 bytes per instruction. However, several VM softwares don't have this limitation, thus its virtual CPU is not 100% compatible even though the unlimited opcode length per instruction seems to be an advantage. When a real CPU executes an instruction whose length is more than 15 bytes, it will raise an exception, but under VMs that don't have this limit will not raise an exception. For this method, I also use two parts of code. One with a 16 bytes instruction and one with a 20 bytes instruction. When any one of them doesn't raise an exception, then it's run under a VM.
function GenericLongOpcodeCheck: boolean;3 . Trap & Carry Flags Bug Detection
begin
try
asm //uses 11 DS segment prefix opcodes. 16-bytes opcode
db $3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e
mov eax, exitcode
end;
result:= true;
except
on eaccessviolation do result:= false;
else result:= true;
end;
if not result then exit;
try
asm //uses 15 DS segment prefix opcodes. 20-bytes opcode
db $3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e,$3e
mov eax, exitcode
end;
result:= true;
except
on eexternal do result:= false;
else result:= true;
end;
end;
The Trap & Carry Flags bug detection that I use here is a ported version of the "Bochs CMPS Demo" code taken from the "Attacks on More Virtual Machine Emulators" white paper. I was forced to port it to 32-bit Windows code since the original one is for DOS platform. Since the method is simple and efficient, there is no need for significant modification.
function GenericTfCfBugCheck: boolean;4 . BIOS Pattern Detection
begin
try
asm
mov esi, offset GenericTfCfBugCheck
mov edi, esi
mov ecx, 2
pushfd
or word ptr [esp], $101
popfd
repe cmpsb
end;
result:= false;
except
asm
mov eax, [esp+$c] //get os exception pointer (pexceptionrecord)
mov ecx, [eax+$10] //get number of parameters
lea eax, [ecx*4+eax+$14] //advance to context record
mov eax, _CONTEXT([eax]).eflags //get eflags
and eax, 1 //filter cf
xor eax, 1 //invert cf
mov @result, al //save as result. TRUE if CF is zero
end;
end;
end;
After I completed the above five detection methods, the combination of backdoor and IDTR & LDTR modification detections can detect all but two cases. They are Bochs detection and Qemu (without KQEMU). These two softwares don't have any backdoor and don't modify the IDTR. For the sake for a complete VM software detection and after some considerations, I came out with a BIOS pattern detection method. This method is simply a text finder that scans the BIOS at memory address 0xF0000 to 0xEFFE0 for a specific text in length of 11 to 32 characters. The text that will berecognized by this detection are listed below.
VM Software | Text Pattern (No Quotes) | Length In Characters |
Bochs | "Bochs BIOS " | 11 |
Parallels | "Parallels Software International" | 32 |
Qemu | "QEMU BIOS " | 10 |
VirtualBox | "innotek VirtualBox BIOS" | 23 |
Virtual PC | "Virtual Machine" | 15 |
VMware | "VMware, Inc." | 12 |
Since this method requires access to the BIOS memory, I make use of the PhysicalMemory device which I hate to say that it requires the administrator privilege. You can get a C++ source code example here . Other alternative method used is with the help of NTVDM to read the BIOS memory. This is possible since NTVDM maps (and/or creates copies of) the lowest 1 MB of physical memory. All I have to do is to execute a DOS program (the COMMAND.COM in this case), use the ReadProcessMemory Windows API, then terminate the process.
I kinda dislike this BIOS pattern detection method since it's a lame detection and requires special privilege, so hopefully I'll be able to have other method that doesn't need special privilege. If anyone have a better idea, I'll be very interested on hearing about it. But please note that I'm not interested on information that directly or indirectly came from the Windows registry nor DMI.
Future Plans
There are still other VM softwares and/or versions which I don't have in my system yet. A couple of those are the latest VirtualBox 1.5.6 and Virtual Simics. Also in my list are all VM softwares that require hardware assisted virtualization such as Virtual Iron (at least when I'm able to upgrade my 5 years old Pentium 4 to AMD64 X2 or Core2 Duo). All other VM softwares that don't provide full VM (full system virtualization) such as SandBox are not and will not be in my list. Other things I might do when I finally have a 64-bit dual core system is to research on VM softwares that supports 64-bit platform (except for Bochs x86-64, since Bochs is an emulator rather than virtualizator).