# VMProtect 虚拟机检测方法详解

## 目录
1. [概述](#1-概述)
2. [硬件检测方法](#2-硬件检测方法)
3. [软件检测方法](#3-软件检测方法)
4. [平台特定检测](#4-平台特定检测)
5. [检测流程](#5-检测流程)
6. [反检测对抗](#6-反检测对抗)
—
## 1. 概述
VMProtect 采用**多层次、多方法**的虚拟机检测策略,确保即使某一种检测方法被绕过,其他方法仍能发现虚拟机环境。
### 检测层次
| 层次 | 方法 | 可靠性 | 适用平台 |
|——|——|——–|———-|
| 硬件层 | CPUID 指令 | 高 | 全平台 |
| 硬件层 | 陷阱标志检测 | 中 | Windows |
| 固件层 | 固件表扫描 | 高 | Windows |
| 固件层 | 物理内存扫描 | 中 | Windows |
| 系统层 | DMI 信息读取 | 高 | Linux |
| 应用层 | 沙箱 DLL 检测 | 高 | Windows |
—
## 2. 硬件检测方法
### 2.1 CPUID 指令检测(主要方法)
#### 原理
CPUID 指令的 EAX=1 功能会返回处理器特征信息,其中 ECX 寄存器的第 31 位(Hypervisor 位)表示是否在虚拟化环境中运行。
#### 实现代码
“`cpp
bool CheckCPUIDHypervisor()
{
int cpu_info[4];
// 执行 CPUID EAX=1
__cpuid(cpu_info, 1);
// 检查 ECX[31] (Hypervisor 位)
// 如果为 1,说明在虚拟机中
if ((cpu_info[2] >> 31) & 1) {
return true;
}
return false;
}
“`
#### 寄存器说明
“`
CPUID EAX=1 返回值:
┌─────────────────────────────────────┐
│ EAX = 处理器签名 │
│ EBX = 品牌索引/CLFLUSH 行大小 │
│ ECX = 特征标志位 │
│ [31] Hypervisor 位 │
│ [30:0] 其他特征 │
│ EDX = 特征标志位 │
└─────────────────────────────────────┘
“`
#### Hyper-V 特殊处理
Hyper-V 的根分区(Root Partition)是物理主机,需要特殊排除:
“`cpp
bool CheckHyperVRootPartition()
{
int cpu_info[4];
// 1. 获取 Hypervisor 供应商 ID
cpu_info[1] = 0;
cpu_info[2] = 0;
cpu_info[3] = 0;
__cpuid(cpu_info, 0x40000000);
// 2. 检查是否为 “Microsoft Hv”
// EBX = “Micr” = 0x7263694d
// ECX = “osof” = 0x666f736f
// EDX = “t Hv” = 0x76482074
if (cpu_info[1] == 0x7263694d &&
cpu_info[2] == 0x666f736f &&
cpu_info[3] == 0x76482074) {
// 3. 检查是否为根分区
cpu_info[1] = 0;
__cpuid(cpu_info, 0x40000003);
// EBX[0] = 1 表示根分区(物理主机)
if (cpu_info[1] & 1) {
return true; // 是根分区,不是虚拟机
}
}
return false;
}
“`
### 2.2 陷阱标志检测(辅助方法)
#### 原理
陷阱标志(Trap Flag,EFLAGS 第 8 位)是 x86 处理器的单步执行标志。当该标志被设置时,每条指令执行后都会触发单步异常(INT 1)。虚拟机对单步异常的处理与物理机存在差异,可用于检测虚拟机。
#### 实现代码
“`cpp
bool CheckTrapFlag()
{
uint8_t mem_val;
uint64_t val;
// 测试 1: RDTSC 指令
__try {
// 设置陷阱标志 (EFLAGS |= 0x100)
__writeeflags(__readeflags() | 0x100);
// 执行 RDTSC 指令
val = __rdtsc();
// 执行 NOP 指令
__nop();
// 如果正常执行到这里,说明陷阱标志未生效
// 可能是虚拟机
} __except (
// 获取触发异常的指令字节
mem_val = *static_cast<uint8_t *>(
GetExceptionInformation()->ExceptionRecord->ExceptionAddress
),
EXCEPTION_EXECUTE_HANDLER
) {
// 异常处理程序被调用
// 检查触发异常的指令是否为 NOP (0x90)
if (mem_val != 0x90) {
return true; // 检测到虚拟机
}
}
// 测试 2: CPUID 指令
__try {
int cpu_info[4];
// 设置陷阱标志
__writeeflags(__readeflags() | 0x100);
// 执行 CPUID 指令
__cpuid(cpu_info, 1);
// 执行 NOP 指令
__nop();
} __except (
mem_val = *static_cast<uint8_t *>(
GetExceptionInformation()->ExceptionRecord->ExceptionAddress
),
EXCEPTION_EXECUTE_HANDLER
) {
if (mem_val != 0x90) {
return true; // 检测到虚拟机
}
}
return false;
}
“`
#### 检测原理
| 行为 | 物理机 | 虚拟机 |
|——|——–|——–|
| 陷阱标志设置 | 正常触发单步异常 | 可能忽略或处理异常 |
| 异常触发点 | NOP 指令 (0x90) | 其他指令 |
| 异常处理 | 按顺序执行 | 可能跳过或延迟 |
—
## 3. 软件检测方法
### 3.1 固件供应商字符串检测
#### 检测目标字符串
“`cpp
// 虚拟机固件中常见的标识字符串
static const struct {
const char *str;
size_t len;
} VM_VENDORS[] = {
{ “VMware”, 6 }, // VMware Workstation/Player/ESXi
{ “VirtualBox”, 10 }, // Oracle VirtualBox
{ “Parallels”, 9 }, // Parallels Desktop (macOS)
{ “QEMU”, 4 }, // QEMU/KVM
{ “Microsoft”, 9 }, // Microsoft Hyper-V (Linux)
{ “innotek”, 7 }, // VirtualBox 旧版本 (Linux)
};
“`
#### 字符串搜索实现
“`cpp
bool FindFirmwareVendor(const uint8_t *data, size_t data_size)
{
for (size_t i = 0; i < data_size; i++) {
// VMware
if (i + 5 < data_size &&
data[i + 0] == ‘V’ && data[i + 1] == ‘M’ &&
data[i + 2] == ‘w’ && data[i + 3] == ‘a’ &&
data[i + 4] == ‘r’ && data[i + 5] == ‘e’)
return true;
// VirtualBox
if (i + 9 < data_size &&
data[i + 0] == ‘V’ && data[i + 1] == ‘i’ &&
data[i + 2] == ‘r’ && data[i + 3] == ‘t’ &&
data[i + 4] == ‘u’ && data[i + 5] == ‘a’ &&
data[i + 6] == ‘l’ && data[i + 7] == ‘B’ &&
data[i + 8] == ‘o’ && data[i + 9] == ‘x’)
return true;
// Parallels
if (i + 8 < data_size &&
data[i + 0] == ‘P’ && data[i + 1] == ‘a’ &&
data[i + 2] == ‘r’ && data[i + 3] == ‘a’ &&
data[i + 4] == ‘l’ && data[i + 5] == ‘l’ &&
data[i + 6] == ‘e’ && data[i + 7] == ‘l’ &&
data[i + 8] == ‘s’)
return true;
// QEMU (Linux)
if (i + 3 < data_size &&
data[i + 0] == ‘Q’ && data[i + 1] == ‘E’ &&
data[i + 2] == ‘M’ && data[i + 3] == ‘U’)
return true;
// Microsoft (Hyper-V on Linux)
if (i + 8 < data_size &&
data[i + 0] == ‘M’ && data[i + 1] == ‘i’ &&
data[i + 2] == ‘c’ && data[i + 3] == ‘r’ &&
data[i + 4] == ‘o’ && data[i + 5] == ‘s’ &&
data[i + 6] == ‘o’ && data[i + 7] == ‘f’ &&
data[i + 8] == ‘t’)
return true;
// innotek (VirtualBox old)
if (i + 6 < data_size &&
data[i + 0] == ‘i’ && data[i + 1] == ‘n’ &&
data[i + 2] == ‘n’ && data[i + 3] == ‘o’ &&
data[i + 4] == ‘t’ && data[i + 5] == ‘e’ &&
data[i + 6] == ‘k’)
return true;
}
return false;
}
“`
### 3.2 Windows 固件表检测
#### 使用 EnumSystemFirmwareTables API
“`cpp
bool CheckWindowsFirmwareTables()
{
HMODULE kernel32 = GetModuleHandleA(“kernel32.dll”);
// 动态获取 API 地址
typedef UINT (WINAPI *tEnumSystemFirmwareTables)(
DWORD FirmwareTableProviderSignature,
PVOID pFirmwareTableEnumBuffer,
DWORD BufferSize
);
typedef UINT (WINAPI *tGetSystemFirmwareTable)(
DWORD FirmwareTableProviderSignature,
DWORD FirmwareTableID,
PVOID pFirmwareTableBuffer,
DWORD BufferSize
);
tEnumSystemFirmwareTables *enum_tables =
(tEnumSystemFirmwareTables *)GetProcAddress(
kernel32, “EnumSystemFirmwareTables”
);
tGetSystemFirmwareTable *get_table =
(tGetSystemFirmwareTable *)GetProcAddress(
kernel32, “GetSystemFirmwareTable”
);
if (!enum_tables || !get_table)
return false;
// 获取固件表列表大小
UINT tables_size = enum_tables(‘FIRM’, NULL, 0);
if (!tables_size)
return false;
// 分配缓冲区并获取表列表
DWORD *tables = new DWORD[tables_size / sizeof(DWORD)];
enum_tables(‘FIRM’, tables, tables_size);
// 遍历每个固件表
for (size_t i = 0; i < tables_size / sizeof(DWORD); i++) {
// 获取表数据大小
UINT data_size = get_table(‘FIRM’, tables[i], NULL, 0);
if (data_size) {
// 分配缓冲区并读取表数据
uint8_t *data = new uint8_t[data_size];
get_table(‘FIRM’, tables[i], data, data_size);
// 检查是否包含虚拟机标识
if (FindFirmwareVendor(data, data_size)) {
delete[] data;
delete[] tables;
return true;
}
delete[] data;
}
}
delete[] tables;
return false;
}
“`
#### 固件表签名说明
“`cpp
// 固件表提供者签名
#define FIRM_SIGNATURE ‘FIRM’ // 原始固件表
#define RSMB_SIGNATURE ‘RSMB’ // SMBIOS 表
#define ACPI_SIGNATURE ‘ACPI’ // ACPI 表
“`
### 3.3 物理内存扫描(备用方法)
当 `EnumSystemFirmwareTables` API 不可用时,直接映射物理内存进行扫描。
“`cpp
bool CheckPhysicalMemory()
{
HMODULE ntdll = GetModuleHandleA(“ntdll.dll”);
// 动态获取 NT API
typedef NTSTATUS (NTAPI *tNtOpenSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);
typedef NTSTATUS (NTAPI *tNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
SECTION_INHERIT InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);
typedef NTSTATUS (NTAPI *tNtUnmapViewOfSection)(
HANDLE ProcessHandle,
PVOID BaseAddress
);
typedef NTSTATUS (NTAPI *tNtClose)(HANDLE Handle);
tNtOpenSection *open_section =
(tNtOpenSection *)GetProcAddress(ntdll, “NtOpenSection”);
tNtMapViewOfSection *map_view =
(tNtMapViewOfSection *)GetProcAddress(ntdll, “NtMapViewOfSection”);
tNtUnmapViewOfSection *unmap_view =
(tNtUnmapViewOfSection *)GetProcAddress(ntdll, “NtUnmapViewOfSection”);
tNtClose *close =
(tNtClose *)GetProcAddress(ntdll, “NtClose”);
if (!open_section || !map_view || !unmap_view || !close)
return false;
// 打开物理内存设备
HANDLE physical_memory = NULL;
UNICODE_STRING str;
OBJECT_ATTRIBUTES attrs;
wchar_t buf[] = L”\\device\\physicalmemory”;
str.Buffer = buf;
str.Length = sizeof(buf) – sizeof(wchar_t);
str.MaximumLength = sizeof(buf);
InitializeObjectAttributes(&attrs, &str, OBJ_CASE_INSENSITIVE, NULL, NULL);
NTSTATUS status = open_section(&physical_memory, SECTION_MAP_READ, &attrs);
if (!NT_SUCCESS(status))
return false;
// 映射物理内存 0xC0000 – 0xD0000
// 该区域通常包含 BIOS/UEFI 固件数据
void *data = NULL;
SIZE_T data_size = 0x10000; // 64KB
LARGE_INTEGER offset;
offset.QuadPart = 0xC0000;
status = map_view(physical_memory, GetCurrentProcess(), &data,
NULL, data_size, &offset, &data_size,
ViewShare, 0, PAGE_READONLY);
bool is_vm = false;
if (NT_SUCCESS(status)) {
// 扫描固件数据
is_vm = FindFirmwareVendor((uint8_t *)data, data_size);
unmap_view(GetCurrentProcess(), data);
}
close(physical_memory);
return is_vm;
}
“`
### 3.4 沙箱检测
#### Sandboxie 检测
“`cpp
bool CheckSandboxie()
{
// 检查 sbiedll.dll 是否加载
HMODULE sbie_dll = GetModuleHandleA(“sbiedll.dll”);
return (sbie_dll != NULL);
}
“`
—
## 4. 平台特定检测
### 4.1 Linux DMI 检测
“`cpp
bool CheckLinuxDMI()
{
// 读取 DMI 系统供应商信息
FILE *fsys_vendor = fopen(“/sys/devices/virtual/dmi/id/sys_vendor”, “r”);
if (!fsys_vendor)
return false;
char sys_vendor[256] = {0};
fgets(sys_vendor, sizeof(sys_vendor), fsys_vendor);
fclose(fsys_vendor);
// 检查是否包含虚拟机标识
return FindFirmwareVendor(
(uint8_t *)sys_vendor,
strlen(sys_vendor)
);
}
“`
#### DMI 信息路径
| 路径 | 内容 |
|——|——|
| `/sys/devices/virtual/dmi/id/sys_vendor` | 系统供应商 |
| `/sys/devices/virtual/dmi/id/product_name` | 产品名称 |
| `/sys/devices/virtual/dmi/id/board_vendor` | 主板供应商 |
| `/sys/devices/virtual/dmi/id/bios_vendor` | BIOS 供应商 |
### 4.2 Windows 驱动检测
“`cpp
bool CheckDriverFirmware()
{
bool is_found = false;
ULONG table_size = 0x1000;
// 分配缓冲区
SYSTEM_FIRMWARE_TABLE_INFORMATION *table =
(SYSTEM_FIRMWARE_TABLE_INFORMATION *)malloc(table_size);
table->Action = SystemFirmwareTable_Get;
table->ProviderSignature = ‘FIRM’;
table->TableID = 0xC0000;
table->TableBufferLength = table_size;
// 查询固件表
NTSTATUS status = NtQuerySystemInformation(
SystemFirmwareTableInformation,
table, table_size, &table_size
);
if (status == STATUS_BUFFER_TOO_SMALL) {
// 重新分配缓冲区
free(table);
table = (SYSTEM_FIRMWARE_TABLE_INFORMATION *)malloc(table_size);
table->Action = SystemFirmwareTable_Get;
table->ProviderSignature = ‘FIRM’;
table->TableID = 0xC0000;
table->TableBufferLength = table_size;
status = NtQuerySystemInformation(
SystemFirmwareTableInformation,
table, table_size, &table_size
);
}
if (NT_SUCCESS(status)) {
is_found = FindFirmwareVendor(
(uint8_t *)table,
table_size
);
}
free(table);
return is_found;
}
“`
—
## 5. 检测流程
### 5.1 完整检测流程图
“`
开始检测
│
▼
┌─────────────────────────────────────────┐
│ 1. CPUID 检测 │
│ __cpuid(1) │
│ 检查 ECX[31] (Hypervisor 位) │
└─────────────────────────────────────────┘
│
├─► Hypervisor 位 = 1 ──► 是虚拟机?
│ │
│ ▼
│ ┌──────────────────────┐
│ │ 检查 Hyper-V 根分区 │
│ │ __cpuid(0x40000000) │
│ │ 检查 “Microsoft Hv” │
│ └──────────────────────┘
│ │
│ ┌────────────┴────────────┐
│ │ │
│ ▼ ▼
│ 是 “Microsoft Hv” 其他虚拟机
│ │ │
│ ▼ ▼
│ ┌──────────────────┐ 确认虚拟机
│ │ __cpuid(0x40000003)│ 返回 true
│ │ 检查 EBX[0] │
│ └──────────────────┘
│ │
│ ┌────────┴────────┐
│ │ │
│ ▼ ▼
│ EBX[0] = 1 EBX[0] = 0
│ (根分区) (子分区)
│ │ │
│ ▼ ▼
│ 不是虚拟机 确认虚拟机
│ 返回 false 返回 true
│
└─► Hypervisor 位 = 0 ──► 继续软件检测
│
▼
┌───────────────────────────┐
│ 平台判断 │
└───────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
Windows Linux macOS
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 陷阱标志检测 │ │ DMI 检测 │ │ (FIXME) │
│ 固件表扫描 │ │ /sys/devices/ │ │ │
│ 物理内存扫描 │ │ virtual/dmi/ │ │ │
│ 沙箱检测 │ │ id/sys_vendor │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
“`
### 5.2 代码实现
“`cpp
bool DetectVirtualMachine()
{
int cpu_info[4];
// ========== 第一层:CPUID 硬件检测 ==========
__cpuid(cpu_info, 1);
if ((cpu_info[2] >> 31) & 1) {
// Hypervisor 位被置位
bool is_found = true;
// 检查是否为 Hyper-V 根分区
cpu_info[1] = 0;
cpu_info[2] = 0;
cpu_info[3] = 0;
__cpuid(cpu_info, 0x40000000);
if (cpu_info[1] == 0x7263694d &&
cpu_info[2] == 0x666f736f &&
cpu_info[3] == 0x76482074) { // “Microsoft Hv”
cpu_info[1] = 0;
__cpuid(cpu_info, 0x40000003);
// 根分区不视为虚拟机
if (cpu_info[1] & 1)
is_found = false;
}
return is_found;
}
// ========== 第二层:软件检测 ==========
#ifdef __APPLE__
// macOS 检测(待实现)
return false;
#elif defined(__unix__)
// Linux DMI 检测
return CheckLinuxDMI();
#else
// Windows 检测
// 2.1 陷阱标志检测
if (CheckTrapFlag())
return true;
// 2.2 固件表检测
if (CheckWindowsFirmwareTables())
return true;
// 2.3 物理内存扫描(备用)
if (CheckPhysicalMemory())
return true;
// 2.4 沙箱检测
if (CheckSandboxie())
return true;
return false;
#endif
}
“`
—
## 6. 反检测对抗
### 6.1 字符串加密
所有检测相关的字符串都经过加密,防止静态分析:
“`cpp
// 字符串加密宏
#define VMProtectDecryptStringA(str) \
DecryptString(str, FACE_STRING_DECRYPT_KEY)
// 使用示例
FILE *f = fopen(
VMProtectDecryptStringA(“/sys/devices/virtual/dmi/id/sys_vendor”),
“r”
);
“`
### 6.2 动态 API 解析
避免在导入表中暴露检测行为:
“`cpp
// 动态获取 API 地址
tEnumSystemFirmwareTables *enum_tables =
(tEnumSystemFirmwareTables *)InternalGetProcAddress(
kernel32,
VMProtectDecryptStringA(“EnumSystemFirmwareTables”)
);
“`
### 6.3 多方法冗余
即使一种方法被绕过,其他方法仍能检测:
| 绕过方法 | 可绕过的检测 | 无法绕过的检测 |
|———-|————-|—————|
| 修改 CPUID | CPUID 检测 | 固件扫描、陷阱标志 |
| 修改固件 | 固件扫描 | CPUID、陷阱标志 |
| 禁用 API | 固件表扫描 | CPUID、物理内存扫描 |
—
## 7. 总结
VMProtect 的虚拟机检测系统采用**硬件+软件**的多层次检测策略:
1. **硬件层**:CPUID 指令检测(最可靠)
2. **硬件层**:陷阱标志行为差异检测
3. **固件层**:固件表/物理内存扫描
4. **系统层**:DMI 信息读取
5. **应用层**:沙箱环境检测
这种设计确保了检测的可靠性和抗绕过能力。














暂无评论内容