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


CPU detection methods used

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.


 

1. Check vendor ID string via CPUID instruction

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:
DetectEAX as argument to CPUIDString
FreeBSD HV0x40000000bhyve bhyve
Hyper-V0x40000000Microsoft Hv
KVM0x40000000KVMKVMKVM
Parallels0x40000000prl hyperv
VirtualBox0x40000000VBoxVBoxVBox
VirtualPC0x40000000Microsoft Hv
VMWare0x40000000VMwareVMware
Xen0x40000000XenVMMXenVMM


 

2. Check if being run in Hypervisor via CPUID instruction

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 0x40000000EAX 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 31st 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)
DetectEAX as argument to CPUIDCheck of return value
Hypervisor131st bit in ECX - set if run in Hypervisor


 

3. Check for global tables location: IDT/GDT/LDT

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


 

4. Using exotic instructions to fool virtual emulators

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:

 


 

5. Detecting environment via execution of illegal instructions (VirtualPC only)

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;
}


 

6. Detecting environment via IN instruction - backdoor port (VMware only)

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;
}


 

Signature recommendations

No signature recommendations are provided for this evasion group as it’s hard to track such a code being executed.


 

Countermeasures

Patch hypervisor. If it proves impossible — due to license issues or something else — patch VM config. Usually undocumented options help.


 

Credits

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.