虚拟机检测手段

  学习一下虚拟机检测的手段,在网上找了一些,练习一下,顺便学习一下涉及的其他知识。
  但是由于现在虚拟机技术愈发大的发展,虚拟机检测技术作用也愈发的小了,然而学以致用,或许在其他方面有所帮助。

代码编辑时的配置:
  UNICODE字符集;
  VS2015 WinXP平台;
  静态编译。

  或许由于调用过多CRT函数,编译出来的程序体积约几百Kb。

  部分虚拟机的检测方法对现在的虚拟机系统不起作用,但还是记录一下,使用方式涉及的知识可以了解一下。

/*
各种虚拟机探测方法练习
*/

//#include "windows.h"
#include "stdio.h"
#include "Winsock2.h"
#include "IPHlpapi.h"
#include "tlhelp32.h"
#pragma comment(lib, "iphlpapi.lib")  // GetAdaptersAddresses

/* 
通过执行特权指令探测Vmware 
因为在虚拟机中指定功能号0xa则会从指定端口获取虚拟机版本信息到指定的目的操作数地址
另外0x14则是获取虚拟机内存大小,当获取的值大于0说明在虚拟机中*/
BOOL detectVmwareByPrivilegeAsmInstruction() {

	BOOL rv = FALSE;
	//DWORD val = 0, val_1 = 0;
	__try {
		__asm {
			pushad
			mov eax, 'VMXh'	// magic value
			mov ebx, 1		// 设置未非'VMXh'值,接收in指令的返回值(VMware 版本)
			// 如果程序运行在虚拟机中,magic值将被移至ebx中
			// https://www.aldeid.com/wiki/VMXh-Magic-Value
			mov ecx, 0ah	//功能号
			mov edx, 'VX'	//端口号
			in eax, dx
			/*mov val, eax
			mov val_1, ebx*/
			cmp ebx, 'VMXh'
			setz [rv]
			popad
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {	// 不在虚拟机中则触发异常
		rv = FALSE;
	}
	//printf_s("%#x   %#x\n", val, val_1);
	return rv;
}

/*
通过IDT基址来判断虚拟机,
RedPill作者发现在Vmware虚拟机上的IDT地址高字节通常为0xff;VirtualPC则是0ze8;
在真是主机上通常为0x80。因此redpill利用判断执行SIDT指令后返回的第一个字节是否大于0xd0来确定是否程序是否运行于虚拟机中
typedef struct
{
	WORD IDTLimit;		// IDT的大小
	WORD LowIDTbase;	// IDT的低位地址
	WORD HiIDTbase;		// IDT的高位地址
} IDTINFO;
但是需满足机器上只有一个处理器,多核
而且经过测试似乎对于最近版本VMware(测试环境是VM 12.0,Winxp sp3)无效*/
BOOL detectVmByIDTBaseAddr() {
	/*\x0f\x01\x0d为sidt指令,\xc3 为 ret;相当于
	sidt[pos]
	retn*/
	unsigned char m[2+4], rpill[] = "\x0f\x01\x0d\x00\x00\x00\x00\xc3";
	*((unsigned*)&rpill[3]) = (unsigned)m;	// 设置读取的 sidt 地址保存位置地址
	DWORD oldProtec = NULL;
	VirtualProtect(rpill, sizeof(rpill), PAGE_EXECUTE_READWRITE, &oldProtec);
	((void(*)())&rpill)();	//执行 rpill 指令
	VirtualProtect(rpill, sizeof(rpill), oldProtec, &oldProtec);

	printf_s("\tidt base address: %#x\n", *(DWORD*)&m[2]);	// idt前两个字节为idt大小
	if (m[5] > 0xd0)
		return TRUE;
	return FALSE;
}

/*虚拟机中的GDT(全局描述表)和LDT(本地描述表)与这真实主机中的基址并不相同
可以通过SGDT和SLDT指令获取;
LDT基址位于0x0000(两个字节)时为真实主机,否则为虚拟机;GDT位于0xffxxxxxx(四个字节)说明位于虚拟机中,反之真实主机*/
BOOL detectVmByGDTAndLDTBaseAddr() {
	INT cnt = 0;

	/*LDT
	经过测试,目前的方法得到的值 LDT base 同样为0x0000,所以无效*/
	WORD ldtBase = 0;
	__asm sldt ldtBase
	printf("\tLDT base address: %#x\n", ldtBase);
	if (ldtBase)
		cnt++;

	/*GDT
	同样无效*/
	CHAR gdt[6];
	__asm sgdt gdt
	DWORD gdtBase = *(unsigned int*)&gdt[2];
	printf("\tGDT base address: %#x\n", gdtBase);
	if (gdt[2] == 0xff)
		cnt++;

	if (cnt > 0)
		return TRUE;
	
	return FALSE;
}

/*
通过STR指令获取TSS(task state segment)的段选择器;虚拟机中,读取的地址往往为0x0040xxxx,
否则为真实主机*/
BOOL detectVmBySTRGetTSSbase() {
	
	char tssSeg[4] = { 0 };

	/*测试无效*/
	__asm str tssSeg
	printf("\tGet base address base address: 0x");
	for (int i = 0; i < 4; i++)
		printf("%02x", tssSeg[i]);
	printf("\n");

	if(tssSeg[0] == 0x00 && tssSeg[1] == 0x40)
		return TRUE;
	return FALSE;
}

/*
通过在注册表中查找和VMware的虚拟硬件或VMwareTools等相关表项进行判别,
可以通过在注册表中搜索VMware或VMware Tool等关键词查找路径*/
BOOL detectVmByReg() {

	/*测试HKEY_CLASSES_ROOT\Applications\VMwareHostOpen.exe项*/
	HKEY phKey;
	if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"Applications\\VMwareHostOpen.exe", &phKey))
		return TRUE;

	return FALSE;
}

/*
由于指令在虚拟机中执行速度远远不如宿主机中的,可以根据指令执行的时间差来识别;
通过 'rdtsc' 指令可以将计算机启动以来的CPU运行周期( time-stamp counter)读至edx:eax;
 time-stamp counter存在一个64-bit MSR中。
 获取之后,高32 bits放入edx,低32 bits则是在eax中。
 比如xchg指令,在宿主机中测试运行之后明显远远小于在虚拟机中的时间*/
BOOL detectVmByTimeInterval() {

	BOOL retu = FALSE;
	CHAR prin[] = "\ttime interval: %#x\n";
	__asm {
		pushad
		rdtsc
		xchg eax, ecx
		rdtsc
		sub eax, ecx
		push eax
		push eax
		lea edx, prin
		push edx
		call ds:printf
		add esp, 0x8
		pop eax
		cmp eax, 0xff
		popad
		jb BACK
		mov retu, 1
	}
BACK:
	return retu;
}

/*
mac地址的前3个字节标识网络硬件制造商;
相同虚拟机软件中虚拟机的mac地址往往是相同的或者不变的,可以根据这个来识别虚拟机*/
BOOL detectVmByMacAddress() {
	BOOL flag = FALSE;
	CHAR buff[20] = { 0 };
	CONST ULONG MAX_TIMES = 3;

	ULONG family = AF_UNSPEC;	// both ipv4 and ipv6
	ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
	ULONG size = (ULONG)sizeof(IP_ADAPTER_ADDRESSES);
	PIP_ADAPTER_ADDRESSES pAdapterAddress = NULL;

	ULONG val = 0, ite = 0;
	do {
		pAdapterAddress = (PIP_ADAPTER_ADDRESSES)malloc(size);
		if (pAdapterAddress == NULL) {
			printf("Alloc Memory Failed!\n");
			return FALSE;
		}
		val = GetAdaptersAddresses(family, flags, 0, pAdapterAddress, &size);

		if (val == ERROR_BUFFER_OVERFLOW) {
			free(pAdapterAddress);
			pAdapterAddress = NULL;
		}
	} while ((val == ERROR_BUFFER_OVERFLOW) && (ite++PhysicalAddressLength != 0) {
				sprintf_s(buff, "%02x-%02x-%02x-%02x-%02x-%02x", pAdapterAddress->PhysicalAddress[0],
					pAdapterAddress->PhysicalAddress[1], pAdapterAddress->PhysicalAddress[2], 
					pAdapterAddress->PhysicalAddress[3], pAdapterAddress->PhysicalAddress[4], 
					pAdapterAddress->PhysicalAddress[5]);
				if (!wcsstr(pAdapterAddress->Description, L"VMnet")) {	// 排除可能是宿主机中虚拟网卡的情况
					printf_s("%s", buff);
					buff[8] = 0;
					if (!strcmp(buff, "00-05-69") || !strcmp(buff, "00-0c-29") || !strcmp(buff, "00-50-56") || /*VMware*/
						!strcmp(buff, "00-03-ff") ||  /*VirtualPC*/
						!strcmp(buff, "08-00-27")  /*VirtualBox*/
						) { 
						flag = TRUE;
					}
				}
				else printf("VmNet");
				printf_s("\n");
			}
			else printf_s("\n");
			pAdapterAddress = pAdapterAddress->Next;
			ite++;
		}
	}
	else {
		free(pAdapterAddress);
		pAdapterAddress = NULL;
	}
	return flag;
}

/*
通过判断Vm相关的进程进行判断,比如Vmware中的vmtoolsd.exe,
但是有时候进程并未在执行可能无法获取*/
BOOL detectVmByVMProcess() {

	BOOL flag = FALSE;
	WCHAR detecProc[] = L"vmtoolsd.exe";
	PROCESSENTRY32 pe32;
	HANDLE hSnapshot = NULL;
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnapshot == INVALID_HANDLE_VALUE) {
		printf_s("\tCreateToolhelp32Snapshot Failed!!");
		return FALSE;
	}
	pe32.dwSize = sizeof(PROCESSENTRY32); //调用前必须设置结构体中的dwSize为PROCESSENTRY32结构体的大小
	if (Process32First(hSnapshot, &pe32)) {
		do {
			if (!wcscmp(pe32.szExeFile, detecProc)) {
				printf_s("\trunning process:%ws\n", pe32.szExeFile);
				flag = TRUE;
				break;
			}
			//printf_s("\t%ws\n", pe32.szExeFile);
		} while (Process32Next(hSnapshot, &pe32));
	}
	CloseHandle(hSnapshot);
	return flag;
}

int main(int argc, char* argv[]) {

	BOOL flag = FALSE;

	/*通过执行特权指令实现探测 vmware*/
	printf("== detect by run `in` I/O instruction:\n");
	if (flag = detectVmwareByPrivilegeAsmInstruction())
		printf("\t\t[yes] detect vmware !\n\n");
	else 
		printf("\t\t[no] nothing found !\n\n");

	flag = false;
	/*使用idt基址检测虚拟机*/
	printf("=[x][invalid method now]= detect by retrieve idt base:\n");
	if (flag = detectVmByIDTBaseAddr()) 
		printf("\t\t[yes] detected vm !\n\n");
	else 
		printf("\t\t[no] nothing found !\n\n");

	flag = false;
	/*根据GDT和LDT基址检测虚拟机*/
	printf("=[x][invalid method now]= detect by retrieve idt base:\n");
	if (flag = detectVmByGDTAndLDTBaseAddr())
		printf("\t\t[yes] detected vm !\n\n");
	else
		printf("\t\t[no] nothing found !\n\n");

	flag = false;
	/*运行STR(非特权)指令,获取TR(任务寄存器)中的端选择器,虚拟机和真实主机之间是不同的。
	当地址等于0x0040xxxx时,说明处于虚拟机中;否则为真实主机*/
	printf("=[x][invalid method now]= detect by retrieve TSS base:\n");
	if (flag = detectVmBySTRGetTSSbase())
		printf("\t\t[yes] detected vm !\n\n");
	else
		printf("\t\t[no] nothing found !\n\n");

	flag = false;
	/*通过检测虚拟机注册表和Vmware相关的表项判别*/
	printf("== detect by register:\n");
	if (flag = detectVmByReg())
		printf("\t\t[yes] detected vm !\n\n");
	else
		printf("\t\t[no] nothing found !\n\n");

	flag = false;
	/*由于指令在虚拟机中执行速度远远不如宿主机中的,可以根据此来识别*/
	printf("== detect by instruction execute interval:\n");
	if (flag = detectVmByTimeInterval())
		printf("\t\t[yes] detected vm !\n\n");
	else
		printf("\t\t[no] nothing found !\n\n");

	flag = false;
	/*通过虚拟机mac地址的前3字节(网络硬件制造商编号)进行判别*/
	printf("== detect by MAC address:\n");
	if (flag = detectVmByMacAddress())
		printf("\t\t[yes] detected vm !\n\n");
	else
		printf("\t\t[no] nothing found !\n\n");

	flag = false;
	/*通过进程中是否有VM相关进程在执行,进行判别*/
	printf("== detect by Vm process:\n");
	if (flag = detectVmByVMProcess())
		printf("\t\t[yes] detected vm !\n\n");
	else
		printf("\t\t[no] nothing found !\n\n");

	return 0;
}

参考文章:
  [1].详解反虚拟机技术
  [2].虚拟机检测技术剖析

你可能感兴趣的:(虚拟机检测手段)