1、执行特权指令检测
在x86体系中,一些指令在获取硬件相关信息时不产生异常,如sidt、sgdt、sldt、cpuid等,而VMware因为性能原因并没有虚拟这些指令,所以意味着这些指令在vm虚拟机中和物理机中运行时会返回不同的结果。
Redpill
简单说,就是通过运行sidt指令获取IDT寄存器的值(IDT: 中断描述符表,可以简单理解为查找处理中断时所用的函数,共256项,如第3项就是我们常用的int3断点)。Redpill的作者测试说明虚拟机中的IDT地址通常位于0xFFXXXXXX,而在真实主机上位于0x80xxxxxx。所以可通过判断执行SIDT指令后返回的第一字节是否大于0xD0,判断是否在虚拟机中。同时这项技术必须满足运行在单核处理器上,因为每个核心只有一个IDT表如果是多核切换就很难确定具体值了
可能会因为虚拟机的升级,导致结果不一样。
2、使用cpuid指令来检测是否设置了代码正在虚拟机程序上运行的特征位,有的虚拟机可能不会设置该特征位!
bool IsVMWare();
int main(int argc,char* argv[])
{
bool bRet;
bRet = IsVMWare();
if (bRet) {
printf("运行在虚拟机环境");
}
else
{
printf("运行在真实物理机环境");
}
getchar();
return 0;
}
bool IsVMWare()
{
unsigned int cpuInfo[4];
__cpuid((int*)cpuInfo, 1);
return ((cpuInfo[2] >> 31) & 1) == 1;
}
3、LDT(局部描述符表)
sgdt与sldt指令探测技术,依赖于LDT(局部描述符表)由处理器分配而非操作系统分配的事实。因为Windows正常情况下不使用LDT,但VM提供了LDT的虚拟化支持,结果就是:真机中LDT位置为0,而在虚拟机,不为0。同时对于GTR,虚拟机中应为0xFFXXXXXX , 否则为真机。
inline bool IsVirtualPC_LDTCheck()
{
unsigned short ldt_addr = 0;
unsigned char ldtr[2];
_asm sldt ldtr
ldt_addr = *((unsigned short *)&ldtr);
return ldt_addr != 0x00000000;
}
4、注册表检测
inline bool DetectVM()
{
HKEY hKey;
char szBuffer[64];
unsigned long hSize = sizeof(szBuffer)-1;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\BIOS\\", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, "SystemManufacturer", NULL, NULL, (unsigned char *)szBuffer, &hSize);
if (strstr(szBuffer, "VMWARE"))
{
RegCloseKey(hKey);
return true;
}
RegCloseKey(hKey);
}
return false;
}
5、GDT检测
inline bool IsVirtualPC_GDTCheck()
{
unsigned int gdt_addr = 0;
unsigned char gdtr[6];
_asm sgdt gdtr
gdt_addr = *((unsigned int *)&gdtr[2]);
return (gdt_addr >> 24) == 0xff;
}
6、TSS检测
inline bool IsVirtualPC_TSSCheck()
{
unsigned char mem[4] = { 0 };
__asm str mem;
return (mem[0] == 0x00) && (mem[1] == 0x40);
}
7、I/O通信端口检测
原理:使用IN指令来读取特定端口的数据进行两机通讯,但由于IN指令属于特权指令,在处于保护模式下的真机上执行此指令时,除非权限允许,否则将会触发类型为"EXCEPTION_PRIV_INSTRUCTION"的异常,而在虚拟机中并不会发生异常,在指定功能号为0xA/10(获取VMware版本)时,会在EBX中返回其版本号“VMXH”;而当功能号为0x14时,可用于获取VMware内存大小,当大于0时则说明处于虚拟机中。代码分析如下:
//查询I/O通信端口
BOOL CheckVMWare1()
{
BOOL bResult = TRUE;
__try
{
__asm
{
push edx
push ecx
push ebx //保存环境
mov eax, 'VMXh'
mov ebx, 0 //将ebx清零
mov ecx, 10 //指定功能号,用于获取VMWare版本,为0x14时获取VM内存大小
mov edx, 'VX' //端口号
in eax, dx //从端口edx 读取VMware到eax
cmp ebx, 'VMXh' //判断ebx中是否包含VMware版本’VMXh’,若是则在虚拟机中
setz[bResult] //为零 (ZF=1) 时设置字节
pop ebx //恢复环境
pop ecx
pop edx
}
}
__except (EXCEPTION_EXECUTE_HANDLER) //如果未处于VMware中,则触发此异常
{
bResult = FALSE;
}
return bResult;
}
8、枚举系统服务并匹配特定的虚拟机相关服务名称来判断
通过枚举系统服务的状态,循环遍历枚举到的服务状态,检查是否有与虚拟机相关的服务。
BOOL CheckVM()
{
//打开系统服务控制器
SC_HANDLE SCMan = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (SCMan == NULL) {
//cout << GetLastError() << endl;
//printf("OpenSCManager Eorror/n");
return FALSE;
}
//保存系统服务的结构
LPENUM_SERVICE_STATUSA service_status;
DWORD cbBytesNeeded = NULL;
DWORD ServicesReturned = NULL;
DWORD ResumeHandle = NULL;
service_status = (LPENUM_SERVICE_STATUSA)LocalAlloc(LPTR, 1024 * 64);
if (service_status == nullptr)
return FALSE;
//获取系统服务的简单信息
bool ESS = EnumServicesStatusA(SCMan, //系统服务句柄
SERVICE_WIN32, //服务的类型
SERVICE_STATE_ALL, //服务的状态
(LPENUM_SERVICE_STATUSA)service_status, //输出参数,系统服务的结构
1024 * 64, //结构的大小
&cbBytesNeeded, //输出参数,接收返回所需的服务
&ServicesReturned, //输出参数,接收返回服务的数量
&ResumeHandle); //输入输出参数,第一次调用必须为0,返回为0代表成功
if (ESS == NULL) {
//printf("EnumServicesStatus Eorror/n");
return FALSE;
}
const char* vms[] = {
"VMware Tools",
"VMware 物理磁盘助手服务",
"VirtualBox Guest",
"Virtual Machine",
};
for (uint32_t i = 0; i < ServicesReturned; i++) {
for (int n = 0; n < ARRAYSIZE(vms); n++) {
if (strstr(service_status[i].lpDisplayName, vms[n]) != NULL)
return TRUE;
}
}
if (service_status != nullptr)
LocalFree(service_status);
CloseServiceHandle(SCMan);
return FALSE;
}