检测程序是否正在虚拟机中运行






4.86/5 (99投票s)
一篇演示如何通过编程方式检查代码是否在虚拟机(如 VPC 或 VMWare)中运行的文章。

引言
本文将演示应用程序如何检测其是否正在虚拟机软件内部运行。
本文中的代码将检测两种知名的机器虚拟化软件
- Microsoft 的 Virtual PC(原 Connectix 产品)。
- VMWare(来自 VMWare.com)。
Bochs 或 Plex86 等其他虚拟机软件不包含在本文章中。
最好读者对 Intel x86 汇编语言有一个大致的了解,以便更好地理解代码的工作原理,但我会尽力用通俗易懂的语言解释这些技术。
请注意,每当我使用“虚拟机软件”一词时,我指的是像 Virtual PC 或 VMWare 这样的软件。当使用“虚拟机”一词时,指的是模拟的机器,通常运行着一个操作系统。
关于虚拟机软件的一些信息
虚拟机软件是通过软件(代码)模拟给定机器的架构,而不是依赖硬件,从而允许代码在虚拟机中执行,就好像它是在真实机器上运行一样。
时至今日,这些软件离完美还有很长的路要走,并且由于模拟给定机器的每个组件所涉及的复杂性,模拟一个给定的真实机器仍然面临许多挑战。
Virtual PC 和 VMWare 都允许您安装“插件”来加速仿真,允许从真实桌面拖放内容到虚拟桌面,并允许在真实机器和虚拟机之间共享文件。
为了完成这项任务,必须存在虚拟机软件与虚拟机本身之间的通信机制。
这种接口称为“后门接口”,因为通过一种特殊/未公开的机制,某些命令可以被(虚拟机软件)以不同的方式执行和解释,而不是被真实机器解释。
接下来,我将介绍如何判断您的软件是正在真实机器上执行还是在虚拟机软件中执行(涵盖 Virtual PC 和 VMWare)。
如何检测 Virtual PC
如您可能已经知道,每台机器都有一个定义的指令集,通常称为指令集架构(ISA)。
当遇到无效指令(ISA 中不存在的指令)时,机器会引发“无效操作码”类型的异常。软件可以处理该异常(使用常规的 try/catch 机制),让操作系统处理该异常,或者在最坏的情况下导致机器崩溃。
Virtual PC 使用一系列无效指令来实现虚拟机与 Virtual PC 软件之间的接口。
以下是 Virtual PC 的虚拟机与 Virtual PC 通信时发生的情况:
- 程序设置异常处理器(try/catch块)。
- 在调用 VM 软件之前设置必要的参数。
- 发出特殊的“无效操作码”指令。
- VM 软件会识别此无效操作码并据此采取行动,如果存在 VPC,则不会引发异常,如果不存在 VPC,则会引发异常。
- 程序的“catch”块将处理异常并检查返回的参数是否存在 VM 软件。
简而言之,Virtual PC 使用“无效操作码”机制作为后门。
以下代码显示了如何检测 Virtual PC 的存在
// IsInsideVPC's exception filter
DWORD __forceinline IsInsideVPC_exceptionFilter(LPEXCEPTION_POINTERS ep)
{
  PCONTEXT ctx = ep->ContextRecord;
  ctx->Ebx = -1; // Not running VPC
  ctx->Eip += 4; // skip past the "call VPC" opcodes
  return EXCEPTION_CONTINUE_EXECUTION;
  // we can safely resume execution since we skipped faulty instruction
}
// High level language friendly version of IsInsideVPC()
bool IsInsideVPC()
{
  bool rc = false;
  __try
  {
    _asm push ebx
    _asm mov  ebx, 0 // It will stay ZERO if VPC is running
    _asm mov  eax, 1 // VPC function number
    // call VPC 
    _asm __emit 0Fh
    _asm __emit 3Fh
    _asm __emit 07h
    _asm __emit 0Bh
    _asm test ebx, ebx
    _asm setz [rc]
    _asm pop ebx
  }
  // The except block shouldn't get triggered if VPC is running!!
  __except(IsInsideVPC_exceptionFilter(GetExceptionInformation()))
  {
  }
  return rc;
}
关于代码的更多细节
- 安装异常处理器。
- 准备输入寄存器“eax”和“ebx”。
- 发出无效指令 0x0F 0x3F 0x07 0x0B。此无效指令类似于函数标识符,它告诉 Virtual PC 具体做什么。对于其他功能,Virtual PC 使用其他无效指令。
- 在异常处理器中 -> 修改寄存器以标记 VPC 的不存在(如果触发异常,则将 EBX设置为 -1 -> VPC 不存在)。
- 从异常返回并恢复执行(仅当 VPC 不存在时)。
- 相应地检查返回的寄存器。
如何检测 VMWare
Intel x86 提供了两个指令允许您执行 I/O 操作,这些指令是“IN”和“OUT”指令。这两种指令是特权指令,不能在用户模式(在保护模式下)进程中使用,除非启用了必要的特权,因此在正常情况下使用它们将导致“EXCEPTION_PRIV_INSTRUCTION”类型的异常。
VMWare 使用“IN”指令从一个特殊端口读取。这个端口实际上不存在,但是当 VMWare 存在时,该端口将是虚拟机与 VMWare 之间的接口。
这是代码
bool IsInsideVMWare()
{
  bool rc = true;
  __try
  {
    __asm
    {
      push   edx
      push   ecx
      push   ebx
      mov    eax, 'VMXh'
      mov    ebx, 0 // any value but not the MAGIC VALUE
      mov    ecx, 10 // get VMWare version
      mov    edx, 'VX' // port number
      in     eax, dx // read port
                     // on return EAX returns the VERSION
      cmp    ebx, 'VMXh' // is it a reply from VMWare?
      setz   [rc] // set return value
      pop    ebx
      pop    ecx
      pop    edx
    }
  }
  __except(EXCEPTION_EXECUTE_HANDLER)
  {
    rc = false;
  }
  return rc;
}
- 程序设置异常处理器(如果 VMWare 不存在,我们只需忽略它的存在)。
- 将魔术数字 0x564D5868(或“VMXh”)设置到 EAX寄存器。
- 将 EBX寄存器设置为任意值,但不能是魔术数字。
- 将“函数编号”值设置到 ECX寄存器。值 10 表示获取 VMWare 版本,其他代码表示其他功能。
- 将魔术端口号 0x5658(或“VX”)设置到 DX。此特殊端口号允许在 VMWare 存在时与其进行接口。
- 从该端口读取到 EAX。- 当 VMWare 不存在时,将发生异常,我们将忽略 VMWare 的存在。
- 否则,代码流将继续。
 
- EBX现在应该读取魔术数字值。
- 如果是,则 VMWare 存在。
使用代码
本文附带了一个用 C#/VB.NET 和 VC++ 编写的示例 GUI,允许您检测 VMWare 或 Virtual PC 的存在。
C++ 代码使用“DetectVM.cpp/.h”,它导出了以下函数
bool IsInsideVPC(); bool IsInsideVMWare();
C#/VB.NET 代码使用以下只读属性
System::Boolean IsInsideVPC System::Boolean IsInsideVMWare
结束语
希望您在阅读本文和使用代码时有所收获。感谢 CodeProject 成员持续的支持和高质量的文章/代码。
特别感谢 Ken Kato 先生,感谢他 的工作,使我能够了解 VMWare 的后门接口。
历史
- 2005 年 3 月 14 日 - 文章的初始版本仅包含 VC++ 示例代码。
- 03/30/2005
	- 更新了文章的 IsInsideVPC说明。
- 使用更少的 ASM 代码重写了 IsInsideVPC()和IsInsideVMWare()。
- 添加了 .NET 示例(感谢 unruledboy 的 .NET 代码贡献)。
 
- 更新了文章的 


