虚拟机检测

本文最后更新于:2021-09-30 上午

前言

现在虚拟机的应用是越来越广泛了,不论是现在流行的网游还是一些病毒都会有自己的反虚拟机机制,判断其是否是运行在虚拟机之中。我现在也学习并记录一下一些虚拟机检测的手段,因为我只使用过VMware,所以我这里只记录自己对于VMware的检测,其他类型的虚拟机暂时没有用过,所以先暂且不提。

我个人对于虚拟机检测的理解,就是检测其中的各种特征,因为虚拟机运行和真实环境是有差距的,比如一些特定的服务,硬件名称等。

进程

在虚拟机中运行的时候,是有特定的进程的,比如图中两个进程

这时就可以编写代码去检测是否存在指定的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BOOL IfProcExist(WCHAR* Procname)
{
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);//拍摄快照
BOOL flag = Process32First(hSnap, &pe32);
while (flag)
{
if (!wcscmp(Procname, pe32.szExeFile))//判断是否是指定进程
{
return TRUE;//找到返回TRUE
}
else
{
flag = Process32Next(hSnap, &pe32);//没有找到搜索下一个
}
}
return FALSE;//没有找到,不存在指定进程,返回FALSE
}

当在虚拟机运行时,检测到存在指定的进程

不过这两个进程都是vmtool的进程,当我把vmtool卸载之后,就没有这两个进程了。所以如果在一个没有vmtool的环境中,可能还需要去寻找其他的一些特征来进行检测。

注册表

虚拟机环境中也有许多的标志是虚拟机的注册表,可以通过查询这些注册表来判断是否处在虚拟环境之中。

比如HKEY_LOCAL_MACHINE\SOFTWARE\VMware, Inc.\VMware Drivers这个表项

可以通过尝试打开此表项,判断是否存在来作为判断依据。

1
2
3
4
5
6
7
8
9
BOOL OpenReg()
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\VMware, Inc.\\VMware Drivers", 0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)//是否能打开此表项
{
return FALSE;//打不开返回FALSE
}
return TRUE;//打开返回TRUE
}

同时经过我测试发现,此表项也是vmtool的表项,如果卸载掉vmtool的话,此表项就不存在了。不过可用于判断的注册表项有许多,可以自己进行搜索并且实验来进行判断。我这里找到了如图的这个表项,应该是bios信息。那么可以根据这个来进行判断,比如查询SystemManufacturer中的值是否是VMware, Inc.来判断是否是虚拟机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
BOOL CheckRegValue()
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\BIOS", 0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)//打开注册表
{
return FALSE;
}
DWORD dwType;
WCHAR data[100];
DWORD len = 100;
RegQueryValueEx(hKey, L"SystemManufacturer", NULL, &dwType, (BYTE*)data, &len);//查询键值
RegCloseKey(hKey);
if (wcscmp(data, L"VMware, Inc.") == 0)//判断键值是否相等
{
return TRUE;//相等返回TRUE,虚拟机环境
}
return FALSE;
}

注册表里的信息非常的多,比如这里还有显卡的相对应信息,显卡名称,这也可以用来检测。具体的话,可以自己在虚拟机中的注册表内查找可以用于检测的特征。

MAC地址

虚拟机的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

那么就可以通过判断当前系统的MAC地址的开头地址来判断是否处于虚拟机之中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL CheckMacAddr()
{
ULONG ulSize = 0;
PIP_ADAPTER_INFO pInfo = NULL;
GetAdaptersInfo(pInfo, &ulSize);//获取缓冲区大小
pInfo = (PIP_ADAPTER_INFO)malloc(ulSize);
GetAdaptersInfo(pInfo, &ulSize);//获取适配器信息
if ((pInfo->Address[0] == 0x00 && pInfo->Address[1] == 0x50 && pInfo->Address[2] == 0x56) ||
(pInfo->Address[0] == 0x00 && pInfo->Address[1] == 0x1C && pInfo->Address[2] == 0x14) ||
(pInfo->Address[0] == 0x00 && pInfo->Address[1] == 0x0C && pInfo->Address[2] == 0x29) ||
(pInfo->Address[0] == 0x00 && pInfo->Address[1] == 0x05 && pInfo->Address[2] == 0x69))//判断MAC地址前几位是否相同
{
return TRUE;//相同返回TRUE
}
return FALSE;
}

特定的文件

在虚拟机中有一些驱动文件,可以通过查询这些文件是否存在来判断。

也不仅仅是这些驱动的文件,可以通过自己的查找,用其他的一些文件来做特征。

1
2
3
4
5
6
7
8
BOOL IfFileExist(char* FilePath)
{
if (_access(FilePath, 0) == 0)//判断文件是否存在,0代表仅检查是否存在
{
return TRUE;//存在返回TRUE
}
return FALSE;
}

网关检测

一般虚拟机中的网关地址如果不进行修改的话,最后一位都是2,那么可以通过检测网关地址的最后一位是否是2,来进行判断当前运行的环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL CheckGateway()
{
ULONG ulSize = 0;
PIP_ADAPTER_INFO pInfo = NULL;
GetAdaptersInfo(pInfo, &ulSize);//获取缓冲区大小
pInfo = (PIP_ADAPTER_INFO)malloc(ulSize);
GetAdaptersInfo(pInfo, &ulSize);//获取适配器信息
char IP[100];
sprintf(IP, "%s", pInfo->GatewayList.IpAddress.String);//网关的信息
int len = strlen(IP);
char* a = &IP[len - 1];
if (strcmp("2", a) == 0)//判断最后一位是否等于2
{
return TRUE;//相等返回TRUE
}
return FALSE;
}

特权指令

Vmware为主机与虚拟机之间提供了相互沟通的通讯机制,它使用“IN”指令来读取特定端口的数据以进行两机通讯,但由于IN指令属于特权指令,在处于保护模式下的真机上执行此指令时,除非权限允许,否则将会触发类型为“EXCEPTION_PRIV_INSTRUCTION”的异常,而在虚拟机中并不会发生异常,在指定功能号0A(获取VMware版本)的情况下,它会在EBX中返回其版本号“VMXH”,可以通过此返回值判断是否处于虚拟机中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
BOOL CheckIN()
{
BOOL flag = FALSE;
__try
{
__asm
{
pushad
mov eax,'VMXh'
xor ebx,ebx
mov ecx,0x0A //功能号0x0A
mov edx,'VX'
in eax,dx
cmp ebx,'VMXh' //判断返回值是否是VMXh
je _vm //是的话返回TRUE
jmp _exit
_vm:
mov eax,TRUE
mov flag,eax
_exit:
popad
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
flag = FALSE;
}
return flag;
}

CPUID

当eax=1时,运行CPUID之后,ecx中的值(转为二进制)如果最高位为1,那么就是在虚拟环境,否则不是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BOOL CPUID()
{
BOOL flag;
__asm
{
pushad
pushfd
mov eax,1
cpuid
and ecx,0x80000000 //只留最高位进行判断
test ecx,ecx //判断ecx寄存器是否为空,为空代表非虚拟机环境返回FALSE,否则返回TRUE
jz _Not
mov flag,TRUE
jmp _end
_Not:
mov flag,FALSE
_end:
popfd
popad
}
return flag;
}

当eax中的值是0x40000000时,ebx、ecx、edx三个寄存器中的返回值加起来正好是“VMWareVMWare”,可以把这个程序拖入OD,直接进行修改来查看。

总结

总结了一些可以使用的虚拟机检测的方式,但是感觉还是比较片面的,其中一些检测的特征也可以通过手动的方式来进行修改。

感觉反虚拟机还是游戏方面做的更好一些,如果能把网游反虚拟机的机制学习清楚的话应该还能学到更多的东西。

参考链接

http://blog.nsfocus.net/malicious-sample-analysis-manual-virtual-machine-test-bottom/

https://www.freebuf.com/articles/system/202717.html

https://www.write-bug.com/article/1822.html

https://bbs.pediy.com/thread-219955.htm