第一次嘗試惡意代碼分析就遇到了虛擬機檢測,于是就想著先學習一下檢測的技術然后再嘗試繞過。學習后最終發現,似乎最好的方法不應該是去patch所有檢測方法,而是直接調試并定位檢測函數再繞過。但既然已經研究了兩天,索性將收集到的資料整理一下,方便后人查找。

一、基于特征的檢測

1.1 文件以及目錄

相關 API :GetFileAttributesetModuleHandle

通常,各種 hypervisor 會在虛擬機上安裝驅動程序和其他軟件,以使客戶機能夠利用 hypervisor 提供的功能。惡意軟件可以搜索這些文件、目錄或進程的存在。VMware 虛擬機中可能會有如下的文件列表:

C:\Program Files\VMware\
C:\Windows\System32\vm3dc003.dll
C:\Windows\System32\vm3ddevapi64-debug.dll
C:\Windows\System32\vm3ddevapi64-release.dll
C:\Windows\System32\vm3ddevapi64-stats.dll
C:\Windows\System32\vm3ddevapi64.dll
C:\Windows\System32\vm3dgl64.dll
C:\Windows\System32\vm3dglhelper64.dll
C:\Windows\System32\vm3dservice.exe
C:\Windows\System32\vm3dum64-debug.dll
C:\Windows\System32\vm3dum64-stats.dll
C:\Windows\System32\vm3dum64.dll
C:\Windows\System32\vm3dum64_10-debug.dll
C:\Windows\System32\vm3dum64_10-stats.dll
C:\Windows\System32\vm3dum64_10.dll
C:\Windows\System32\vm3dum64_loader.dll
C:\Windows\System32\vmGuestLib.dll
C:\Windows\System32\vmGuestLibJava.dll
C:\Windows\System32\vmhgfs.dll
C:\Windows\System32\VMWSU.DLL
C:\Windows\System32\vsocklib.dll
C:\Windows\System32\drivers\vm3dmp.sys
C:\Windows\System32\drivers\vm3dmp_loader.sys
C:\Windows\System32\drivers\vm3dmp-debug.sys
C:\Windows\System32\drivers\vm3dmp-stats.sys
C:\Windows\System32\drivers\vmnet.sys
C:\Windows\System32\drivers\vmmouse.sys
C:\Windows\System32\drivers\vmusb.sys
C:\Windows\System32\drivers\vmci.sys
C:\Windows\System32\drivers\vmhgfs.sys
C:\Windows\System32\drivers\vmmemctl.sys
C:\Windows\System32\drivers\vmx86.sys
C:\Windows\System32\drivers\vmrawdsk.sys
C:\Windows\System32\drivers\vmusbmouse.sys
C:\Windows\System32\drivers\vmkdb.sys
C:\Windows\System32\drivers\vmnetuserif.sys
C:\Windows\System32\drivers\vmnetadapter.sys

1.2 Devices

相關API:CreateFile

在Windows虛擬機中,虛擬機通常擁有獨特命名的 “devices”。

\\.\HGFS,主機和虛擬機之間共享的文件系統

\\.\vmci,虛擬機和主機之間的通信管道

1.3 MAC 地址

相關API:GetAdaptersAddressesGetAdaptersInfoNetbios

Hypervisors 通常為虛擬機提供虛擬化的網卡(NICs),具有獨占于hypervisor供應商的MAC地址。

已知的 VMware 虛擬機的 MAC地址有:

◆00:50:56:XX:XX:XX

◆00:1C:14:XX:XX:XX

◆00:0C:29:XX:XX:XX

◆00:05:69:XX:XX:XX

1.4 BIOS 字符串

相關API:RegOpenKeyExRegQueryValueEx

虛擬化產品通常會附帶一個基本輸入/輸出系統(BIOS),其中包含虛擬機特有的字符串。可以通過檢查Windows注冊表來查看。

HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System中鍵值 “SystemBiosVersion” 包含 “VMware, Inc.”

HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\BIOS中包含鍵值

  • “BIOSVendor” 為 “VMware, Inc.”
  • “BIOSVersion” 的起始字符串為 “VM.”
  • “SystemManufacturer” 為 “VMware, Inc.”
  • “SystemProductName” 包含 “VMware.”


HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\SystemInformation中也有類似的信息

1.5 其他 Windows 注冊表項

VMware中,以下注冊表項會帶有 “VMWARE” 或者 “VM” 字符串

HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0
HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0
HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 2\Scsi Bus 0\Target Id 0\Logical Unit Id 0
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Disk\Enum
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum\IDE
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum\SCSI

1.6 SMBIOS 字符串

相關API:GetSystemFirmwareTable

System Management BIOS 是由BIOS讀取的一組數據結構。虛擬機會帶有有特殊的字符串。VMware 虛擬機會帶有 “VMware”。

1.7 ACPI 字符串

相關API:GetSystemFirmwareTable

Advanced Configuration and Power Interface (ACPI) 是操作系統通過 BIOS 或 UEFI 與各種硬件組件進行通信的接口。VMware 虛擬機會帶有 “VMware”。

1.8 進程

相關API:CreateToolhelp32SnapshotProcess32FirstProcess32Next

一些虛擬機利會有特殊的后臺進程。這些進程主要提供一些方便的功能,例如自動配置屏幕分辨率和配置網絡設置等。

VMware虛擬機中可能會有以下進程:

◆Vmtoolsd.exe

◆Vmwaretray.exe

◆Vmwareuser.exe

◆VGAuthService.exe

◆Vmacthlp.exec

有些惡意軟件還會通過進程數來檢測虛擬環境,比如 ”EvilBunny“ 會檢測是否存在至少15個進程。

1.9 硬盤硬件ID以及名稱

相關API:SetupDiGetClassDevsSetupDiGetDeviceRegistryProperty

Win32/Winwebsec的一些變種使用了一種通過 Setup API來檢查硬盤設備名的方法。類似的也可以檢測硬盤硬件ID。

1.10 通過 WMI 獲取硬件信息

Windows Management Instrumentation(WMI),提供了一種標準化的接口,允許程序獲取系統信息并執行管理任務。WMI是基于COM(Component Object Model)技術構建的,并使用WQL(WMI查詢語言)來查詢數據。

◆可以通過 WMI 獲取 BIOS 序列號并查看是否帶有類似“VMWARE”的字符串。類似的還可以檢測 Win32_ComputerSystem 下的 Model 以及 Manufacturer 字段。

VOID CheckBIOSSerialNumberWMI(){  IWbemServices* pSvc = NULL;  IWbemLocator* pLoc = NULL;  IEnumWbemClassObject* pEnumerator = NULL;  BOOL bFound = FALSE;  HRESULT hRes;   // Init WMI  if (!InitWMI(&pSvc, &pLoc, _T("ROOT\\CIMV2")))      return;   // If success, execute the desired query  if (!ExecWMIQuery(&pSvc, &pLoc, &pEnumerator, _T("SELECT * FROM Win32_BIOS")))      return;   // Get the data from the query  IWbemClassObject* pclsObj = NULL;  ULONG uReturn = 0;  VARIANT vtProp;   while (pEnumerator)  {      hRes = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);      if (0 == uReturn)          break;       // Get the value of the Name property      VariantInit(&vtProp);      hRes = pclsObj->Get(_T("SerialNumber"), 0, &vtProp, 0, 0);      if (SUCCEEDED(hRes)) {          if (vtProp.vt == VT_BSTR) {              if (StrStrI(vtProp.bstrVal, _T("VMWare")) != 0)              {                  bFound = TRUE;                  VariantClear(&vtProp);                  pclsObj->Release();                  break;              }          }          VariantClear(&vtProp);      }       // release the current result object      pclsObj->Release();  }   // Cleanup  pSvc->Release();  pLoc->Release();  pEnumerator->Release();  CoUninitialize();   PrintResult(bFound, _T("Checking SerialNumber from BIOS using WMI"));}

◆可以通過返回結果的條目數,檢查是否存在一些物理機應該有的硬件,比如:

SELECT * FROM Win32_Fan // 風扇SELECT * FROM Win32_CacheMemory //SELECT * FROM Win32_VoltageProbe // 電壓探針SELECT * FROM Win32_PerfFormattedData_Counters_ThermalZoneInformation // 熱區信息SELECT * FROM CIM_Memory // 物理內存SELECT * FROM CIM_Sensor // 傳感器設備SELECT * FROM CIM_NumericSensor // 數字傳感器SELECT * FROM CIM_TemperatureSensor // 溫度傳感器SELECT * FROM CIM_VoltageSensor // 電壓傳感器

二、基于指令的檢測

某些CPU指令在虛擬機中執行時返回值與在物理機機上執行時返回值不同。惡意軟件可以執行這些指令,并嘗試根據返回值推斷虛擬機的存在。雖然這些檢測方法不普遍適用于所有虛擬機,且隨著虛擬機的迭代也已經部分失效,但仍然有必要了解。下面列出已知的用于虛擬機檢測的指令。

2.1 IN

IN指令從指定的I/O端口讀取值到指定的內存地址。I/O端口是操作系統和I/O設備(如磁盤控制器和聲音設備)之間的通信手段。基于VMware的虛擬機具有端口號為 0x5658 的I/O設備,允許hypervisor 和虛擬機之間通信。通常,在非特權用戶模式下運行IN指令將觸發特權指令異常。但在 VMware 中,不會引發此類異常。VMware 公司甚至將此作為官方檢測 VMware hypervisor 的手段。VMware 也提供了一個接口,允許用戶“關閉”此方法。

bool isVmwarePresent()
{
    __try {
        _asm {
            mov eax, 0x564d5868 // 'VMXh'
            mov ebx, 0
            mov cx, 1
            mov dx, 0x5658      // 'VX'
            in eax, dx
        }
        return true;
    }
    __except (GetExceptionCode() == EXCEPTION_PRIV_INSTRUCTION) {
        return false;
    }
}

2.2 CPUID

CPUID是一種x86指令,用來獲取CPU的各種信息。該指令通過輸入 EAX 中的值來確定要在EAX,EBX,ECX和EDX寄存器中返回的信息。下面給出三種基于 CPUID 指令的檢測方法。

1.當 EAX=0 時, CPUID 將按EBX,EDX和ECX寄存器的順序返回一個12個字符的制造商ID字符串。Intel CPU返回“GenuineIntel”,而AMD CPU在K5型號之后返回“AuthenticAMD” 。當在虛擬機中運行時,ID將被專屬于某些虛擬機的自定義字符串替換。VMware虛擬機中理論上將返回 “VmwareVmware”。

BOOL CheckCPUIDVendor(){ INT CPUInfo[4] = { -1 }; CHAR szHypervisorVendor[0x40];  __cpuid(CPUInfo, 0x40000000); SecureZeroMemory(szHypervisorVendor, sizeof(szHypervisorVendor)); memcpy(szHypervisorVendor, CPUInfo + 1, 12);  if (_strcmpi(szHypervisorVendor, "VMwareVMware") == 0)     return TRUE;     return FALSE;}

2.當 EAX=1 時,CPUID將返回有關處理器功能的信息,例如處理器支持的指令。返回值 ECX 中的第 31 位確定是否存在 hypervisor。在物理機中此值為 0,在虛擬機中為 1。

BOOL CheckCPUIDVendor(){ INT CPUInfo[4] = { -1 };  __cpuid(CPUInfo, 1);  if ((CPUInfo[2] >> 31 ) & 1)     return TRUE;     return FALSE;}

3.在虛擬機中執行 CPUID 還會觸發 VM-Exit Event,使得進程從 guest 切換到 VMM。可以用 RDTSC 指令來計算這個開銷。

BOOL rdtsc_diff_vmexit(){ ULONGLONG tsc1 = 0; ULONGLONG tsc2 = 0; ULONGLONG avg = 0; INT cpuInfo[4] = {};  // Try this 10 times in case of small fluctuations for (INT i = 0; i < 10; i++) {     tsc1 = __rdtsc();     __cpuid(cpuInfo, 0);     tsc2 = __rdtsc();      // Get the delta of the two RDTSC     avg += (tsc2 - tsc1); }  // We repeated the process 10 times so we make sure our check is as much reliable as we can avg = avg / 10; return (avg < 1000 && avg > 0) ? FALSE : TRUE;}

2.3 SIDT(已失效)

SIDT是 Intel x86 指令,用于檢測舊單核處理器上的虛擬機存在。這個指令被 “ScoopyNG” 以及 “Redpill” 使用,來檢測虛擬機的存在。該命令將中斷描述符表寄存器 (IDTR)的內容存放到指定內存地址中。中斷描述符表(IDT)是一種重要的操作系統數據結構,用于處理來自硬件(如鍵盤和鼠標)的中斷命令。hypervisor 會將虛擬化操作系統的 IDT 的地址移動到 hypervisor 可預測的地址。Klein 和 Rutkowska 都發現VMware在當時將 IDT 存儲在地址0xFFXXXXXX上,從而在單核處理器上檢測 VMware 虛擬機。

然而,隨著多核處理器的引入,這種虛擬機檢測技術不再可靠,因為處理器內的每個核心都具有唯一的中斷描述符表,而每個表的地址都不同。此外,在我們對 VMware 的虛擬機測試SIDT命令時,即使只分配了一個處理器核心,IDT 的地址也從未出現在0xFFXXXXXX的地址上。因此,這種虛擬機檢測方法已失效。

2.4 SGDT(已失效)

SGDT是 Intel x86 指令,用于將全局描述符表寄存器(GDTR)的內容存放到指定內存地址中。GDT 是處理器使用的數據結構,用于定義有關進程內存的詳細信息,例如基址、大小和內存權限。這種虛擬機檢測方法曾被“ScoopyNG”使用。與SIDT指令類似,當在基于VMware的虛擬機中運行時,該指令會返回0xFFXXXXXX的值。然而,這個指令也面臨著 SIDT 指令引入多核處理器后遇到的同樣問題。在使用 VMware Workstation 測試這種檢測方法時,返回的值并不是0xFFXXXXXX。因此,這種檢測方法已失效。

2.5 SLDT(已失效)

SLDT 是 Intel x86 指令,用于將本地描述符表寄存器(LDTR)的內容存放到指定內存地址。LDT包含有關進程內存段的信息,例如代碼段、數據段以及堆。當在虛擬機中執行此命令時,將返回非零值,而在非虛擬化環境中返回0。“ScoopyNG”和惡意軟件“Conficker”曾使用過這種虛擬機檢測方法。我們在 VMware Workstation、VirtualBox、XEN、HyperV 和 KVM/QEMU 虛擬機上測試了此命令,發現所有虛擬機都返回0。這種檢測方法已失效。

2.6 STR(已失效)

STR是 Intel x86 指令,它返回當前執行進程的任務狀態寄存器(TSR)內容到指定內存地址。任務狀態段 TSS 用于存放操作系統用于任務切換的數據,其中包括進程 threads 和 registry 信息,供處理器操作系統處理線程調度。Klein 發現在虛擬機內運行此命令時,它返回格式為 0x0040XXXX 的地址。對VMware Workstation、VirtualBox、XEN、HyperV和KVM/QEMU虛擬機上測試此命令,發現這種檢測方法已失效。

2.7 SMSW(已失效)

SMSW是 Intel x86命令之一,用于將機器狀態字返回到指定的內存地址。機器狀態字的信息來自CR0寄存器,與 CPU 用于指示是否啟用某些功能(如內存分頁)的 flags 有關。Quist 發現,在VMware虛擬機內運行此命令,當 EAX=0xCCCCCCCC 時,將返回 0x8001XXXX。經測試在VMware虛擬機內和物理機上測試此命令都會返回值 0x8005XXXX。因此這種檢測方法已失效。

2.8 VM “Synthetic Instructions”(已失效)

Traut在2003年提出了一種使用“合成CPU指令”的虛擬機檢測方法。這些指令僅被虛擬機支持,在物理機上執行時會觸發無效指令異常。由于這些是無效的CPU指令,因此它們未在Intel或AMD的程序員手冊中記錄。這些指令的格式為“0FC7C8XXXX”。其中最“VMCPUID”指令被“Necurs”僵尸網絡用于檢查虛擬機的存在。

在 VMware Workstation、VirtualBox、XEN、HyperV 和 KVM/QEMU 虛擬化平臺上測試 “VMCPUID” 指令的代碼如下:

bool IsVmcpuidSupported()
{
    bool supported = true;
    void* mempool;
    char vmcpuid[] = "\x0F\xC7\xC8\x01\x00"; // VMCPUID OPCODE
    mempool = malloc(sizeof(vmcpuid));
    memcpy(mempool, &vmcpuid, sizeof(vmcpuid));
    DWORD prevAccess;
    VirtualProtect(mempool, sizeof(vmcpuid), PAGE_EXECUTE_READ, &prevAccess);
    __try {
        ((void(*)())mempool)();
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        supported = false;
    }
    return supported;
}

結果所有測試的虛擬化平臺都不支持 VMCPUID 命令。實際上,這個命令被錯誤地解釋為兩個命令:

CMPXCHG8B EAX
ADD DWORD PTR DS:[EAX],EAX

三、其他檢測方法

3.1 Thermal Zone Temperature

惡意軟件“Win32/gravityRAT”采用了一種通過檢測系統 thermal zone 來檢測虛擬機存在的方法。通過使用下面的powershell腳本,可以輕松檢查機器是否支持“MSAcip_ThermalZoneTemperature”對象。如果此腳本沒有返回結果,則假定該計算機在虛擬機中,因為虛擬機默認不支持熱力監控。由于Thermal zone sensors 可以在系統的BIOS或UEFI中禁用和啟用,在多個物理機上測試結果不一致,所以該方法并不可靠。

function Get-AntiVMwithTemperature {
    $t = Get-WmiObject MSAcip_ThermalZoneTemperature -Namespace "root/wmi"
    $valorTempKelvin = $t.CurrentTemperature / 10
    $valorTempCelsius = $valorTempKelvin - 273.15
    $valorTempFashrenheit = (9/5) * $valorTempCelsius + 32
    return $valorTempCelsius.ToString() + " C : " + valorTempFashrenheit.ToString() + \
        " F : " + $valorTempKelvin.ToString() + "K"
}

Ref:

Anti-VM Technique with MSAcpi_ThermalZoneTemperature

https://debugactiveprocess.medium.com/anti-vm-techniques-with-msacpi-thermalzonetemperature-32cfeecda802

3.2 IP Timestamp Patterns

這種虛擬機檢測方法是由Noorafize等人發現的 ,它基于虛擬機無法像物理機一樣保持準確的時間。虛擬機通常通過與主機計算機共享時間來進行時間同步,以嘗試偽裝成物理機,但來自虛擬機的IP報文時間戳存在不一致性。研究人員發現,由于虛擬機需要與 hypervisor 進行交互,導致虛擬機的時間戳信息略有不同。為了使用此檢測方法,攻擊者需要向目標系統發送大量的IP / ICMP 數據包以獲取足夠的信息,以推斷目標機器是否為虛擬機。

Ref:

M. Noorafiza, H. Maeda, T. Kinoshita and R. uda, "Virtual Machines Detection Methods using IP Timestamps pattern Characteristic," vol. 8, 2016

3.3 CPU Detail Anomalies

通過檢查CPU物理核心數、邏輯核心數和cache容量與實際產品配置的不一致性來檢測虛擬機。

Ref:Mettrick D. Virtual machine detection through Central Processing Unit (CPU) detail anomalies[D]. , 2022.

3.4 Physical memory resource maps

在 Windows 中,設備驅動程序需要訪問硬件資源(如內存、I/O 端口、中斷等),這些資源的地址通常是硬件相關的,因此在不同的系統上地址可能會發生變化。為了方便驅動程序的編寫和移植,Windows 內核提供了資源映射機制,將硬件相關的地址轉換為系統相關的地址,從而使驅動程序可以在不同的系統上運行而不需要進行修改。內存翻譯就是其中一種資源映射類型,它將硬件設備的物理內存地址映射為系統中的虛擬內存地址,使驅動程序可以通過虛擬地址來訪問設備內存。

可以通過比對虛擬地址的不同檢測虛擬機環境。測試發現Hyper-V虛擬機具有與 00001000-000a0000 匹配的物理內存映射區域。VirtualBox虛擬機具有與 00001000-009f000 匹配的物理內存映射區域。

Ref:

VM Detection Tricks, Part 1: Physical memory resource maps

https://labs.nettitude.com/blog/vm-detection-tricks-part-1-physical-memory-resource-maps/

3.5 Driver Thread Fingerprinting

驅動程序可能創建一定數量的線程,并具有可預測的屬性,因此這些屬性可以用于指紋識別并構建有用的檢測啟發式算法。

Ref:

VM Detection Tricks, Part 2: Driver Thread Fingerprinting

https://labs.nettitude.com/blog/vm-detection-tricks-part-2-driver-thread-fingerprinting/

3.6 基于鼠標行為的方法

惡意軟件 Win32/Okrum 使用了基于行為的一些方法來檢測沙箱:

◆GetTickCount函數調用兩次,間隔20秒休眠。如果GetTickCount值沒有改變(即時間已經加速),惡意軟件將終止自身。

◆連續兩次調用GetCursorPos函數。如果鼠標在X軸上的位置發生了變化(即鼠標位置是隨機生成的),惡意軟件將終止自身。

◆調用GetGlobalMemoryStatusEx。如果實際物理內存的數量小于1.5GB,惡意軟件將終止自身

◆只有左鍵(物理)鼠標按鈕按下至少三次后(在無限循環中查詢GetAsyncKeyState),負載才開始。

Ref:

ESET_Okrum_and_Ketrican.pdf

https://www.welivesecurity.com/wp-content/uploads/2019/07/ESET_Okrum_and_Ketrican.pdf

參考

1.https://www.cynet.com/attack-techniques-hands-on/malware-anti-vm-techniques/

2.https://www.ptsecurity.com/ww-en/analytics/antisandbox-techniques/

3.https://artemonsecurity.com/vmde.pdf

4.https://github.com/LordNoteworthy/al-khaser

5.https://github.com/a0rtega/pafish