前言:
反调试和反虚拟技术都是通过检查系统中调试器或者虚拟机的行为特征来判断是否存在该检测环境下,不同的是调试器是软件级别,虚拟机是硬件级别。
VMware痕迹
- 进程列表
可以使用Process32First
,Process32Next
等Windows API函数列举系统中的进程列表,主要是为了探测是否存在虚拟机的痕迹:
Vmware:
Vmtoolsd.exe
Vmwaretrat.exe
Vmwareuser.exe
Vmacthlp.exe
VirtualBox:
vboxservice.exe
vboxtray.exe
VMware Tools:
VMwareService.exe
VMwareTray.exe
VMwareUser.exe
tips:虚拟机中通常不会安装一些日常使用软件,可以用探测进程的方法来检测运行系统的大致情况,如QQ、浏览器等软件。
- 注册表
可以使用RegOpenKey
等函数打开指定键,由此判断是否在虚拟机中。
考虑是否可以通过更改注册表项名称来防止检测:
备份注册表:在进行任何注册表修改之前,务必备份注册表,以防止意外情况发生。
定位注册表项:找到 HKEY_CLASSES_ROOT\Applications\vmware.exe 注册表项。
重命名注册表项:将 vmware.exe 重命名为其他名称,例如 notvmware.exe。
列举整个注册表:
#include <windows.h>
#include <stdio.h>
void EnumerateRegistryKeysAndValues(HKEY hKey, const char* subKey) {
HKEY hSubKey;
if (RegOpenKeyExA(hKey, subKey, 0, KEY_READ, &hSubKey) == ERROR_SUCCESS) {
DWORD index = 0;
char keyName[256];
DWORD keyNameSize = sizeof(keyName);
FILETIME fileTime;
while (RegEnumKeyExA(hSubKey, index, keyName, &keyNameSize, NULL, NULL, NULL, &fileTime) == ERROR_SUCCESS) {
char fullKeyPath[512];
snprintf(fullKeyPath, sizeof(fullKeyPath), "%s\\%s", subKey, keyName);
printf("Key: %s\n", fullKeyPath);
EnumerateRegistryKeysAndValues(hKey, fullKeyPath);
keyNameSize = sizeof(keyName);
index++;
}
index = 0;
char valueName[256];
DWORD valueNameSize = sizeof(valueName);
DWORD valueType;
BYTE valueData[256];
DWORD valueDataSize = sizeof(valueData);
while (RegEnumValueA(hSubKey, index, valueName, &valueNameSize, NULL, &valueType, valueData, &valueDataSize) == ERROR_SUCCESS) {
printf("Value: %s, Type: %lu, Data: ", valueName, valueType);
switch (valueType) {
case REG_SZ:
printf("%s\n", valueData);
break;
case REG_DWORD:
printf("%lu\n", *((DWORD*)valueData));
break;
default:
printf("(Unknown type)\n");
break;
}
valueNameSize = sizeof(valueName);
valueDataSize = sizeof(valueData);
index++;
}
RegCloseKey(hSubKey);
} else {
printf("Failed to open registry key: %s\n", subKey);
}
}
int main() {
// 遍历 HKEY_LOCAL_MACHINE
printf("Enumerating HKEY_LOCAL_MACHINE:\n");
EnumerateRegistryKeysAndValues(HKEY_LOCAL_MACHINE, "");
// 遍历 HKEY_CURRENT_USER
printf("\nEnumerating HKEY_CURRENT_USER:\n");
EnumerateRegistryKeysAndValues(HKEY_CURRENT_USER, "");
// 遍历 HKEY_CLASSES_ROOT
printf("\nEnumerating HKEY_CLASSES_ROOT:\n");
EnumerateRegistryKeysAndValues(HKEY_CLASSES_ROOT, "");
// 遍历 HKEY_USERS
printf("\nEnumerating HKEY_USERS:\n");
EnumerateRegistryKeysAndValues(HKEY_USERS, "");
// 遍历 HKEY_CURRENT_CONFIG
printf("\nEnumerating HKEY_CURRENT_CONFIG:\n");
EnumerateRegistryKeysAndValues(HKEY_CURRENT_CONFIG, "");
这里时间和资源消耗会很大,感觉不是特别适用,如果注册表被列举完全,去分析也是很大的工作量。
- 文件系统
利用WMIC
、WINAPI
以及CMD
查找系统中的文件,
VMware:
C:\windows\System32\Drivers\Vmmouse.sys
C:\windows\System32\Drivers\vmtray.dll
C:\windows\System32\Drivers\VMToolsHook.dll
C:\windows\System32\Drivers\vmmousever.dll
C:\windows\System32\Drivers\vmhgfs.dll
C:\windows\System32\Drivers\vmGuestLib.dll
VirtualBox:
C:\windows\System32\Drivers\VBoxMouse.sys
C:\windows\System32\Drivers\VBoxGuest.sys
C:\windows\System32\Drivers\VBoxSF.sys
C:\windows\System32\Drivers\VBoxVideo.sys
C:\windows\System32\vboxdisp.dll
C:\windows\System32\vboxhook.dll
C:\windows\System32\vboxoglerrorspu.dll
C:\windows\System32\vboxoglpassthroughspu.dll
C:\windows\System32\vboxservice.exe
C:\windows\System32\vboxtray.exe
C:\windows\System32\VBoxControl.exe
如果这些文件不存放在默认磁盘c盘中,可以用GetLogicalDrives
函数来获取系统中所有逻辑驱动器的列表。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>
#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383
void CheckVMwareFiles() {
const char* vmwareFiles[] = {
"\\Program Files\\VMware\\VMware Tools\\",
"\\Program Files (x86)\\VMware\\VMware Tools\\",
"\\Windows\\System32\\drivers\\vm3dmp.sys",
"\\Windows\\System32\\drivers\\vmci.sys",
"\\Windows\\System32\\drivers\\vmhgfs.sys",
"\\Windows\\System32\\drivers\\vmmouse.sys",
"\\Windows\\System32\\drivers\\vmrawdsk.sys",
"\\Windows\\System32\\drivers\\vmusbmouse.sys",
"\\Windows\\System32\\drivers\\vmx_svga.sys",
"\\Windows\\System32\\drivers\\vmxnet.sys",
"\\Windows\\System32\\drivers\\vmxnet3.sys",
"\\Windows\\System32\\drivers\\vmscsi.sys",
"\\Windows\\System32\\drivers\\vmx86.sys"
};
DWORD drives = GetLogicalDrives();
for (char drive = 'A'; drive <= 'Z'; drive++) {
if (drives & (1 << (drive - 'A'))) {
char drivePath[4] = {drive, ':', '\\', '\0'};
for (int i = 0; i < sizeof(vmwareFiles) / sizeof(vmwareFiles[0]); i++) {
char fullPath[MAX_PATH];
snprintf(fullPath, sizeof(fullPath), "%s%s", drivePath, vmwareFiles[i]);
if (GetFileAttributesA(fullPath) != INVALID_FILE_ATTRIBUTES) {
printf("Found VMware file: %s\n", fullPath);
}
}
}
}
}
void CheckVMwareServices() {
const char* vmwareServices[] = {
"VMware Tools Service",
"VMware Physical Disk Helper Service",
"VMware NAT Service",
"VMware DHCP Service"
};
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (hSCManager == NULL) {
printf("Failed to open SCManager\n");
return;
}
for (int i = 0; i < sizeof(vmwareServices) / sizeof(vmwareServices[0]); i++) {
SC_HANDLE hService = OpenServiceA(hSCManager, vmwareServices[i], SERVICE_QUERY_STATUS);
if (hService != NULL) {
printf("Found VMware service: %s\n", vmwareServices[i]);
CloseServiceHandle(hService);
}
}
CloseServiceHandle(hSCManager);
}
int main() {
printf("Checking VMware files...\n");
CheckVMwareFiles();
printf("\nChecking VMware services...\n");
CheckVMwareServices();
return 0;
}
跟注册表一样,时间和资源消耗很严重,同时,也有可能存在权限不够,探测不完全的问题。
- 运行的服务
用wmic
命令或者sc.exe query
查询系统中运行的服务。
//可以使用 wmic service where "name='服务名称'" get name,state 命令来查询特定服务的状态。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
void CheckServiceWithWMIC(const char* serviceName) {
char command[256];
snprintf(command, sizeof(command), "wmic service where \"name='%s'\" get name,state", serviceName);
HANDLE hReadPipe, hWritePipe;
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
if (!CreatePipe(&hReadPipe, &hWritePipe, &saAttr, 0)) {
printf("CreatePipe failed (%lu)\n", GetLastError());
return;
}
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
if (!CreateProcessA(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
printf("CreateProcess failed (%lu)\n", GetLastError());
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
return;
}
CloseHandle(hWritePipe);
char buffer[4096];
DWORD bytesRead;
while (ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead != 0) {
buffer[bytesRead] = '\0';
printf("%s", buffer);
}
CloseHandle(hReadPipe);
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
int main() {
printf("Checking VMware Tools Service with WMIC...\n");
CheckServiceWithWMIC("VMware Tools Service");
return 0;
}
- mac地址前缀
上诉都是软件在系统中运行的痕迹,可以通过一定方式进行隐藏,或者探测工作量较大,下面是学习虚拟机的硬件特征。
检查虚拟机可能出现的默认mac地址前缀的逻辑主要是在虚拟机中需要配置虚拟网络配置器,通常,mac地址的前三个字节标识一个提供商,不太容易被修改,例如,以00:0C:29开始的mac地址与vmware相对应。
以vmware为例,不同版本Vmware地址有所差异。
vmware虚拟机可以使用多个不同的mac地址前缀,具体取决于虚拟机的配置和vmware产品的版本,
00:50:56:这是 VMware 虚拟机最常见的 MAC 地址前缀,用于 VMware Workstation、VMware ESXi、VMware Fusion 和 VMware Player 等产品。
00:0C:29:这是另一种常见的 VMware 虚拟机 MAC 地址前缀,通常用于较旧版本的 VMware 产品。
00:1C:14和00:1C:42:通常用于较新的 VMware 产品。
可以通过 VMware Tools 提供的命令行工具 vmtoolsd
来获取虚拟机的 MAC 地址前缀。
vmtoolsd --cmd "info-get guestinfo.hosthardware.systemInfo"
同理,也可以通过其他硬件设备来探测VMware,例如主板。
- 探测内存痕迹
作为虚拟化的结果,VMware在物理内存中会留下很多痕迹。一些处理器结构或是被移动或是被修改,这就留下了可识别的指纹。
例如,CPUID
指令通常用于查询处理器的特性信息。而在虚拟化软件中通常会修改 CPUID
指令的返回值,以模拟物理 CPU 的特性。 CPUID
这条指令,除了用于识别CPU(CPU的型号、家族、类型等),还可以读出CPU支持的功能(比如是否支持MMX,是否支持4MB的页等等),内容非常丰富,所以这里也只是简单的学习。
简化示例展示vmware如何处理这条指令:
虚拟机调用指令-->VMware拦截指令-->VMware模拟指令返回值-->返回模拟结果
基本原理就是通过查询vmware模拟指令返回的值来判断是否存在虚拟机。
例如,VMware 会在 CPUID
的 0x40000000
叶中返回特定的 Hypervisor 信息;可能会修改 CPUID
的 Vendor String 为 "VMwareVMware"
,以标识虚拟化环境。
所以,可以通过检查 Hypervisor Present 位和Hypervisor Vendor String来判断是否当前环境为虚拟机。
//CPUID 指令的 0x1 叶中的 ECX[31] 位(Hypervisor Present 位)可以用于检测虚拟化环境。如果该位被设置,表示当前环境是虚拟机。
#include <stdio.h>
#include <stdint.h>
void cpuid(uint32_t leaf, uint32_t subleaf, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) {
__asm__ volatile (
"cpuid"
: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
: "a" (leaf), "c" (subleaf)
);
}
int is_virtualized() {
uint32_t eax, ebx, ecx, edx;
cpuid(0x1, 0x0, &eax, &ebx, &ecx, &edx);
return (ecx & (1 << 31)) != 0; // 检查 Hypervisor Present 位
}
int main() {
if (is_virtualized()) {
printf("Running in a virtualized environment.\n");
} else {
printf("Running in a non-virtualized environment.\n");
}
return 0;
}
//CPUID 指令的 0x40000000 叶可以用于获取 Hypervisor 的 Vendor String。虚拟化软件(如 VMware、Hyper-V、KVM 等)会在该叶中返回特定的 Vendor String,以标识虚拟化环境。
#include <stdio.h>
#include <stdint.h>
#include <string.h>
void cpuid(uint32_t leaf, uint32_t subleaf, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) {
__asm__ volatile (
"cpuid"
: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
: "a" (leaf), "c" (subleaf)
);
}
int is_vmware() {
uint32_t eax, ebx, ecx, edx;
char vendor[13];
// 获取 Hypervisor 信息
cpuid(0x40000000, 0x0, &eax, &ebx, &ecx, &edx);
*(uint32_t *)(vendor + 0) = ebx;
*(uint32_t *)(vendor + 4) = ecx;
*(uint32_t *)(vendor + 8) = edx;
vendor[12] = '\0';
return strcmp(vendor, "VMwareVMware") == 0;
}
int main() {
if (is_vmware()) {
printf("Running in a VMware virtualized environment.\n");
} else {
printf("Running in a non-VMware environment.\n");
}
return 0;
}
同理还有很多其他类似的指令:
RDTSC
指令用于读取时间戳计数器。虚拟化软件通常会修改 RDTSC
的行为,以模拟物理 CPU 的时间戳计数器。
MSR
是特定于处理器的寄存器,用于存储各种硬件状态和配置信息。虚拟化软件通常会修改某些 MSR
的值,以模拟物理 CPU 的行为。
VMX
是 Intel 的虚拟化扩展技术。虚拟化软件通常会使用VMX
来管理虚拟机。
虚拟机运行在宿主操作系统之上,并为客户机操作系统提供完成的虚拟平台。所以,主机处理器和虚拟机处理器在处理某些指令会存在安全缺陷。
比如,虚拟机监视器需要重新定位Guest系统中的IDTR寄存器,来避免与HOST系统的IDTR寄存器产生冲突,通过sidt指令可以获取IDTR寄存器的值,对比判断是否存在虚拟环境,但该种方法仅在单核处理器上有效,在多核处理器上不是一直有效。
再比如,Windows不会使用LDT结构,但是vmware会为LDT提供虚拟化支持,所以Host系统重LDT位置的值为0,虚拟机中它却是非零值。可以用sldt指令进行探测。
- 查询I/O通信端口
查询 I/O 通信端口通常用于与硬件设备进行通信。I/O 端口是 CPU 与外部设备(如键盘、鼠标、网络适配器等)进行数据交换的接口。在 x86 架构中,I/O 端口是通过 IN
和 OUT
指令进行访问的。
其基本原理是IN指令属于特权指令,在主机中强行运行会触发`EXCEPTION_PRIV_INSTRUCTION`的异常,但是在虚拟环境中并不会发生异常。
VMware漏洞
据不完全统计,vmware系统产品所披露的漏洞有:
CVE-2020-3992——VMware ESXI 远程代码执行漏洞
CVE-2021-21994——VMware ESXi SFCB身份验证绕过漏洞
CVE-2021-21995——VMware ESXi OpenSLP拒绝服务漏洞
CVE-2021-22005——VMware vCenter Server 任意文件上传漏洞复现
CVE-2021-21972——VMware VCenter未授权任意文件上传漏洞
CVE-2021-21985——VMware vCenter Server远程代码执行漏洞
CVE-2024-37079——VMware vCenter Server堆溢出漏洞
CVE-2024-37080——VMware vCenter Server堆溢出漏洞
CVE-2024-37081——VMware vCenter Server权限提升漏洞
以VMware vCenter - CVE-2024-37081 任意命令执行为例,这些漏洞可以干扰虚拟机的正常运行使用。
这个漏洞成因主要是利用了`/etc/sudoers`文件中`Defaults env_keep`参数配置错误,允许用户在运行sudo命令前,修改危险的环境变量,例如“PYTHONPATH”、 “VMWARE_PYTHON_PATH”、VMWARE_PYTHON_BIN 等。
运行环境:
- VMware vCenter 8.0.0.10200
- Photon Linux
影响的sudo用户或组:
可以实现的干扰:
- 以高权限从任意受控位置加载恶意代码
- 以高权限执行恶意脚本
echo '/bin/bash' > /tmp/shell && chmod +x /tmp/shell
export VMWARE_PYTHON_BIN=/tmp/shell
sudo /bin/dcli
- 以高权限任意读取文件
sudo /usr/sbin/sendmail -tf aaa -C/etc/shadow
漏洞信息参考于[nomi-sec/PoC-in-GitHub: