虛擬機檢測技術剖析
作者:riusksk (泉哥)
主頁:http://riusksk.blogbus.com
前言
在當今信息安全領域,特別是惡意軟件分析中,經常需要利用到虛擬機技術,以提高病毒分析過程的安全性以及硬件資源的節約性,因此它在惡意軟件領域中是應用越來越來廣泛。這裏我們所謂的虛擬機(Virtual Machine)是指通過軟件模擬的具有完整硬件系統功能的、運行在一個完全隔離環境中的完整計算機系統。通過虛擬機軟件(比如VMware,Virtual PC ,VirtualBox),你可以在一臺物理計算機上模擬出一臺或多臺虛擬的計算機,這些虛擬機完全就像真正的計算機那樣進行工作,例如你可以安裝操作系統、安裝應用程序、訪問網絡資源等等。攻擊者為了提高惡意程序的隱蔽性以及破壞真實主機的成功率,他們都在惡意程序中加入檢測虛擬機的代碼,以判斷程序所處的運行環境。當發現程序處於虛擬機(特別是蜜罐系統)中時,它就會改變操作行為或者中斷執行,以此提高反病毒人員分析惡意軟件行為的難度。本文主要針對基於Intel CPU的虛擬環境VMware中的Windows XP SP3系統進行檢測分析,並列舉出當前常見的幾種虛擬機檢測方法。
方法一:通過執行特權指令來檢測虛擬機
Vmware為真主機與虛擬機之間提供了相互溝通的通訊機制,它使用“IN”指令來讀取特定端口的數據以進行兩機通訊,但由於IN指令屬於特權指令,在處於保護模式下的真機上執行此指令時,除非權限允許,否則將會觸發類型為“EXCEPTION_PRIV_INSTRUCTION”的異常,而在虛擬機中並不會發生異常,在指定功能號0A(獲取VMware版本)的情況下,它會在EBX中返回其版本號“VMXH”;而當功能號為0×14時,可用於獲取VMware內存大小,當大於0時則說明處於虛擬機中。VMDetect正是利用前一種方法來檢測VMware的存在,其檢測代碼分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
bool IsInsideVMWare() { 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; } return rc; } |
方法二:利用IDT基址檢測虛擬機
利用IDT基址檢測虛擬機的方法是一種通用方式,對VMware和Virtual PC均適用。中斷描述符表IDT(Interrupt Descriptor Table)用於查找處理中斷時所用的軟件函數,它是一個由256項組成的數據,其中每一中斷對應一項函數。為了讀取IDT基址,我們需要通過SIDT指令來讀取IDTR(中斷描述符表寄存器,用於IDT在內存中的基址),SIDT指令是以如下格式來存儲IDTR的內容:
typedef struct { WORD IDTLimit; // IDT的大小 WORD LowIDTbase; // IDT的低位地址 WORD HiIDTbase; // IDT的高位地址 } IDTINFO; |
由於只存在一個IDTR,但又存在兩個操作系統,即虛擬機系統和真主機系統。為了防止發生沖突,VMM(虛擬機監控器)必須更改虛擬機中的IDT地址,利用真主機與虛擬機環境中執行sidt指令的差異即可用於檢測虛擬機是否存在。著名的“紅丸”(redpill)正是利用此原理來檢測VMware的。Redpill作者在VMware上發現虛擬機系統上的IDT地址通常位於0xFFXXXXXX,而Virtual PC通常位於0xE8XXXXXX,而在真實主機上正如圖2所示都位於0x80xxxxxx。Redpill僅僅是通過判斷執行SIDT指令後返回的第一字節是否大於0xD0,若是則說明它處於虛擬機,否則處於真實主機中。Redpill的源碼甚是精簡,源碼分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <stdio.h> int main () { //相當於SIDT[adrr],其中addr用於保存IDT地址 unsigned char m[2+4], rpill[] = "\x0f\x01\x0d\x00\x00\x00\x00\xc3"; //將sidt[addr]中的addr設為m的地址 *((unsigned*)&rpill[3]) = (unsigned)m; //執行SIDT指令,並將讀取後IDT地址保存在數組m中 ((void(*)())&rpill)(); //由於前2字節為IDT大小,因此從m[2]開始即為IDT地址 printf ("idt base: %#x\n", *((unsigned*)&m[2])); //當IDT基址大於0xd0xxxxxx時則說明程序處於VMware中 if (m[5]>0xd0) printf ("Inside Matrix!\n", m[5]); else printf ("Not in Matrix.\n"); return 0; } |
利用此IDT檢測的方法存在一個缺陷,由於IDT的值只針對處於正在運行的處理器而言,在單CPU中它是個常量,但當它處於多CPU時就可能會受到影響了,因為每個CPU都有其自己的IDT,這樣問題就自然而然的產生了。針對此問題,Offensive Computing組織成員提出了兩種應對方法,其中一種方法就是利用Redpill反復地在系統上循環執行任務,以此構造出一張當前系統的IDT值變化統計圖,但這會增加CPU負擔;另一種方法就是windows API函數SetThreadAffinityMask()將線程限制在單處理器上執行,當執行此測試時只能準確地將線程執行環境限制在本地處理器,而對於將線程限制在VM處理器上就可能行不通了,因為VM是計劃在各處理器上運行的,VM線程在不同的處理器上執行時,IDT值將會發生變化,因此此方法也是很少被使用的。為此,有人提出了使用LDT的檢測方法,它在具有多個CPU的環境下檢測虛擬機明顯優於IDT檢測方法,該方法具體內容參見下節內容。
方法三:利用LDT和GDT的檢測方法
在 《Intel® 64 and IA-32 Architecture Software Developer’s Manual Volume 3A: System Programming Guide》第二章的Vol.3 2-5 一頁(我的Intel開發手冊是2008版的)中對於LDT和GDT的描述如下(以下內容為個人翻譯):
在保護模式下,所有的內存訪問都要通過全局描述符表(GDT)或者本地描述符表(LDT)才能進行。這些表包含有段描述符的調用入口。各個段描述符都包含有各段的基址,訪問權限,類型和使用信息,而且每個段描述符都擁有一個與之相匹配的段選擇子,各個段選擇子都為軟件程序提供一個GDT或LDT索引(與之相關聯的段描述符偏移量),一個全局/本地標誌(決定段選擇子是指向GDT還是LDT),以及訪問權限信息。
若想訪問段中的某一字節,必須同時提供一個段選擇子和一個偏移量。段選擇子為段提供可訪問的段描述符地址(在GDT 或者LDT 中)。通過段描述符,處理器從中獲取段在線性地址空間裏的基址,而偏移量用於確定字節地址相對基址的位置。假定處理器在當前權限級別(CPL)可訪問這個段,那麽通過這種機制就可以訪問在GDT 或LDT 中的各種有效代碼、數據或者堆棧段,這裏的CPL是指當前可執行代碼段的保護級別。
……
GDT的線性基址被保存在GDT寄存器(GDTR)中,而LDT的線性基址被保存在LDT寄存器(LDTR)中。
由於虛擬機與真實主機中的GDT和LDT並不能相同,這與使用IDT的檢測方法一樣,因此虛擬機必須為它們提供一個“復制體”。關於GDT和LDT的基址可通過SGDT和SLDT指令獲取。虛擬機檢測工具Scoopy suite的作者Tobias Klein經測試發現,當LDT基址位於0×0000(只有兩字節)時為真實主機,否則為虛擬機,而當GDT基址位於0xFFXXXXXX時說明處於虛擬機中,否則為真實主機。具體實現代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <stdio.h> void LDTDetect(void) { unsigned short ldt_addr = 0; unsigned char ldtr[2]; _asm sldt ldtr ldt_addr = *((unsigned short *)&ldtr); printf("LDT BaseAddr: 0x%x\n", ldt_addr); if(ldt_addr == 0x0000) { printf("Native OS\n"); } else printf("Inside VMware\n"); } 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) == 0xff) { printf("Inside VMware\n"); } else printf("Native OS\n"); } int main(void) { LDTDetect(); GDTDetect(); return 0; } |
方法四:基於STR的檢測方法
在保護模式下運行的所有程序在切換任務時,對於當前任務中指向TSS的段選擇器將會被存儲在任務寄存器中,TSS中包含有當前任務的可執行環境狀態,包括通用寄存器狀態,段寄存器狀態,標誌寄存器狀態,EIP寄存器狀態等等,當此項任務再次被執行時,處理器就會其原先保存的任務狀態。每項任務均有其自己的TSS,而我們可以通過STR指令來獲取指向當前任務中TSS的段選擇器。這裏STR(Store task register)指令是用於將任務寄存器 (TR) 中的段選擇器存儲到目標操作數,目標操作數可以是通用寄存器或內存位置,使用此指令存儲的段選擇器指向當前正在運行的任務的任務狀態段 (TSS)。在虛擬機和真實主機之中,通過STR讀取的地址是不同的,當地址等於0x0040xxxx時,說明處於虛擬機中,否則為真實主機。實現代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#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]==0x00) && (mem[1]==0x40)) printf("\n INSIDE MATRIX!!\n"); else printf("\n Native OS!!\n"); return 0; } |
方法五:基於註冊表檢測虛擬機
在windows虛擬機中常常安裝有VMware Tools以及其它的虛擬硬件(如網絡適配器、虛擬打印機,USB集線器……),它們都會創建任何程序都可以讀取的windows註冊表項,因此我們可以通過檢測註冊表中的一些關鍵字符來判斷程序是否處於虛擬機之中。關於這些註冊表的位置我們可以通過在註冊表中搜索關鍵詞“vmware”來獲取,下面是我在VMware下的WinXP中找到的一些註冊表項:
項名:HKEY_CLASSES_ROOT\Applications\VMwareHostOpen.exe
項名:HKEY_CLASSES_ROOT\Installer\Products\C2A6F2EFE6910124C940B2B12CF170FE\ProductName
鍵值“VMware Tools”
項名:HKEY_CLASSES_ROOT\Installer\Products\C2A6F2EFE6910124C940B2B12CF170FE\SourceList\PackageName
鍵值:VMware Tools.msi
項名:HKEY_CURRENT_USER\Printers\DeviceOld
鍵值:_#VMwareVirtualPrinter,winspool,TPVM:
項名:HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier
鍵值:VMware Virtual IDE Hard Drive
項名:HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier
鍵值:NECVMWar VMware IDE CDR10
項名:HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\C2A6F2EFE6910124C940B2B12CF170FE\ProductName
鍵值:VMware Tools
項名:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\C2A6F2EFE6910124C940B2B12CF170FE\InstallProperties\DisplayName
鍵值:VMware Tools
項名:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Reinstall\0002\DeviceDesc
鍵值:VMware SVGA II
項名:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards\2\Description
鍵值:VMware Accelerated AMD PCNet Adapter
項名:HKEY_LOCAL_MACHINE\SOFTWARE\VMware, Inc.\VMware Tools
項名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000\DriverDesc
鍵值:VMware SVGA II
項名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-
08002BE10318}\0000\ProviderName
鍵值:VMware, Inc.
項名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\0001\DriverDesc
鍵值:VMware Accelerated AMD PCNet Adapter
項名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E97B-E325-11CE-BFC1-08002BE10318}\0000\DriverDesc
鍵值:VMware SCSI Controller
項名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Print\Monitors\ThinPrint Print Port Monitor for VMWare
除以上這些表項之外,還有很多地方可以檢測,特別是虛擬機提供的虛擬化軟硬件、服務之類,比如文件共享服務,VMware 物理磁盤助手服務,VMware Ethernet Adapter Driver,VMware SCSI Controller等等的這些信息都可作為檢測虛擬機的手段。這裏我們就以其中某表項為例編程舉例一下,其它表項檢測方法同理,具體代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
.386 .model flat, stdcall option casemap:none include windows.inc include user32.inc include kernel32.inc include advapi32.inc includelib user32.lib includelib kernel32.lib includelib advapi32.lib .data szCaption db "VMware Detector ",0 szInside db "Inside VMware!",0 szOutside db "Native OS!",0 szSubKey db "software\VMWare, Inc.\VMware tools",0 hKey dd ? .code start: invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, addr szSubKey, 0, KEY_WRITE or KEY_READ, addr hKey .if eax == ERROR_SUCCESS invoke MessageBox, NULL,addr szInside, addr szCaption, MB_OK .else invoke MessageBox, NULL,addr szOutside, addr szCaption, MB_OK .endif invoke RegCloseKey,hKey invoke ExitProcess,NULL end start |
方法六:基於時間差的檢測方式
本方法通過運行一段特定代碼,然後比較這段代碼在虛擬機和真實主機之中的相對運行時間,以此來判斷是否處於虛擬機之中。這段代碼我們可以通過RDTSC指令來實現,RDTSC指令是用於將計算機啟動以來的CPU運行周期數存放到EDX:EAX裏面,其中EDX是高位,而EAX是低位。下面我們以xchg ecx, eax 一句指令的運行時間為例,這段指令在我的真實主機windows 7系統上的運行時間為0000001E,而該指令在虛擬機WinXP下的運行時間為00000442,兩者之間的運行時間明顯差別很多,在虛擬機中的運行速度遠不如真實主機的,一般情況下,當它的運行時間大於0xFF時,就可以確定它處於虛擬機之中了,因此不難寫出檢測程序,具體實現代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
.586p .model flat, stdcall option casemap:none include windows.inc include kernel32.inc include user32.inc includelib kernel32.lib includelib user32.lib .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 |
方法七:利用虛擬硬件指紋檢測虛擬機
利用虛擬硬件指紋也可用於檢測虛擬機的存在,比如VMware默認的網卡MAC地址前綴為“00-05-69,00-0C-29或者00-50-56”,這前3節是由VMware分配的唯一標識符OUI,以供它的虛擬化適配器使用。在我的VMWare WinXP下的MAC地址為00-0C-29-5B-D7-67,但由於這些可經過修改配置文件來繞過檢測。另外,還可通過檢測特定的硬件控制器,BIOS,USB控制器,顯卡,網卡等特征字符串進行檢測,這些在前面使用註冊表檢測方法中已有所涉及。另外之前在看雪論壇上也有朋友提到通過檢測硬盤Model Number是否含有“vmware”或“virtual”等字樣來實現檢測虛擬機的功能,網址見這(附源碼):http://bbs.pediy.com/showthread.php?t=110046。
總結
國外SANS安全組織的研究人員總結出當前各種虛擬機檢測手段不外乎以下四類:
● 搜索虛擬環境中的進程,文件系統,註冊表;
● 搜索虛擬環境中的內存
● 搜索虛擬環境中的特定虛擬硬件
● 搜索虛擬環境中的特定處理器指令和功能
因為現代計算系統大多是由文件系統,內存,處理器及各種硬件組件構成的,上面提到的四種檢測手段均包含了這些因素。縱觀前面各種檢測方法,也均在此四類當中。除此之外,也有人提出通過網絡來檢測虛擬機,比如搜索ICMP和TCP數據通訊的時間差異,IP ID數據包差異以及數據包中的異常頭信息等等。隨著技術研究的深入,相信會有更多的檢測手段出現,與此同時,虛擬機廠商也會不斷進化它們的產品,以增加anti-vmware的難度,這不也正是一場永無休止的無煙戰爭!
http://www.programlife.net/vmware-virtual-machine-detection.html
虛擬機檢測技術剖析