关于虚拟机检测技术的研究

平平无奇的搬砖日,突然想起来之前分析的一个病毒好像有某种反虚拟机的方法,当时没太研究明白,甚至都没研究明白是检测虚拟机的操作。现在想起来回过头去再研究一下,虚拟机的检测技术,在这个过程中借鉴了很多其他大神的经典文章(当然大多数都是人家的东西,我只不过是拾人牙慧)
相较而言,我们一般在分析病毒的时候,也会碰到一些反虚拟机反调试的操作。常见的反虚拟机的手段,无非就是遍历系统进程,检测特殊进程、检测特殊服务、检测注册表项等几种。国外SANS安全组织的研究人员总结出当前各种虚拟机检测手段不外乎以下四类:
● 搜索虚拟环境中的进程,文件系统,注册表;
● 搜索虚拟环境中的内存
● 搜索虚拟环境中的特定虚拟硬件
● 搜索虚拟环境中的特定处理器指令和功能
学习了几位大神早期分享的文章,并且根据里边的示例。自己做了一下测试,加了一些自己在测试过程中的理解。
这次主要针对虚拟环境的内存以及处理器指令的技术手段进行虚拟机环境的检测。
第一种:使用特权指令进行虚拟环境的检测:
Vmware为物理机与虚拟机之间提供了相互沟通的通讯机制,它使用“IN”指令来读取特定端口的数据以进行两机通讯,但由于IN指令属于特权指令,在处于保护模式下的真机上执行此指令时,除非权限允许,否则将会触发类型为“EXCEPTION_PRIV_INSTRUCTION”的异常,而在虚拟机中并不会发生异常,在指定功能号0A(获取VMware版本)的情况下,它会在EBX中返回其版本号“VMXH”;而当功能号为0x14时,可用于获取VMware内存大小,当大于0时则说明处于虚拟机中。
检测例程:

#include <stdio.h>
#include <Windows.h>
int main()
{
	bool rc = true;
	__try
	{
		__asm
		{
			push   edx
			push   ecx
			push   ebx

			mov    eax, 'VMXh'
			mov    ebx, 0  // 将ebx设置为非幻数’VMXH’的其它值
			mov    ecx, 10 // 指定功能号,用于获取VMWare版本,当它为0x14时用于获取VMware内存大小
			mov    edx, 'VX' // 端口号
			in     eax, dx // 从端口dx读取VMware版本到eax
	  //若上面指定功能号为0x14时,可通过判断eax中的值是否大于0,若是则说明处于虚拟机中
	  cmp    ebx, 'VMXh' // 判断ebx中是否包含VMware版本’VMXh’,若是则在虚拟机中
	  setz[rc] // 设置返回值

	  pop    ebx
	  pop    ecx
	  pop    edx
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)  //如果未处于VMware中,则触发此异常
	{
		rc = false;
	}
	if (rc)
		printf("在虚拟机环境");
	else
		printf("不在虚拟机");
	getchar();
	return rc;
}
#include <stdio.h>
#include <Windows.h>
int main()
{
	bool rc = true;
		__asm
		{
		  push   edx
		  push   ecx
		  push   ebx
	 
		  mov    eax, 'VMXh'
		  mov    ebx, 0  // 将ebx设置为非幻数’VMXH’的其它值
		  mov    ecx, 14h // 指定功能号,用于获取VMWare版本,当它为0x14时用于获取VMware内存大小
		  mov    edx, 'VX' // 端口号
		  in     eax, dx // 从端口dx读取VMware版本到eax
	//若上面指定功能号为0x14时,可通过判断eax中的值是否大于0,若是则说明处于虚拟机中
		  cmp    eax, 0 // 判断ebx中是否包含VMware版本’VMXh’,若是则在虚拟机中
		  JBE    Table
		  setz [rc] // 设置返回值
Table:
		  
		  pop    ebx
		  pop    ecx
		  pop    edx
		}

if(!rc)
	 printf("在虚拟机环境");
else
	 printf("不在虚拟机");
 getchar();
  return rc;
	
}

检测结果:
在这里插入图片描述
在这里插入图片描述
方法二:使用敏感指令进行虚拟环境监测:
VMM(Virtual Machine Monitor,虚拟机监视器)是一种可以在同一物理硬件上创建分离的多台(虚拟)机器的软件,如上所说,VMWare的VMM似乎只是作为一个应用程序运行在我们的操作系统里边,实际上,它可以运行在操作系统的底层,也就是我们常说的系统层,完全控制硬件。也就是说在这段时间里,物理机的虚拟内存和线性内存会被短暂的移除。
在这里插入图片描述
如上图所示,有几点需要注意:
1:虚拟机内存和物理机内存可以相互切换
2:操作系统完全控制物理机OS上下文
3:VMM完全控制虚拟机的上下文
4:每个上下文有自己的地址空间,中断描述符表,堆栈,执行上下文(也就是物理机和虚拟机都各自有自己的一套数据结构),从这里就可以着手去检测虚拟机的存在
接下来就是第二种检测虚拟机的方法:敏感指令检测虚拟机
利用IDT基址检测虚拟机的方法是一种通用方式,对VMware和Virtual PC均适用。中断描述符表IDT(Interrupt Descriptor Table)用于查找处理中断时所用的软件函数,它是一个由256项组成的数据,其中每一中断对应一项函数。中断描述符表IDT将每个异常或中断向量分别与它们的处理过程联系起来,IDT是由8字节长描述符组成的一个数组。IDT表可以驻留在线性地址空间的任何地方,处理器使用IDTR寄存器来定位IDT表的位置。我们可以使用敏感指令来获取需要的数据,以下敏感指令可以分别获取相应的寄存器的值。

  • SGDT:存储GDT寄存器
  • SIDT:存储IDT寄存器
  • SLDT:存储LDT寄存器
  • SMSW: 存储机器状态

LIDT和SIDT指令分别用于加载和保存IDTR寄存器的内容(中断描述符表寄存器,用于IDT在内存中的基址),LIDT指令用于把内存中的限长值和基地址操作数加载到IDTR寄存器中。该指令仅能由当前特权级CPL是0的代码执行,通常被用于创建IDT时的操作系统初始化代码中。SIDT指令用于把IDTR中的基地址和限长内容复制到内存中,该指令可在任何特权级上执行。
SIDT指令是以如下格式来存储IDTR的内容

typedef struct
{
    WORD IDTLimit;      // IDT的大小
    WORD LowIDTbase;    // IDT的低位地址
    WORD HiIDTbase; // IDT的高位地址
} IDTINFO;

由于在使用的时候IDTR是唯一存在的,不可能存在两个IDTR表同时起作用。但是存在两个操作系统,也就是我们的物理机操作系统和虚拟机操作系统。为了防止二者冲突所以VMM会修改虚拟机中的IDT地址,这也就造成了物理机和虚拟机IDT地址的差异,我们可以通过这个差异来进行虚拟机的检测。
使用一句话代码读取到IDT表的基址:
一句汇编读取到IDT的位置

_asm sidt idtr;

(我在实际测试过程中发现当当IDT基址小于0xd0xxxxxx时则说明程序处于VMware虚拟环境中,但是这个具体的实用性还有待检测,我在Win10 64位物理及检测的时候发现并不是很准确)
测试例程:

#include <stdio.h>
int main () {

	unsigned char m[2+4], rpill[] = "\x0f\x01\x0d\x00\x00\x00\x00\xc3";   //相当于SIDT[adrr],其中addr用于保存IDT地址
	*((unsigned*)&rpill[3]) = (unsigned)m;    //将sidt[addr]中的addr设为m的地址
	((void(*)())&rpill)();    //执行SIDT指令,并将读取后IDT地址保存在数组m中
	printf ("idt base: %#x\n", *((unsigned*)&m[2]));  //由于前2字节为IDT大小,因此从m[2]开始即为IDT地址
	if (m[5]>0xd0) 
		printf("Not in Matrix!%#x\n", m[5]); //当IDT基址大于0x80xxxxxx时则说明程序处于VMware中
	else 
		printf ("in Matrix.%#x\n",m[5]);getchar();
	return 0;
}

测试结果:
在这里插入图片描述
在这里插入图片描述
以上是利用IDT基址的方式进行检测虚拟机的方式,同理可以使用LDT和GDT的方式进行虚拟机的检测。

(在操作系统中,全局描述符表GDT只有一张,一个处理器对应一个GDT,同理为了防止冲突,虚拟机的GDT和物理机的GDT必然有差别,我们可以根据这个数值的差别进行判断)

使用LGDT指令将GDT的入口地址装入寄存器GDTR,便于CPU在殉职的时候使用,我们可以通过SGDT指令来获取GDTR寄存器的数据;同理使用LLDT指令将选择子装在到LDTR寄存器里边,使用SLDT来获取LDTR寄存器的数值。
测试例程:

#include <stdio.h>
void GDTDetect(void)
{
	unsigned int gdt_addr = 0;
	unsigned char gdtr[4];

	_asm sgdt gdtr
	gdt_addr = *((unsigned int *)&gdtr[2]);
	printf("GDT BaseAddr:0x%x\n", gdt_addr);

	if ((gdt_addr >> 24) == 0x80)
	{
		printf("Inside VMware\n");
	}
	else
		printf("Native OS\n");
}

int main(void)
{
	GDTDetect();
	getchar();
	return 0;
}

测试结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
基于STR机制的检测方式
在保护模式下运行的所有程序在切换任务时,对于当前任务中指向TSS的段选择器将会被存储在任务寄存器中,TSS中包含有当前任务的可执行环境状态,包括通用寄存器状态,段寄存器状态,标志寄存器状态,EIP寄存器状态等等,当此项任务再次被执行时,处理器就会其原先保存的任务状态。每项任务均有其自己的TSS,而我们可以通过STR指令来获取指向当前任务中TSS的段选择器。任务寄存器拥有当前任务的TSS的段选择符和段描述符,TSS描述符仅可能存放在GDT中,不能存放在LDT或IDT中。指令LTR(加载任务寄存器)和STR(保存任务寄存器)加载和保存任务寄存器的可见部分。LTR指令让任务寄存器加载TSS描述符的段选择符,该指令只能运行在特权级0,该指令通常用来系统初始化时初始化任务寄存器。指令STR可以将任务寄存器的可见部分保存到通用寄存器或内存中。该指令可以运行在任何特权级。
这里STR(Store task register)指令是用于将任务寄存器 (TR) 中的段选择器存储到目标操作数,目标操作数可以是通用寄存器或内存位置,使用此指令存储的段选择器指向当前正在运行的任务的任务状态段 (TSS)。在虚拟机和真实主机之中,通过STR读取的地址是不同的,当地址等于0x0040xxxx时,说明处于虚拟机中,否则为物理机。(在实际测试过程中发现,与之相反当STR地址等于0x40的时候说明处于物理机状态,反之则是在虚拟机之中)
测试代码:

}*/
#include <stdio.h>
int main(void)
{
	unsigned char mem[4] = { 0 };
	int i;

	__asm str mem;
	printf(" STR base: 0x");
	for (i = 0; i < 4; i++)
	{
		printf("%02x", mem[i]);
	}

	if (mem[0] == 0x40)
		printf("\n Native OS!!\n");
	else
		printf("\n INSIDE MATRIX!!\n");
	getchar();
	return 0;
}

第三种方法:基于时间差的检测方式
顾名思义,就是通过特定的指令在虚拟机和真机运行的相对时间进行检测。可以通过RDTSC指令来实现,RDTSC指令是用于将计算机启动以来的CPU运行周期数存放到EDX:EAX里面,其中EDX是高位,而EAX是低位。
主要测试代码如下:

.data
szTitle         db  "VMDetect With RDTSC", 0h
szInsideVM      db  "Inside VMware!", 0h
szOutsideVM     db  "Native OS!", 0h
 
.code
 
start:
    RDTSC
    xchg        ecx, eax
    RDTSC  
    sub     eax, ecx
    cmp     eax, 0FFh
    jg      Detected
     
    invoke  MessageBox, 0, offset szOutsideVM, offset szTitle, 0
    ret
     
Detected:
    invoke  MessageBox, 0, offset szInsideVM, offset szTitle, 0
    ret
end start

对抗方法:
对于以上列出的几种检测虚拟机的方式,包括未列出的检测注册表项、检测特定进程、检测硬件指纹、检测特定服务的方法都有相应的对抗手段。
在开启虚拟化的情况下,以上的大部分检测方式均会失效。至于硬件指纹,VN的硬件特征有很多,比较常见的检测方式会检测VMWare的MAC地址,VMware默认的网卡MAC地址前缀为“00-05-69,00-0C-29或者00-50-56
。可以使用相应的工具修改特征来躲避检测。

总结:
在安全对抗愈演愈烈的现在,网络安全问题已然是一个不容忽视的重大问题,而虚拟机作为网络安全从业者必不可少的辅助工具,对于网络安全的贡献也是不容小觑的,之后必然也会是兵家必争之地,虚拟机的检测以及对抗也必然会成为攻城略地的重中之重,随着VMWare的不断更新升级以及Hacker的技术手段不断升级,相信会有越来越多的检测虚拟机的技术手段,同时兵来将挡水来土掩,也一定会有更多的反反虚拟机的技术手段产生,生生不息,安全至上。
参考链接:
https://bbs.pediy.com/thread-119969-1.htm
https://bbs.pediy.com/thread-228395.htm
https://bbs.pediy.com/thread-229571.htm
https://www.ibm.com/developerworks/cn/linux/l-cn-vt/index.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值