當前位置: 華文星空 > 知識

作業系統能否知道自己處於虛擬機器中?

2019-12-03知識

目前虛擬機器環境檢測有兩個「金標準」,分別是Al-khaser和Pafish。這兩個開源計畫幾乎一網打盡了所有公開常見的VM檢測技術。下面簡要分析一下它們的技術原理。

一、硬體資訊檢測

首先大概說說作業系統是怎麽知道這台電腦安了哪些裝置的。電腦啟動的時候,主機板固件會給OS傳兩個資訊表,分別是ACPI和SMBIOS。ACPI表有很多部份,其中硬體資訊主要集中在DSDT和SSDT這兩部份。

ACPI表的每個部份開頭都有一個OEM ID和OEM Table ID, 這是第一個容易露餡的地方 ,例如QEMU預設將OEM ID寫為BOCHS,將OEM Table ID寫為BXPC + 部份名稱,如DSDT部份就寫成「BXPCDSDT」。

虛擬機器的ACPI表中往往會存在一些現實中不存在的硬體,用於主客機間通訊, 這是第二個容易露餡的地方 ,例如QEMU的DSDT表中會有DBUG和FWCF這兩個硬體。

電腦中大部份裝置的資訊並不寫在ACPI和SMBIOS表中。像顯卡、音效卡、網卡、USB這些都屬於PCI裝置,與PCI控制器相連。PCI控制器本身提供一個介面,可以列出所有檢測到的PCI裝置。每個PCI裝置有四個用來亮明身份的代號,分別是Vendor ID, Device ID, Subsystem ID, class ID。一般來說,OS檢測到PCI裝置時,會首先根據Vendor ID和Device ID搜尋驅動;如果搜不到驅動,則會根據 class ID尋找是否有這一類裝置的通用驅動。

虛擬機器模擬出的PCI裝置,其身份代號往往采用特定數位, 這是第三個容易露餡的地方 ,例如QEMU模擬出的VirtIO裝置,其Vendor ID多為0x1AF4。

相比於ACPI,SMBIOS對系統的正常執行沒有那麽重要。但是Windows的「系統資訊」工具顯示的內容,多半都來自SMBIOS表, 這是第四個容易露餡的地方 ,例如目前主流的虛擬機器固件是OVMF,那麽虛擬機器裏面「系統資訊」就會顯示出OVMF的資訊。

有些惡意軟體會透過檢測系統是否有風扇和熱區域(Thermal Zone)來判斷是否處於虛擬環境。目前所有版本的Windows,都是從SMBIOS表中讀取風扇資訊,從ACPI表中讀取Thermal Zone資訊。但是正常的商業軟體一般不使用此方法判定,因為很多正常的膝上型電腦也沒有在SMBIOS表中寫入風扇資訊。

此外還有硬碟產品名、序列號、音效卡ID、網卡MAC地址等容易帶有虛擬機器特征的地方。

二、CPU資訊檢測

x86 CPU本身有一條指令叫CPUID,用於探測該CPU所支持的功能,例如是否支持SSE指令集等。有些功能虛擬機器無法模擬,就會遮蔽掉相關功能的資訊, 這是第五個容易露餡的地方

早期虛擬化技術不完善的時候,虛擬機器軟體需要挪動一些重要數據結構的位置,例如中斷表(IDT)等。著名的Red Pill程式就是靠讀取這些結構的地址來判定虛擬環境。但是後來有了Intel VT-x等硬體虛擬化技術,以及KVM以後,這些檢測方法就基本被淘汰了。

基於KVM的客機,如果將EAX寄存器置為0x40000000,並執行CPUID指令,會在EBX、ECX、EDX寄存器中讀取到字串「KVMKVMKVM」, 這是第六個容易露餡的地方

三、驅動資訊檢測

在有Linux KVM之前,各虛擬機器軟體用的幾乎都是半虛擬化(Paravirtualization),也就是必須對客機軟體做一定修改才能在虛擬機器中正常執行。例如VMWare虛擬機器需要在客機中安裝一些驅動程式,這些驅動的資訊中都帶有VMWare標識, 這是第七個容易露餡的地方

KVM實作的是全虛擬化,不需要對客機做任何修改,所以不必擔心這些問題。但是,按預設配置的QEMU虛擬機器會帶有很多VirtIO介面的裝置,這些裝置的驅動也會留下虛擬機器的痕跡,需要當心。

四、計時檢測

x86 CPU中有一個精度極高的計時器,稱為TSC計時器,可以精確到CPU時鐘周期數。那麽可以執行一段CPU指令,並將消耗的時間與正常CPU上消耗的時間進行對比,如果明顯高於正常值,就可判定處於虛擬機器環境。

目前最常用的方法是,在兩次讀取TSC計時器之間,執行一次CPUID指令。前文說到,虛擬機器軟體一般會特殊處理CPUID指令,遮蔽掉一些無法模擬的功能的資訊,執行這些操作所需的時間遠多於正常CPU上執行一次CPUID所需的時間, 這是第八個容易露餡的地方

目前沒有簡單的辦法可以騙過計時檢測,所有已知方案都需要用特制的Linux內核和特制的QEMU軟體配合。

但是近幾年來,虛擬機器環境檢測已經沒那麽重要了。這是因為微軟在Windows 10上大力推廣Hyper-V技術,有相當數量的使用者自己都不知道自己處於虛擬機器環境。例如,只要在Windows 10 Home的Windows Defender中開啟Memory Integrity或Core Isolation功能,就等同於開啟Hyper-V,並讓Windows執行在虛擬機器環境下。

於是有很多網遊反作弊系統,只要檢測到開啟了Hyper-V,就放棄檢測虛擬機器環境。於是在Linux界就有了一種神奇的操作,先用KVM開Windows虛擬機器,然後在Windows中開啟Hyper-V,這樣就能愉快地玩各種3A巨作了。

當然這種操作需要CPU和Hypervisor支持Nested Virtualization,然而Windows的Hyper-V長期以來不支持AMD的Nesting,會在啟動時卡死。直到2020年6月,微軟才宣布Windows 10 Insider的Hyper-V開始支持巢狀虛擬化。最近國外Reddit上有人報告使用5.11.6版本的Linux內核(無需打修補程式),配合4.2版本的QEMU,可以在AMD系統上正常啟動Windows 10 20p並開啟Hyper-V,可能是Linux KVM那邊做了改進。

既然這麽多人看我就再寫一寫怎麽繞開這些虛擬機器檢測方法。我日常電腦裝的是Linux,平時用Linux KVM + QEMU方案跑Windows虛擬機器。QEMU是開源的虛擬機器Hypervisor,命令列參數非常靈活,有另一個開源計畫libvirt專門幫助配置QEMU的參數。下面的內容都是基於KVM + QEMU + libvirt。我從簡單的操作寫起,比較難隱藏的東西放到後面。

1)CPU資訊:開啟libvirt的XML配置,找到<cpu>段落,將mode設為host-passthrough,然後段落裏面添加一行

<feature policy="disable" name="hypervisor"/>

找到<os>段落,裏面添加一行

<smbios mode="host"/>

2)KVM Hypervisor資訊:XML配置中找到<features>段落,裏面添加幾行

<hyperv> <vendor_id state="on" value="random"/> </hyperv> <kvm> <hidden state="on"/> </kvm>

3)硬碟產品名、序列號:將Disk bus設為SCSI,Serial隨便填寫,添加一個SCSI Controller,Model填為saslsi1068,然後在XML配置中找到<disk>段落,在裏面添加幾行

<vendor>Samsung</vendor> <product>20GB Harddisk</product>

此處Vendor和Product可隨便填寫

4)網卡:NIC Device Model選rtl8139,然後用這個網站隨機生成一個MAC地址填進去。

5)QEMU寫死的ID資訊:修改這些ID有兩種方法,一種是下載QEMU原始碼,修改寫死ID後重新編譯;另一種是直接用radare2等二進制修改工具,在QEMU可執行檔上打修補程式。下面列出應當隱藏的寫死ID對應的程式碼位置。(QEMU原始碼見github)

ACPI OEM ID:見hw/acpi/aml-build.c中的build_header函式

ACPI DSDT中的FWCF裝置:見hw/i386/acpi-build.c中的build_dsdt函式

ACPI DSDT中的DBUG裝置:見hw/i386/acpi-build.c中的build_dbg_aml函式

常規PCI裝置ID:見hw/pci/pci.c中的pci_set_default_subsystem_id函式

VGA裝置ID:見hw/display/vga-pci.c中的vga_pci_ class_init函式

音效卡ID:見hw/audio/hda-codec-common.h中所有含有QEMU_HDA_ID_*宏的數據結構

VirtIO Serial裝置ID:見hw/virtio/virtio-serial-pci.c中的virtio_serial_pci_ class_init函式

6)風扇和熱區域:利用QEMU的-acpitable參數,偽造一個ACPI SSDT表,裏面填入一個Thermal Zone的資訊;再利用QEMU的-smbios參數,偽造一個SMBIOS Entry Type 27,裏面填入一個風扇的資訊。

ACPI標準第11.7節提供了ACPI Thermal Zone的例子

SMBIOS標準第7.28節提供了SMBIOS Entry Type 27的格式說明

7)計時檢測:這個繞過很麻煩,需要重新編譯Linux內核,主要原理是每當遇到CPUID這種需要Hypervisor專門處理的指令時,都把TSC計時器的當前值減掉一定量,這樣就抵消掉處理這些指令花掉的時間。

詳見Reddit上相關討論:
https://www. reddit.com/r/VFIO/comme nts/g4pqkq/rdtsc_detection_workarounds/
https://www. reddit.com/r/VFIO/comme nts/i071qx/spoof_and_make_your_vm_undetectable_no_more/
https://www. reddit.com/r/VFIO/comme nts/i1xbue/here_is_my_take_on_rdtsc_offsetting/