本文介紹Pafish。Pafish是一個用來偵 測虛擬環境的開源工具,除了虛擬環境以外,Pafish還能偵測主流的沙箱和debug環境。由于Pafish是開源的,病毒作者們可以很容易的重用其代 碼。Pafish的github主頁在https://github.com/a0rtega/pafish。 目前是V05.3版。
運行Pafish以后,Pafish會對各項特征進行檢查,如果沒有發現匹配特征,就顯示一個綠色的OK, 如果發現了,就顯示一個紅色的traced! 如圖所示,Pafish檢測到當前運行環境匹配virtualbox的各項特征。
看一下pafish的github目錄,包含以下文件
基本上,Pafish用一個c文件來對應一個偵測模塊。Pafish包含如下模塊:
·基于cpu的偵測: cpu.c
·偵測 調試環境: debuggers.c
·基本偵測: gensandbox.c
·偵測主流商業虛擬環境:比如vitualbox,VMware等
3 模塊介紹
3.1 基于cpu的偵測
由于架構差異,同樣的代碼在虛擬機的運行速度往往慢于實體機。Pafish用RDTSC指令來計算執行一段代碼所花費的平均CPU運行周期數。RDTSC 指令可以以極小的代價獲得高精度的 CPU 時鐘周期數(Time Stamp Counter)。用法是,先后執行兩次RDTSC,記下兩個 64-bit 整數 ret 和 ret2,那么 ret2-ret1 代表了這期間 所花費的CPU 時鐘周期數。
static?inline?unsigned?long?long?rdtsc_diff()?{ ?????????????????????????????unsigned?long?long?ret,?ret2; ?????????????????????????????unsigned?eax,?edx; ?????????????????????????????__asm__?volatile("rdtsc"?:?"=a"?(eax),?"=d"?(edx)); ?????????????????????????????ret??=?((unsigned?long?long)eax)?|?(((unsigned?long?long)edx)?<<?32); ?????????????????????????????__asm__?volatile("rdtsc"?:?"=a"?(eax),?"=d"?(edx)); ?????????????????????????????ret2??=?((unsigned?long?long)eax)?|?(((unsigned?long?long)edx)?<<?32); ?????????????????????????????return?ret2?-?ret; }
然后這個取差值的過程重復10次,得到平均值。如果這個平均周期差小于750的話,就認為是實體機環境,否則則認為是虛擬機環境。筆者估計Pafish選擇750作為平均周期差的是根據實踐經驗。
int?cpu_rdtsc()?{ ?????????????????????????????int?i; ?????????????????????????????unsigned?long?long?avg?=?0; ?????????????????????????????for?(i?=?0;?i?<?10;?i++)?{ ??????????????????????????????????????????????????????????avg?=?avg?+?rdtsc_diff(); ??????????????????????????????????????????????????????????Sleep(500); ?????????????????????????????} ?????????????????????????????avg?=?avg?/?10; ?????????????????????????????return?(avg?<?750?&&?avg?>?0)???FALSE?:?TRUE; }
另一種方法是用cpuid指令得到cpu的名稱和型號。如果名稱中包含一些特殊的字符串,比如KVM, VMware等,則確定在虛擬環境中。
static?inline?void?cpuid_vendor_00(char?*?vendor)?{ ?????????????????????????????int?ebx,?ecx,?edx; ? ?????????????????????????????__asm__?volatile("cpuid"?\ ???????????????????????????????????????????????????????????????????????????????????????:?"=b"(ebx),?\ ?????????????????????????????????????????????????????????????????????????????????????????"=c"(ecx),?\ ?????????????????????????????????????????????????????????????????????????????????????????"=d"(edx)?\ ???????????????????????????????????????????????????????????????????????????????????????:?"a"(0x00)); ?????????????????????????????sprintf(vendor??,?"%c%c%c%c",?ebx,?(ebx?>>?8),?(ebx?>>?16),?(ebx?>>?24)); ?????????????????????????????sprintf(vendor+4,?"%c%c%c%c",?edx,?(edx?>>?8),?(edx?>>?16),?(edx?>>?24)); ?????????????????????????????sprintf(vendor+8,?"%c%c%c%c",?ecx,?(ecx?>>?8),?(ecx?>>?16),?(ecx?>>?24)); ?????????????????????????????vendor[12]?=?0x00; } ? int?cpu_known_vm_vendors(char?*?vendor)?{ ?????????????????????????????const?int?count?=?4; ?????????????????????????????int?i; ?????????????????????????????string?strs[count]; ?????????????????????????????strs[0]?=?"KVMKVMKVMKVM"; ?????????????????????????????strs[1]?=?"Microsoft?Hv"; ?????????????????????????????strs[2]?=?"VMwareVMware"; ?????????????????????????????strs[3]?=?"XenVMMXenVMM"; ?????????????????????????????for?(i?=?0;?i?<?count;?i++)?{ ??????????????????????????????????????????????????????????if?(!memcmp(vendor,?strs[i],?12))?return?TRUE; ?????????????????????????????} ?????????????????????????????return?FALSE; }
3.2 偵測 調試環境:
第一種方法很簡單,只需要看一下IsDebuggerPresent的返回值。IsDebuggerPresent是一個kernel32.dll中的函數,通過它可以檢測當前進程是否正在被調試(用戶模式)。
int?debug_isdebuggerpresent()?{ ?????????????????????????????if?(IsDebuggerPresent()) ?????????????????????????????return?TRUE; ?????????????????????????????else ?????????????????????????????return?FALSE; }
第二種方法用到OutputDebugString。OutputDebugString用來把調試信息輸出到調試器的輸出窗口。但 是OutputDebugString只能在調試環境下執行。而在非調試環境下,OutputDebugString會產生一個錯誤。Pafish先設置 一個錯誤代碼(99)作為基準值,然后調用OutputDebugString,再取出錯誤代碼與99比較。如果運行在一個調試環境中,那么應該沒有新錯 誤產生,所以取出的錯誤代碼還是99。
int?debug_outputdebugstring()?{ ?????????????????????????????DWORD?err?=?99;?/*?Random?error?*/ ?????????????????????????????SetLastError(err); ?????????????????????????????/*?If?we're?been?debugging,?this?shouldn't ?????????????????????????????drop?an?error.?*/ ?????????????????????????????OutputDebugString("useless"); ?????????????????????????????if?(GetLastError()?==?err){ ??????????????????????????????????????????????????????????return?TRUE; ?????????????????????????????} ?????????????????????????????else?{ ??????????????????????????????????????????????????????????return?FALSE; ?????????????????????????????} }
3.3 基本偵測:
由于硬件的限制,很多人在創建虛擬機的時候往往只選擇夠用的配置,而不是高配置。(至少配置不會高于實體機)基本偵測是基于鼠標的移動,CPU的數 量,硬盤和內存的大小等等。比如用戶在一段時間內沒有移動鼠標,系統只有一個CPU,硬盤小于60G,內存小于1G,這些都可能說明是一個虛擬環境。這種 偵測方法不會非常準確,比如一個服務器可能沒有配備鼠標,或者一個配置很差的舊電腦都會被誤認為是虛擬環境。但是從統計上看來,還是有一定意義的。
int?gensandbox_one_cpu_GetSystemInfo()?{ ?????????????????????????????SYSTEM_INFO?siSysInfo; ?????????????????????????????GetSystemInfo(&siSysInfo); ?????????????????????????????return?siSysInfo.dwNumberOfProcessors?<?2???TRUE?:?FALSE; }
第二種方法是如果當前系統的用戶名包含sandbox,virus,malware等關鍵字,則認為是在虛擬機中,當然這個檢測方法可以很容易的繞過。
int?gensandbox_username()?{ ?????????????????????????????char?username[200]; ?????????????????????????????size_t?i; ?????????????????????????????DWORD?usersize?=?sizeof(username); ?????????????????????????????GetUserName(username,?&usersize); ?????????????????????????????for?(i?=?0;?i?<?strlen(username);?i++)?{?/*?case-insensitive?*/ ??????????????????????????????????????????????????????????username[i]?=?toupper(username[i]); ?????????????????????????????} ?????????????????????????????if?(strstr(username,?"SANDBOX")?!=?NULL)?{ ??????????????????????????????????????????????????????????return?TRUE; ?????????????????????????????} ?????????????????????????????if?(strstr(username,?"VIRUS")?!=?NULL)?{ ??????????????????????????????????????????????????????????return?TRUE; ?????????????????????????????} ?????????????????????????????if?(strstr(username,?"MALWARE")?!=?NULL)?{ ??????????????????????????????????????????????????????????return?TRUE; ?????????????????????????????} ?????????????????????????????return?FALSE; }
3.4 偵測商業虛擬環境
除了基本的虛擬環境以外,Pafish還可以根據指紋偵測幾種主流的虛擬機,包括sandboxie,Qemu,Virtualbox, Vmware and wine.偵測方法大同小異,這里只介紹針對virtualbox的偵測方法。
在原有的基礎上,Virtualbox提供了一些增強功能,比如virtualbox guest Additions 和共享文件夾。virtualbox guest Additions可以自動調節窗口的分辨率,把實體機的字符串復制到虛擬機里面。而 共享文件夾解決了虛擬機和實體機共享文件的問題。這些增強功能極大的提高了virtualbox的易用性。然而,福兮禍所伏。若想使用 virtualbox guest Additions,用戶需要在虛擬機上安裝一些特殊的驅動和應用程序。而這些驅動和應用基本不可能出現在實體機上。所以Pafish可以查找這些驅動和 應用來判斷當前運行環境是否是virtualbox。
查找注冊表,看看有沒有VirtualBox Guest Additions的字符串。
int?vbox_reg_key3()?{ ?????????????????????????????return?pafish_exists_regkey(HKEY_LOCAL_MACHINE,?"SOFTWARE\\Oracle\\VirtualBox?Guest?Additions"); }
查找有沒有VirtualBox Guest Additions的相關進程 (vboxservice.exe 和vboxtray.exe)
查找右下角有沒有關于VboxTrayTool的任務欄托盤窗口.
int?vbox_traywindow()?{ ?????????????????????????????HWND?h1,?h2; ?????????????????????????????h1?=?FindWindow("VBoxTrayToolWndClass",?NULL); ?????????????????????????????h2?=?FindWindow(NULL,?"VBoxTrayToolWnd"); ?????????????????????????????if?(h1?||?h2)?return?TRUE; ?????????????????????????????else?return?FALSE; }
查找是否存在以下文件
"C:\\WINDOWS\\system32\\vboxdisp.dll"; "C:\\WINDOWS\\system32\\vboxhook.dll"; "C:\\WINDOWS\\system32\\vboxmrxnp.dll"; "C:\\WINDOWS\\system32\\vboxogl.dll"; "C:\\WINDOWS\\system32\\vboxoglarrayspu.dll"; "C:\\WINDOWS\\system32\\vboxoglcrutil.dll"; "C:\\WINDOWS\\system32\\vboxoglerrorspu.dll"; "C:\\WINDOWS\\system32\\vboxoglfeedbackspu.dll"; "C:\\WINDOWS\\system32\\vboxoglpackspu.dll"; "C:\\WINDOWS\\system32\\vboxoglpassthroughspu.dll"; "C:\\WINDOWS\\system32\\vboxservice.exe"; "C:\\WINDOWS\\system32\\vboxtray.exe"; "C:\\WINDOWS\\system32\\VBoxControl.exe"; "C:\\program?files\\oracle\\virtualbox?guest?additions\\"; "C:\\WINDOWS\\system32\\drivers\\VBoxMouse.sys"; "C:\\WINDOWS\\system32\\drivers\\VBoxGuest.sys"; "C:\\WINDOWS\\system32\\drivers\\VBoxSF.sys"; "C:\\WINDOWS\\system32\\drivers\\VBoxVideo.sys";
用WNetGetProviderName檢查共享文件夾的網絡類型名稱,如果是”VirtualBox Shared Folders”,則檢查到virtualbox的共享文件夾
int?vbox_network_share()?{ ?????????????????????????????unsigned?long?pnsize?=?0x1000; ?????????????????????????????char?provider[pnsize]; ?????????????????????????????int?retv?=?WNetGetProviderName(WNNC_NET_RDR2SAMPLE,?provider,?&pnsize); ?????????????????????????????if?(retv?==?NO_ERROR)?{ ??????????????????????????????????????????????????????????if?(lstrcmpi(provider,?"VirtualBox?Shared?Folders")?==?0)?{ ???????????????????????????????????????????????????????????????????????????????????????return?TRUE; ??????????????????????????????????????????????????????????} ??????????????????????????????????????????????????????????else?{ ???????????????????????????????????????????????????????????????????????????????????????return?FALSE; ??????????????????????????????????????????????????????????} ?????????????????????????????} ?????????????????????????????return?FALSE; }
以上,我們可以看到如果在虛擬機上安裝了virtualbox guest Additions 和共享文件夾,該虛擬機的其他進程可以很容易的發現其相關的窗口,文件,驅動,服務信息。這就相當于李鬼在臉上寫著“我不是李逵,我是李鬼”。那么,是不 是不安裝virtualbox guest Additions 和共享文件夾就安全了吶?答案是未必。一些默認的硬件信息仍然可以被作為指紋用于檢測virtualbox的存在。比如virtualbox默認的網卡 MAC地址前綴為08:00:27,這前3字節是virtualbox分配的唯一標識符OUI,以供其虛擬網卡使用。
int?vbox_mac()?{ ?????????????????????????????/*?VirtualBox?mac?starts?with?08:00:27?*/ ?????????????????????????????return?pafish_check_mac_vendor("\x08\x00\x27"); }
另外,還可以讀取注冊表信息,通過檢測特定的硬件信息,比如SCSI, systembiosversion, videobiosversion, ACPI,如果這些信息 包含VBOX關鍵字,那么可以斷定是virtualbox虛擬環境。
int?vbox_reg_key1()?{ ?????????????????????????????return?pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE,?"HARDWARE\\DEVICEMAP\\Scsi\\Scsi?Port?0\\Scsi?Bus?0\\Target?Id?0\\Logical?Unit?Id?0",?"Identifier",?"VBOX"); }
還可以檢測其系統bios生成日期,如果是1999年6月23號,那么很可能是virtualbox
int?vbox_reg_key10()?{ ?????????????????????????????return?pafish_exists_regkey_value_str(HKEY_LOCAL_MACHINE,?"HARDWARE\\DESCRIPTION\\System",?"SystemBiosDate",?"06/23/99"); }
4. 總結
綜上,Pafish給出了一些很實用的檢測虛擬機 的方法。虛擬的畢竟就是虛擬的,總歸會留下蛛絲螞跡。不過很多人認為我們也可以利用虛擬機檢測來免疫病毒,比如在實體機上設置一些虛假信息讓病毒誤認為這 個實體機是一個虛擬機,從而跳過“做壞事”的階段。對于這個觀點,有人認為靠譜,有人認為不靠譜,大家怎么看?