Go back

Contents

CPU detection methods used
1. Check vendor ID string via CPUID instruction
2. Check if being run in Hypervisor via CPUID instruction
3. Check for global tables location: IDT/GDT/LDT
4. Using exotic instructions to fool virtual emulators
5. Detecting environment via execution of illegal instructions (VirtualPC only)
6. Detecting environment via IN instruction - backdoor port (VMware only)
Signature recommendations
Countermeasures
Credits


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 processor 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


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 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 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)
Detect EAX as argument to CPUID Check of return value
Hypervisor 1 31st 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.

  • 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

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.


Go back