vmp完整源码分析-014-虚拟机检测详解2

# VMProtect 虚拟机检测方法详解

20260402111256268-image

 

## 目录

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. **应用层**:沙箱环境检测

这种设计确保了检测的可靠性和抗绕过能力。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容