题
有没有办法从虚拟机内识别您的代码正在虚拟机内运行?
我想有或多或少简单的方法来识别特定的虚拟机系统,特别是如果虚拟机安装了提供商的扩展(例如 VirtualBox 或 VMWare)。但是有没有一个通用的方法来识别你不是直接在CPU上运行的呢?
解决方案
对此的大量研究致力于检测所谓的“蓝色药丸”攻击,即主动尝试逃避检测的恶意虚拟机管理程序。
检测虚拟机的经典技巧是填充 ITLB,运行一条指令 必须 进行虚拟化(当将控制权交给虚拟机管理程序时,必然会清除此类处理器状态),然后运行更多代码来检测 ITLB 是否仍然填充。第一篇论文位于 这里, ,以及一个相当丰富多彩的解释 研究人员的博客 和替代方案 Wayback Machine 博客文章链接(图片损坏).
对此讨论的底线是,总有一种方法可以检测恶意虚拟机管理程序,并且检测不试图隐藏的虚拟机管理程序要简单得多。
其他提示
红帽有一个程序可以检测正在运行的虚拟化产品(如果有): virt-what
.
从长远来看,使用第三方维护的工具比尝试推出自己的检测逻辑是更好的策略:更多的眼睛(针对更多虚拟化产品进行测试)等。
更实证的方法是检查已知的 VM 设备驱动程序。您可以编写 WMI 查询来定位 VMware 显示适配器、磁盘驱动器、网络适配器等。如果您知道只需担心环境中已知的 VM 主机类型,那么这将是合适的。这是 在 Perl 中执行此操作的示例, ,可以移植到您选择的语言。
这取决于你想要什么:
如果虚拟机不是故意隐藏的,您可以使用一些已知的钩子。例如寻找 VmWare 驱动程序或内存中是否存在某些字符串或某些其他迹象。
如果虚拟机确实希望你为它做一些特殊的事情,它就会有一些明显的钩子,比如修改处理器的 ID 或添加一些你可以访问以检测它的特殊寄存器。或者是内存中已知位置的特殊设备(假设您可以对世界的物理内存空间进行原始访问)。请注意,IBM Power6 和 Sun UltraSparc T1/T2 等现代机器设计旨在始终运行虚拟机管理程序,而不是直接在原始硬件上运行。操作系统使用的“硬件”接口实际上是虚拟机管理程序软件层的接口,无法绕过它。在这种情况下,检测是微不足道的,因为它是一个恒定的“是”。对于所有能够承受开销的计算机系统来说,这可能是未来的方向,看看最近设计中的支持,例如飞思卡尔 QorIQ P4080 芯片 (www.freescale.com/qoriq)。
如果虚拟机故意试图隐藏,而您正在追逐它的存在,那么这就是一场猫捉老鼠的游戏,其中虚拟机的定时干扰和不同的性能配置文件几乎总是会泄露它。显然,这取决于 VM 的实现方式以及架构中有多少硬件支持(我认为 zSeries 大型机比常规 x86 更擅长隐藏特定操作系统下 VM 或 VM 堆栈的存在)例如,机器是)。看 http://jakob.engbloms.se/archives/97 关于这个话题的一些讨论。可以尝试隐藏为虚拟机,但如果足够努力,检测很可能总是获胜。
在大多数情况下,您不应该尝试这样做。除了少数特定情况外,您不应该关心是否有人在虚拟机中运行您的代码。
如果需要,在 Linux 中最常见的方法是查看 /sys/devices/virtual/dmi/id/product_name
, ,它将列出大多数真实系统上的笔记本电脑/主板的名称以及大多数虚拟系统上的虚拟机管理程序。 dmidecode | grep Product
是另一种常见的方法,但我认为这需要 root 访问权限。
这里有一个 (java + 窗口)解决方案来识别底层机器是物理机还是虚拟机。
虚拟机示例:
制造商
- Xen
- 微软公司
- 伊诺泰克有限公司
- 红帽
- 威睿公司
模型
- HVM域
- 虚拟机
- 虚拟盒子
- 键盘虚拟机
VMware虚拟平台
import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public abstract class OSUtil { public static final List<String> readCmdOutput(String command) { List<String> result = new ArrayList<>(); try { Process p=Runtime.getRuntime().exec("cmd /c " + command); p.waitFor(); BufferedReader reader=new BufferedReader( new InputStreamReader(p.getInputStream()) ); String line; while((line = reader.readLine()) != null) { if(line != null && !line.trim().isEmpty()) { result.add(line); } } } catch (Exception e) { e.printStackTrace(); } return result; } public static final String readCmdOutput(String command, int lineNumber) { List<String> result = readCmdOutput(command); if(result.size() < lineNumber) { return null; } return result.get(lineNumber - 1); } public static final String getBiosSerial() { return readCmdOutput("WMIC BIOS GET SERIALNUMBER", 2); } public static final String getHardwareModel() { return readCmdOutput("WMIC COMPUTERSYSTEM GET MODEL", 2); } public static final String getHardwareManufacturer() { return readCmdOutput("WMIC COMPUTERSYSTEM GET MANUFACTURER", 2); } public static void main(String[] args) { System.out.println("BIOS Serial: " + getBiosSerial()); System.out.println("Hardware Model: " + getHardwareModel()); System.out.println("Hardware Manufacturer: " + getHardwareManufacturer()); } }
您可以使用输出来确定它是虚拟机还是物理机:
物理机输出:
BIOS 序列号:2HC3J12
硬件型号:灵越7570
硬件制造商:戴尔公司
虚拟机输出:
BIOS 序列号:0
硬件型号:伊诺泰克有限公司
硬件制造商:虚拟盒子
一个很好的例子是,显然对主板制造商进行了 WMI 查询,如果它返回“Microsoft”,则表明您位于虚拟机中。我以为这仅适用于 VMWare。可能有不同的方法来区分每个虚拟机主机软件。
这篇文章在这里 http://blogs.technet.com/jhoward/archive/2005/07/26/407958.aspx 有一些很好的建议和链接,提供了几种检测您是否在虚拟机中的方法(至少是 VMWare 和 VirtualPC)。
您可以通过查看网络连接的 MAC 地址来确定您是否位于虚拟机中。例如,Xen 通常建议使用特定的地址范围 00:16:3e:xx:xx:xx。
这并不能得到保证,因为由系统管理员指定他们喜欢的 MAC 地址。
TrapKIT提供ScoopyNG,VMware识别工具 ——它试图绕过规避技术,但不一定针对除 VMware 之外的任何虚拟化软件。源代码和二进制文件均可用。
如果虚拟机能够很好地完成工作,那么客户端应该看不到它正在被虚拟化。不过,我们还可以看看其他线索。
我认为寻找特定于虚拟机环境的已知驱动程序或软件将是最好的方法。
例如,在运行 Windows 的 VMWare 客户端上,vmxnet.sys 将是网络驱动程序,显示为 VMware 加速 AMD PCNet 适配器。