windowswindows-10virtual-machinereverse-engineeringvirtualization

How does Windows 10 task manager detect a virtual machine?


The Windows 10 task manager (taskmgr.exe) knows if it is running on a physical or virtual machine.

If you look in the Performance tab you'll notice that the number of processors label either reads Logical processors: or Virtual processors:.

In addition, if running inside a virtual machine, there is also the label Virtual machine: Yes.

See the following two screen shots:

taskmgr locical processors

taskmgr virtual processors

My question is if there is a documented API call taskmgr is using to make this kind of detection?

I had a very short look at the disassembly and it seems that the detection code is somehow related to GetLogicalProcessorInformationEx and/or IsProcessorFeaturePresent and/or NtQuerySystemInformation.

However, I don't see how (at least not without spending some more hours of analyzing the assembly code).

And: This question is IMO not related to other existing questions like How can I detect if my program is running inside a virtual machine? since I did not see any code trying to compare smbios table strings or cpu vendor strings with existing known strings typical for hypervisors ("qemu", "virtualbox", "vmware"). I'm not ruling out that a lower level API implementation does that but I don't see this kind of code in taskmgr.exe.

Update: I can also rule out that taskmgr.exe is using the CPUID instruction (with EAX=1 and checking the hypervisor bit 31 in ECX) to detect a matrix.

Update: A closer look at the disassembly showed that there is indeed a check for bit 31, just not done that obviously.

I'll answer this question myself below.


Solution

  • I've analyzed the x64 taskmgr.exe from Windows 10 1803 (OS Build 17134.165) by tracing back the writes to the memory location that is consulted at the point where the Virtual machine: Yes label is set.

    Responsible for that variable's value is the return code of the function WdcMemoryMonitor::CheckVirtualStatus

    Here is the disassembly of the first use of the cpuid instruction in this function:

    lea     eax, [rdi+1]                 // results in eax set to 1
    cpuid
    mov     dword ptr [rbp+var_2C], ebx  // save CPUID feature bits for later use
    test    ecx, ecx
    jns     short loc_7FF61E3892DA       // negative value check equals check for bit 31
    ...
    return 1
    loc_7FF61E3892DA:
    // different feature detection code if hypervisor bit is not set
    

    So taskmgr is not using any hardware strings, mac addresses or some other sophisticated technologies but simply checks if the hypervisor bit (CPUID leaf 0x01 ECX bit 31)) is set.

    The result is bogus of course since e.g. adding -hypervisor to qemu's cpu parameter disables the hypervisor cpuid flag which results in task manager not showing Virtual machine: yes anymore.

    And finally here is some example code (tested on Windows and Linux) that perfectly mimics Windows task manager's test:

    #include <stdio.h>
    
    #ifdef _WIN32
    #include <intrin.h>
    #else
    #include <cpuid.h>
    #endif
    
    int isHypervisor(void)
    {
    #ifdef _WIN32
        int cpuinfo[4];
        __cpuid(cpuinfo, 1);
        if (cpuinfo[2] >> 31 & 1)
            return 1;
    #else
        unsigned int eax, ebx, ecx, edx;
        __get_cpuid (1, &eax, &ebx, &ecx, &edx);
        if (ecx >> 31 & 1)
            return 1;
    #endif
        return 0;
    }
    
    int main(int argc, char **argv)
    {
        if (isHypervisor())
            printf("Virtual machine: yes\n");
        else
            printf("Virtual machine: no\n"); /* actually "maybe */
    
        return 0;
    }