CPU H FileSystem Registry Generic OS Queries Global OS object UI artifacts OS Features Processes Network CPU Hardware Firmware tables Hooks Timing WMI Human-like behavior macOS
Techniques in this group use specific processor instructions to either get particular information about CPU — or execute predefined instruction sequence which behaves differently in usual host OS and in virtual environment.
The CPUID instruction is an instruction that returns process identification and feature information to EBX, ECX, EDX . The information received to these registers can be used to identify a vendor.
Code sample
__declspec ( naked ) void get_cpuid_vendor ( char * vendor_id ) {
__asm {
; save non - volatile register
push ebx
; nullify output registers
xor ebx , ebx
xor ecx , ecx
xor edx , edx
; call cpuid with argument in EAX
mov eax , 0x40000000
cpuid
; store vendor_id ptr to destination
mov edi , vendor_id
; move string parts to destination
mov eax , ebx ; part 1 of 3 from EBX
stosd
mov eax , ecx ; part 2 of 3 from ECX
stosd
mov eax , edx ; part 3 of 3 from EDX
stosd
; restore saved non - volatile register
pop ebx
; return from function
retn
}
}
Detections table
Check vendor ID string via CPUID instruction - returned in parts in EBX, ECX, EDX: Detect EAX as argument to CPUID String FreeBSD HV 0x40000000 bhyve bhyve Hyper-V 0x40000000 Microsoft Hv KVM 0x40000000 KVMKVMKVM Parallels 0x40000000 prl hyperv VirtualBox 0x40000000 VBoxVBoxVBox VirtualPC 0x40000000 Microsoft Hv VMWare 0x40000000 VMwareVMware Xen 0x40000000 XenVMMXenVMM
An other way to detect if the program is being run in hypervisor is using the CPUID instruction in an other way. Instead of setting EAX (the argument to CPUID ) to be 0x40000000 , EAX is set to 1. When EAX is set to 1, the 31st bit in ECX (CPUID ’s returned value) is set, it indicates that the program is being run in Hypervisor.
Code sample (function GetAdaptersAddresses )
__declspec ( naked ) bool is_run_in_hypervisor () {
__asm {
; nullify output register
xor ecx , ecx
; call cpuid with argument in EAX
mov eax , 1
cpuid
; set CF equal to 31 st bit in ECX
bt ecx , 31
; set AL to the value of CF
setc al
; return from function
retn
}
}
Detections table
Check if being run in Hypervisor (via CPUID) Detect EAX as argument to CPUID Check of return value Hypervisor 1 31st bit in ECX - set if run in Hypervisor
This technique doesn’t work on latest VMware releases (all Windows releases affected). However, it is described here for the sake of completeness.
This trick involves looking at the pointers to critical operating system tables that are typically relocated on a virtual machine. It’s what called “Red Pill” and was first introduced by Joanna Rutkowska. There is one Local Descriptor Table Register (LDTR), one Global Descriptor Table Register (GDTR), and one Interrupt Descriptor Table Register (IDTR) per CPU. They have to be moved to a different location when a guest operating system is running to avoid conflicts with the host. On real machines the IDT, for example, is located lower in memory than it is on guest (i.e., virtual) machines.
Code sample
idt_vm_detect = (( get_idt_base () >> 24 ) == 0xff );
ldt_vm_detect = ( get_ldt_base () == 0xdead0000 );
gdt_vm_detect = (( get_gdt_base >> 24 ) == 0xff );
// sidt instruction stores the contents of the IDT Register
// (the IDTR which points to the IDT) in a processor register.
ULONG get_idt_base () {
UCHAR idtr [ 6 ];
#if defined (ENV32BIT)
_asm sidt idtr
#endif
return * (( unsigned long * ) & idtr [ 2 ]);
}
// sldt instruction stores the contents of the LDT Register
// (the LDTR which points to the LDT) in a processor register.
ULONG get_ldt_base () {
UCHAR ldtr [ 5 ] = " \xef\xbe\xad\xde " ;
#if defined (ENV32BIT)
_asm sldt ldtr
#endif
return * (( unsigned long * ) & ldtr [ 0 ]);
}
// sgdt instruction stores the contents of the GDT Register
// (the GDTR which points to the GDT) in a processor register.
ULONG get_gdt_base () {
UCHAR gdtr [ 6 ];
#if defined (ENV32BIT)
_asm sgdt gdtr
#endif
return gdt = * (( unsigned long * ) & gdtr [ 2 ]);
}
Credits for this code sample: al-khaser project
This technique is described by this link (slide #37). MMX instructions may be used as random instructions by malware. Sometimes such subsets of CPU instruction are not supported by emulators and thus exception is thrown instead of performing analysis.
Example:
The malware executes illegal instructions, which should generate exception on the real CPU but are executed normally - or in some different way - in virtual environment. Information about CPU exceptions is provided by this link .
Code sample (variant 1, generating #ud exception)
push ebx
xor ebx, ebx
mov eax, 1
; the following 4 bytes below generate #ud exception
db 0x0F
db 0x3F
db 0x0D
db 0x00
test ebx, ebx
setz al
pop ebx
It should be emphasized that there are more than 1,000 combinations of
0x0F
0x3F
0xXX
0xYY
bytes that may be used by malware in order to detect VirtualPC enviroment.
Code sample (variant 2, executing illegal STI instruction)
// Taken here: https://pastebin.com/Nsv5B1yk
// http://waleedassar.blogspot.com
// http://www.twitter.com/waleedassar
// Use this code to detect if Windows XP is running inside Virtual PC 2007
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#define CONTEXT_ALL 0x1003F
int dummy ( int );
unsigned long gf = 0 ;
int __cdecl Handler ( EXCEPTION_RECORD * pRec , void * est , unsigned char * pContext , void * disp )
{
if ( pRec -> ExceptionCode == 0xC0000096 ) //Privileged instruction
{
//---------------------Installing the trick--------------------------------------
* ( unsigned long * )( pContext ) = CONTEXT_ALL ; /*CONTEXT_DEBUG_REGISTERS|CONTEXT_FULL*/
* ( unsigned long * )( pContext + 0x4 ) = ( unsigned long )( & dummy );
* ( unsigned long * )( pContext + 0x8 ) = ( unsigned long )( & dummy );
* ( unsigned long * )( pContext + 0xC ) = ( unsigned long )( & dummy );
* ( unsigned long * )( pContext + 0x10 ) = ( unsigned long )( & dummy );
* ( unsigned long * )( pContext + 0x14 ) = 0 ;
* ( unsigned long * )( pContext + 0x18 ) = 0x155 ; //Enable the four DRx On-Execute
//---------------------------------------------------------------------------------
( * ( unsigned long * )( pContext + 0xB8 )) ++ ;
return ExceptionContinueExecution ;
}
else if ( pRec -> ExceptionCode == EXCEPTION_SINGLE_STEP )
{
if ( gf == 1 )
{
MessageBox ( 0 , "Expected behavior (XP)" , "waliedassar" , 0 );
ExitProcess ( 0 );
}
gf ++ ;
( * ( unsigned long * )( pContext + 0xC0 )) |= 0x00010000 ; //Set the RF (Resume Flag)
return ExceptionContinueExecution ;
}
return ExceptionContinueSearch ;
}
int dummy ( int x )
{
x += 0x100 ;
return x ;
}
int main ( int shitArg )
{
unsigned long ver_ = GetVersion ();
unsigned long major = ver_ & 0xFF ;
unsigned long minor = ( ver_ >> 0x8 ) & 0xFF ;
if ( major == 0x05 & minor == 0x01 ) //Windows XP
{
unsigned long x = 0 ;
__asm
{
push offset Handler
push dword ptr fs : [ 0x0 ]
mov dword ptr fs : [ 0x0 ], esp
STI ; Triggers an exception ( privileged instruction )
}
dummy ( 0xFF );
__asm
{
pop dword ptr fs : [ 0x0 ]
pop ebx
}
MessageBox ( 0 , "Virtual PC 2007 detected (XP)" , "waliedassar" , 0 );
}
return 0 ;
}
Code sample (variant 3, resetting VirtualPC)
// Taken here: https://pastebin.com/exAK5XQx
// http://waleedassar.blogspot.com (@waleedassar)
// Executing "\x0F\xC7\xC8\x05\x00" in VirtualPC 2007 triggers a reset error.
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
bool flag = false ;
int __cdecl Handler ( EXCEPTION_RECORD * pRec , void * est , unsigned char * pContext , void * disp )
{
if ( pRec -> ExceptionCode == 0xC000001D || pRec -> ExceptionCode == 0xC000001E || pRec -> ExceptionCode == 0xC0000005 )
{
flag = true ;
( * ( unsigned long * )( pContext + 0xB8 )) += 5 ;
return ExceptionContinueExecution ;
}
return ExceptionContinueSearch ;
}
int main ( int argc , char * argv [])
{
__asm
{
push offset Handler
push dword ptr fs : [ 0x0 ]
mov dword ptr fs : [ 0x0 ], esp
}
flag = false ;
__asm
{
__emit 0x0F
__emit 0xC7
__emit 0xC8
__emit 0x05
__emit 0x00
}
if ( flag == false )
{
MessageBox ( 0 , "VirtualPC detected" , "waliedassar" , 0 );
}
__asm
{
pop dword ptr fs : [ 0x0 ]
pop eax
}
return 0 ;
}
This article explains why backdoor port communication is used in VMware in the first place.
Code sample (variant 1)
bool VMWare :: CheckHypervisorPort () const {
bool is_vm = false ;
__try {
__asm {
push edx
push ecx
push ebx
mov eax , ' VMXh '
mov ebx , 0
mov ecx , 10
mov edx , ' VX '
in eax , dx // <- key point is here
cmp ebx , ' VMXh '
setz [ is_vm ]
pop ebx
pop ecx
pop edx
}
}
__except ( EXCEPTION_EXECUTE_HANDLER ) {
is_vm = false ;
}
return is_vm ;
}
Code sample (variant 2)
bool VMWare :: CheckHypervisorPortEnum () const {
bool is_vm = false ;
short ioports [] = { ' VX ' , ' VY ' };
short ioport ;
for ( short i = 0 ; i < _countof ( ioports ); ++ i ) {
ioport = ioports [ i ];
for ( unsigned char cmd = 0 ; cmd < 0x2c ; ++ cmd ) {
__try {
__asm {
push eax
push ebx
push ecx
push edx
mov eax , ' VMXh '
movzx ecx , cmd
mov dx , ioport
in eax , dx // <- key point is here
pop edx
pop ecx
pop ebx
pop eax
}
is_vm = true ;
break ;
}
__except ( EXCEPTION_EXECUTE_HANDLER ) {}
}
if ( is_vm )
break ;
}
return is_vm ;
}
No signature recommendations are provided for this evasion group as it’s hard to track such a code being executed.
Patch hypervisor. If it proves impossible — due to license issues or something else — patch VM config. Usually undocumented options help.
vs CPUID instruction: refer to this article for the example of such a patch vs IN instruction (VMware backdoor): take a look at these config changes
Credits go to open-source project from where code samples were taken and to independent researcher who shared his findings:
Though Check Point tool InviZzzible has them all implemented, due to modular structure of the code it would require more space to show a code sample from this tool for the same purposes. That’s why we’ve decided to use other great open-source projects for examples throughout the encyclopedia.