惡意代碼編寫者經常使用反虛擬機技術逃避分析,這種技術可以檢測自己是否運行在虛擬機中。如果惡意代碼探測到自己在虛擬機中運行,它會執行與其本身行為不同的行為,其中最簡單的行為是停止自身運行。
近年來,隨著虛擬化技術的使用不斷增加,採用反虛擬機技術的惡意代碼數量逐漸下降。惡意代碼編寫者已經開始意識到,目標主機是虛擬機,也並不意味著它就沒有攻擊價值。
隨著虛擬化技術的不斷髮展和普通應用,反虛擬機技術可能變得更加少見。這裡研究最常見的反虛擬機技術(包括VMware、virtualbox和virtualpc,重點是最常用的VMware),並且介紹一些如何防禦它們的辦法。
一、檢測虛擬機痕跡
1.根據MAC地址
通常,MAC地址的前三個字節標識一個提供商。以00:05:69、00:0c:29和00:50:56開始的MAC地址與VMware相對應;以00:03:ff開始的MAC地址與virtualpc對應;以08:00:27開始的MAC地址與virtualbox對應。
BOOL CheckVMWare()
{
string mac;
get_3part_mac(mac);
if (mac=="00-05-69" || mac=="00-0c-29" || mac=="00-50-56")
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVirtualPC()
{
string mac;
get_3part_mac(mac);
if (mac=="00-03-ff")
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVirtualBox()
{
string mac;
get_3part_mac(mac);
if (mac=="08-00-27")
{
return TRUE;
}
else
{
return FALSE;
}
}
typedef struct _ASTAT_
{
ADAPTER_STATUS adapt;
NAME_BUFFER NameBuff[30];
} ASTAT, *PASTAT;
void get_3part_mac(string &mac)
{
NCB Ncb;
ASTAT Adapter;
UCHAR uRetCode;
LANA_ENUM lenum;
memset(&Ncb, 0, sizeof(Ncb));
Ncb.ncb_command = NCBENUM;
Ncb.ncb_buffer = (UCHAR *)&lenum;
Ncb.ncb_length = sizeof(lenum);
uRetCode = Netbios(&Ncb);
for (int i = 0; i < lenum.length; i++)
{
memset(&Ncb, 0, sizeof(Ncb));
Ncb.ncb_command = NCBRESET;
Ncb.ncb_lana_num = lenum.lana[i];
uRetCode = Netbios(&Ncb);
memset(&Ncb, 0, sizeof(Ncb));
Ncb.ncb_command = NCBASTAT;
Ncb.ncb_lana_num = lenum.lana[i];
strcpy((char *)Ncb.ncb_callname, "*");
Ncb.ncb_buffer = (unsigned char *)&Adapter;
Ncb.ncb_length = sizeof(Adapter);
uRetCode = Netbios(&Ncb);
if (uRetCode == 0)
{
char tmp[128];
sprintf(tmp, "%02x-%02x-%02x",
Adapter.adapt.adapter_address[0],
Adapter.adapt.adapter_address[1],
Adapter.adapt.adapter_address[2]
);
mac = tmp;
}
}
}
2. 基於主板序列號、主機型號、系統盤所在磁盤名稱等其他硬件信息
//通過WMI獲取主機信息 BOOL ManageWMIInfo(string &result, string table, wstring wcol)
{
HRESULT hres;
char bord[1024];
//初始化COM hres = CoInitialize(0);
//獲得WMI連接COM接口 IWbemLocator *pLoc = NULL;
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *) &pLoc);
if (FAILED(hres))
{
cout << "Failed to create IWbemLocator object."
<< "Err code = 0x"
<< hex << hres << endl;
CoUninitialize();
return false;
}
//通過連接接口連接WMI的內核對象名ROOT//CIMV2 IWbemServices *pSvc = NULL;
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace NULL, // User name. NULL = current user NULL, // User password. NULL = current 0, // Locale. NULL indicates current NULL, // Security flags. 0, // Authority (e.g. Kerberos) 0, // Context object &pSvc // pointer to IWbemServices proxy );
if (FAILED(hres))
{
cout << "Could not connect. Error code = 0x"
<< hex << hres << endl;
pLoc->Release();
CoUninitialize();
return false;
}
//設置請求代理的安全級別 hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx NULL, // Server principal name RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx NULL, // client identity EOAC_NONE // proxy capabilities );
if (FAILED(hres))
{
cout << "Could not set proxy blanket. Error code = 0x"
<< hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return false;
}
//通過請求代理來向WMI發送請求 IEnumWbemClassObject* pEnumerator = NULL;
string select = "SELECT * FROM "+ table;
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t(select.c_str()),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
cout << "Query for Network Adapter Configuration failed."
<< " Error code = 0x”"
<< hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return false;
}
//循環枚舉所有的結果對象 ULONG uReturn = 0;
IWbemClassObject *pclsObj;
while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
if(0 == uReturn)
{
break;
}
VARIANT vtProp;
VariantInit(&vtProp);
hr = pclsObj->Get(wcol.c_str(), 0, &vtProp, 0, 0);
if(!FAILED(hr))
{
CW2A tmpstr1(vtProp.bstrVal);
strcpy_s(bord,200,tmpstr1);
result = bord;
}
VariantClear(&vtProp);
}
//釋放資源 pSvc->Release();
pLoc->Release();
pEnumerator->Release();
pclsObj->Release();
CoUninitialize();
return true;
}
BOOL CheckVMWare()
{
string table = "Win32_BaseBoard";
wstring wcol = L"SerialNumber";
string ret;
ManageWMIInfo(ret, table, wcol);
if (ret == "None")
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVMWare()
{
string table = "Win32_DiskDrive";
wstring wcol = L"Caption";
string ret;
ManageWMIInfo(ret, table, wcol);
if (ret.find("VMware") != string::npos)
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVMWare()
{
string table = "Win32_computersystem";
wstring wcol = L"Model";
string ret;
ManageWMIInfo(ret, table, wcol);
if (ret.find("VMware") != string::npos)
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVirtualBox()
{
string table = "Win32_computersystem";
wstring wcol = L"Model";
string ret;
ManageWMIInfo(ret, table, wcol);
if (ret.find("VirtualBox") != string::npos)
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVirtualBox()
{
string table = "Win32_DiskDrive";
wstring wcol = L"Caption";
string ret;
ManageWMIInfo(ret, table, wcol);
if (ret.find("VBOX") != string::npos)
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVirtualPC()
{
string table = "Win32_DiskDrive";
wstring wcol = L"Caption";
string ret;
ManageWMIInfo(ret, table, wcol);
if (ret.find("Virtual HD") != string::npos)
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVirtualPC()
{
string table = "Win32_computersystem";
wstring wcol = L"Model";
string ret;
ManageWMIInfo(ret, table, wcol);
if (ret.find("Virtual Machine") != string::npos)
{
return TRUE;
}
else
{
return FALSE;
}
}
3.根據當前進程信息
通過進程快照讀取當前進程信息,查找是否存在虛擬機中特有的進程,如VMware中的vmware.exe和VirtualBox中的VBoxService.exe。
BOOL CheckVMWare()
{
DWORD ret = 0;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hProcessSnap == INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bMore = Process32First(hProcessSnap, &pe32);
while(bMore)
{
if (strcmp(pe32.szExeFile, "vmware.exe")==0)
{
return TRUE;
}
bMore = Process32Next(hProcessSnap, &pe32);
}
CloseHandle(hProcessSnap);
return FALSE;
}
BOOL CheckVirtualBox()
{
DWORD ret = 0;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hProcessSnap == INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bMore = Process32First(hProcessSnap, &pe32);
while(bMore)
{
if (strcmp(pe32.szExeFile, "VBoxService.exe")==0)
{
return TRUE;
}
bMore = Process32Next(hProcessSnap, &pe32);
}
CloseHandle(hProcessSnap);
return FALSE;
}
4.根據特定的文件夾或文件信息
通過查找磁盤中是否存在特定的文件夾或文件,判斷當前是否在虛擬機中。VMware虛擬機中通常會有路徑C:\Program
Files\VMware\VMware Tools\;VirtualBox 虛擬機中通常會有路徑 C:\Program
Files\Oracle\VirtualBox Guest Additions\。
BOOL CheckVMware()
{
if (PathIsDirectory("C:\\Program Files\\VMware\\VMware Tools\") == 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
BOOL CheckVirtualBox()
{
if (PathIsDirectory("C:\\Program Files\\Oracle\\VirtualBox Guest Additions\") == 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
5.根據特定註冊表信息
通過讀取主機具有虛擬機特性的註冊表位置來判斷是否處於虛擬機環境中。針對VMware可以判斷註冊表項HKEY_CLASSES_ROOT\\Applications\\VMwareHostOpen.exe;針對VirtualBox可以判斷註冊表項HKEY_LOCAL_MACHINE\\SOFTWARE\\Oracle\\VirtualBox
Guest Additions。當然,註冊表中能被檢測出的位置很多,這裡只是舉個例子。
BOOL CheckVMWare()
{
HKEY hkey;
if (RegOpenKey(HKEY_CLASSES_ROOT, "\\Applications\\VMwareHostOpen.exe", &hkey) == ERROR_SUCCESS)
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckVirtualBox()
{
HKEY hkey;
if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Oracle\\VirtualBox Guest Additions", &hkey) == ERROR_SUCCESS)
{
return TRUE;
}
else
{
return FALSE;
}
}
6.根據特定服務名
通過獲取主機當前具有VMware特性的服務信息,判斷當前主機是否為虛擬機。在VMware中通常會存在VMware物理磁盤助手服務和VMware Tools服務等;在VirtualBox中通常會存在VirtualBox Guest Additions Service服務等。
BOOL CheckVMWare()
{
int menu = 0;
//打開系統服務控制器 SC_HANDLE SCMan = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if(SCMan == NULL)
{
cout << GetLastError() << endl;
printf("OpenSCManager Eorror/n");
return -1;
}
//保存系統服務的結構 LPENUM_SERVICE_STATUSA service_status;
DWORD cbBytesNeeded = NULL;
DWORD ServicesReturned = NULL;
DWORD ResumeHandle = NULL;
service_status = (LPENUM_SERVICE_STATUSA)LocalAlloc(LPTR, 1024 * 64);
//獲取系統服務的簡單信息 bool ESS = EnumServicesStatusA(SCMan, //系統服務句柄 SERVICE_WIN32, //服務的類型 SERVICE_STATE_ALL, //服務的狀態 (LPENUM_SERVICE_STATUSA)service_status, //輸出參數,系統服務的結構 1024 * 64, //結構的大小 &cbBytesNeeded, //輸出參數,接收返回所需的服務 &ServicesReturned, //輸出參數,接收返回服務的數量 &ResumeHandle); //輸入輸出參數,第一次調用必須為0,返回為0代表成功 if(ESS == NULL)
{
printf("EnumServicesStatus Eorror/n");
return -1;
}
for(int i = 0; i < ServicesReturned; i++)
{
if (strstr(service_status[i].lpDisplayName, "VMware Tools")!=NULL || strstr(service_status[i].lpDisplayName, "VMware 物理磁盤助手服務")!=NULL)
{
return TRUE;
}
}
//關閉服務管理器的句柄 CloseServiceHandle(SCMan);
return FALSE;
}
BOOL CheckVirtualPC()
{
int menu = 0;
//打開系統服務控制器 SC_HANDLE SCMan = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if(SCMan == NULL)
{
cout << GetLastError() << endl;
printf("OpenSCManager Eorror/n");
return -1;
}
//保存系統服務的結構 LPENUM_SERVICE_STATUSA service_status;
DWORD cbBytesNeeded = NULL;
DWORD ServicesReturned = NULL;
DWORD ResumeHandle = NULL;
service_status = (LPENUM_SERVICE_STATUSA)LocalAlloc(LPTR, 1024 * 64);
//獲取系統服務的簡單信息 bool ESS = EnumServicesStatusA(SCMan, //系統服務句柄 SERVICE_WIN32, //服務的類型 SERVICE_STATE_ALL, //服務的狀態 (LPENUM_SERVICE_STATUSA)service_status, //輸出參數,系統服務的結構 1024 * 64, //結構的大小 &cbBytesNeeded, //輸出參數,接收返回所需的服務 &ServicesReturned, //輸出參數,接收返回服務的數量 &ResumeHandle); //輸入輸出參數,第一次調用必須為0,返回為0代表成功 if(ESS == NULL)
{
printf("EnumServicesStatus Eorror/n");
return -1;
}
for(int i = 0; i < ServicesReturned; i++)
{
if (strstr(service_status[i].lpDisplayName, "Virtual Machine")!=NULL)
{
return TRUE;
}
}
//關閉服務管理器的句柄 CloseServiceHandle(SCMan);
return FALSE;
}
BOOL CheckVirtualBox()
{
int menu = 0;
//打開系統服務控制器 SC_HANDLE SCMan = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if(SCMan == NULL)
{
cout << GetLastError() << endl;
printf("OpenSCManager Eorror/n");
return -1;
}
//保存系統服務的結構 LPENUM_SERVICE_STATUSA service_status;
DWORD cbBytesNeeded = NULL;
DWORD ServicesReturned = NULL;
DWORD ResumeHandle = NULL;
service_status = (LPENUM_SERVICE_STATUSA)LocalAlloc(LPTR, 1024 * 64);
//獲取系統服務的簡單信息 bool ESS = EnumServicesStatusA(SCMan, //系統服務句柄 SERVICE_WIN32, //服務的類型 SERVICE_STATE_ALL, //服務的狀態 (LPENUM_SERVICE_STATUSA)service_status, //輸出參數,系統服務的結構 1024 * 64, //結構的大小 &cbBytesNeeded, //輸出參數,接收返回所需的服務 &ServicesReturned, //輸出參數,接收返回服務的數量 &ResumeHandle); //輸入輸出參數,第一次調用必須為0,返回為0代表成功 if(ESS == NULL)
{
printf("EnumServicesStatus Eorror/n");
return -1;
}
for(int i = 0; i < ServicesReturned; i++)
{
if (strstr(service_status[i].lpDisplayName, "VirtualBox Guest")!=NULL)
{
return TRUE;
}
}
//關閉服務管理器的句柄 CloseServiceHandle(SCMan);
return FALSE;
}
7.根據時間差
由於在虛擬機中,代碼的運行速度通常不如真實主機。所以惡意代碼通過運行一段特定的代碼來比較這段代碼在虛擬機和真實主機之中的相對運行時間,以此來判斷是否處於虛擬機之中。
BOOL CheckVMWare()
{
__asm
{
rdtsc
xchg ebx,eax
rdtsc
sub eax,ebx
cmp eax,0xFF
jg detected
}
return FALSE;
detected:
return TRUE;
}
BOOL CheckVirtualPC()
{
__asm
{
rdtsc
xchg ebx,eax
rdtsc
sub eax,ebx
cmp eax,0xFF
jg detected
}
return FALSE;
detected:
return TRUE;
}
BOOL CheckVirtualBox()
{
__asm
{
rdtsc
xchg ebx,eax
rdtsc
sub eax,ebx
cmp eax,0xFF
jg detected
}
return FALSE;
detected:
return TRUE;
}
二、查找漏洞指令
虛擬機監視器監視虛擬機的運行,它運行在宿主操作系統,併為客戶機操作系統提供一個完整的虛擬平臺。與此同時,虛擬機監視器也存在一些可以被惡意代碼探測到虛擬化的安全缺陷。
在內核模式下,VMware使用二進制翻譯技術進行指令的模擬。運行於內核態的某些特權指令被解釋和模擬,所以它們不在物理處理器上運行。
相反,在用戶模式下,代碼直接在處理器上運行,幾乎所有與硬件交互的指令,要麼是特權指令,要麼會產生內核態陷阱指令或中斷指令。
VMware截獲所有中斷並處理它們,以便虛擬機仍然認為這是一個正常機器。然而在x86體系結構中,一些指令在獲取硬件相關的信息時並不產生異常,如sidt、sgdt、sldt、cpuid等等。為了正確虛擬這些指令,VMware需要在所有指令上進行二進制翻譯,因此造成巨大的性能損失。
為了避免執行全指令模擬造成的巨大性能損失,VMware允許一些特定指令在沒有正確虛擬化的前提下運行。最終,這意味著某些指令序列在VMware虛擬機而不是在物理機中運行時返回不同的結果。處理器使用某些關鍵的結構與表,它們會被加載與真實系統不同的偏移量,而這正是未進行全虛擬化的副作用。
中斷描述表(IDT)是CPU內部的一個數據結構,操作系統使用它來確保正確響應中斷和異常。在x86體系結構下,所有的內存獲取,或是通過全局描述表(GDT)獲得,或是通過本地描述表(LDT)獲得。這些表中包含段描述符,它們提供每一個段的詳細存取信息,其中包含段基地址類型、長度,以及存取權限等等。
IDT、GDT和LDT是CPU內部的寄存器,它們分別存放著各自表的基地址和大小。有三條敏感指令(sidt、sgdt和sldt)可以讀取這些表的位置,並且將相應的寄存器存入內存地址。因為這些指令可以隨時被用戶態代碼調用,且不會產生陷阱,也未被VMware正確虛擬化,所以這些異常都可能被用來探測VMware的存在。
1.使用Red Pill反虛擬機技術
RedPill通過運行sidt指令獲取IDTR寄存器的值。虛擬機監視器必須重新定位Guest系統的IDTR,來避免與Host系統的IDTR衝突。因為在虛擬機中運行sidt指令時,虛擬機監視器不會得到通知,所以會返回虛擬機的IDTR。RedPill通過測試這種差異來探測Vmware的使用。這種方法存在一個缺陷,由於IDT的值只針對處於正在運行的處理器而言,在單CPU中它是個常量,但當它處於多CPU時就可能會受到影響了,因為每個CPU都有其自己的IDT,這樣問題就自然而然的產生了。
針對此問題,Offensive Computing組織成員提出了兩種應對方法:
其中一種方法就是利用Red Pill反覆地在系統上循環執行任務,以此構造出一張當前系統的IDT值變化統計圖,但這會增加CPU負擔;
另一種方法就是windows API函數SetThreadAffinityMask()將線程限制在單處理器上執行,當執行此測試時只能準確地將線程執行環境限制在本地處理器,而對於將線程限制在VM處理器上就可能行不通了,因為VM是計劃在各處理器上運行的,VM線程在不同的處理器上執行時,IDT值將會發生變化,因此此方法也很少被使用。
2.使用No Pill反虛擬機技術
sgdt和sldt指令探測VMware的技術通常被稱為No Pill。
BOOL CheckVMWare()
{
ULONG xdt = 0 ;
ULONG InVM = 0;
__asm
{
push edx
sidt [esp-2]
pop edx
nop
mov xdt , edx
}
if (xdt > 0xd0000000)
{
InVM = 1;
}
else
{
InVM = 0;
}
__asm
{
push edx
sgdt [esp-2]
pop edx
nop
mov xdt , edx
}
if (xdt > 0xd0000000)
{
InVM += 1;
}
if (InVM == 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
通過禁用VMware加速可以防止No Pill技術的探測。
3.查詢I/O端口
VMware使用虛擬化的I/O端口完成宿主系統與虛擬機之間的通信,以便支持諸如複製和粘貼功能。這個端口可以被查詢,然後與一個magic數比較,以確定VMware的使用。
這種技術成功的關鍵在於x86體系結構中的in指令,它從一個源操作數指定的端口複製數據到目的操作數指定的內存地址。VMware會監視in指令的執行,並捕獲目的通信端口為0x5668(VX)的I/O。
VMware會檢查第二個操作數是否是VX,在這種情況發生時,EAX寄存器載入的值是0x564D5868(VMXh),ECX寄存器必須被載入你希望在端口上執行相應操作的值,值0xA表示 get VMware version type,0x14代表 get the memory size。
它們都可以被用來探測VMware,但0xA更受歡迎,因為它能確定VMware的版本。如代碼所示setz指令在magic數與VMXh匹配時設置返回值rc為1,如果在真實的機器上運行會觸發EXCEPTION_EXECUTE_HANDLER 異常,在異常處理中設置返回值rc為0。
BOOL CheckVMWare()
{
bool rc = true;
__try
{
__asm
{
push edx
push ecx
push ebx
mov eax, 'VMXh'
mov ebx, 0
mov ecx, 10
mov edx, 'VX'
in eax, dx
cmp ebx, 'VMXh'
setz [rc]
pop ebx
pop ecx
pop edx
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
rc = false;
}
return rc;
}
對付這種反虛擬化技術的最簡單方法是使用NOP指令替換in指令,或修補條件跳轉,使得它不論比較結果如何,都執行到未探測到虛擬機的程序分支。
4.使用str指令
在保護模式下運行的所有程序在切換任務時,對於當前任務中指向TSS的段選擇器將會被存儲在任務寄存器中,TSS中包含有當前任務的可執行環境狀態,包括通用寄存器狀態、段寄存器狀態、標誌寄存器狀態、EIP寄存器狀態等等,當此項任務再次被執行時,處理器就會其原先保存的任務狀態。
每項任務均有其自己的TSS,而我們可以通過STR指令來獲取指向當前任務中TSS的段選擇器。這裡STR指令是用於將任務寄存器(TR)中的段選擇器存儲到目標操作數,目標操作數可以是通用寄存器或內存位置,使用此指令存儲的段選擇器指向當前正在運行的任務的任務狀態段(TSS)。
在虛擬機和真實主機之中,通過STR讀取的地址是不同的,當地址等於0x0040xxxx時,說明處於虛擬機中,否則為真實主機。
BOOL CheckVMWare()
{
unsigned char mem[4] = {0};
__asm str mem;
if ((mem[0] == 0x00) && (mem[1] == 0x40))
{
return TRUE;
}
else
{
return FALSE;
}
}
在IDA PRO中,可以使用下面的腳本查找我們前面提到的指令。
from idautils import *
from idc import *
heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA()))
antiVM = []
for i in heads:
if (GetMnem(i) == "sidt" or GetMnem(i) == "sgdt" or GetMnem(i) == "sldt" or GetMnem(i) == "smsw" or GetMnem(i) == "str" or GetMnem(i) == "in" or GetMnem(i) == "cpuid"):
antiVM.append(i)
print "Number of potential Anti-VM instructions: %d" % (len(antiVM))
for i in antiVM:
SetColor(i, CIC_ITEM, 0x0000ff)
Message("Anti-VM: %08x\n" % i)
要在IDA PRO中運行腳本,選擇File->Script File,可以看到下面的輸出。
這個輸出表明腳本檢測到了三條漏洞指令類型。滾動到IDA PRO的反彙編窗口,我們看到三條紅色高亮顯示的指令sidt、str和sldt。
5.使用無效的操作碼
每臺機器都有一組定義的指令,通常稱為指令集架構(Instruction Set Architecture)。
當遇到無效指令(不存在於ISA中)時,機器引發無效操作碼異常。軟件可以處理異常(使用通常的try/catch機制),也可以讓操作系統處理異常,或者在最壞的情況下崩潰機器。
VirtualPC使用一堆無效指令來允許虛擬機和VirtualPC之間連接。當VirtualPC的虛擬機想要與VirtualPC通信時,程序設置異常處理程序(try/catch塊),在調用VM軟件之前設置所需的參數,發出特殊的無效操作碼指令。
VM軟件將識別此無效操作碼並相應地操作,如果VirtualPC存在則不引起異常,並且如果VirtualPC不存在則產生異常。
最後,程序的catch塊將處理異常並檢查返回的VM軟件的參數。總之,VirtualPC使用無效的操作碼機制作為後門。
DWORD IslnsideVPC_exceptionFilter(LPEXCEPTION_POINTERS ep)
{
PCONTEXT ctx=ep->ContextRecord;
ctx->Ebx = -1; //未運行在VPC中 ctx->Eip += 4; //跳過”call VPC”操作 return EXCEPTION_CONTINUE_EXECUTION;
}
BOOL CheckVirtualPC()
{
bool rc = TRUE;
__try
{
__asm
{
push ebx
mov ebx, 0
mov eax, 1
__emit 0fh
__emit 3fh
__emit 07h
__emit 0bh
test ebx, ebx
setz[rc]
pop ebx
}
}
__except(IslnsideVPC_exceptionFilter(GetExceptionInformation()))
{
rc = FALSE;
}
return rc;
}
三、基於社會工程學的技巧
1.檢測電腦中常用軟件的使用情況
名為 Intelligent Software Solutions Inc.doc 的惡意軟件樣本文件使用了下面的反虛擬機技巧,SHA256值為048fc07fb94a74990d2d2b8e92c099f3f986af185c32d74c857b07f7fcce7f8e。
RecentFiles對象表示系統最近打開過的歷史文檔。
通常,安裝了word程序的用戶可能會打開超過2個或更多數量的文檔。然而,當該惡意軟件植入到新創建的虛擬機和word環境中後,總是狀況不斷,不能正常運行。每次測試時手動打開一兩次,總是出現程序異常。即使是保存了虛擬機鏡像狀態,重啟調試分析後,惡意程序仍然不能正常執行。
從DKTxHE函數功能可以看出,惡意軟件以RecentFiles數量來判斷是否身處VM環境中,如果在VM環境中,它將不會執行任何惡意行為。之後,隨意創建了3個不同名稱的word文檔,逐一打開並關閉,讓歷史文檔數量為3,最終成功運行並檢測到了惡意軟件。
2. 探測殺毒軟件公司相關的IP地址
同樣是上面的惡意軟件,它在另一個子程序中使用了下面的反虛擬機技巧。
首先,它通過向遠程地址 https://www.maxmind.com/geoip/v2.1/city/me 發出某種認證請求,之後設置請求信息中的HTTP
Refer屬性和User-Agent值,訪問鏈接 https://www.maxmind.com/en/locate-my-ip-address 以此獲取宿主系統的地址信息。
獲取信息封裝於JSON格式文件中,包含國家、城市、或者與IP相關的組織機構等信息。IP信息的organization字段顯示為美國Comcast寬帶網絡供應商。
惡意軟件發出訪問請求後,獲取到宿主系統的相關信息將存儲於某個數組中。如果獲取到的組織機構名稱與JSON文件中的任何機構字符串匹配,惡意軟件將發生異常並停止運行。當然,列表中的機構名稱在代碼中是經過混淆的。
四、虛擬機逃逸
VMware等軟件中或多或少都存在一些安全漏洞,可以利用這些漏洞使宿主操作系統崩潰或者是在宿主操作系統中運行代碼。當主機系統被感染後,一些公開可用的工具可以用來對VMware等軟件進行攻擊。
五、總結
當遇到的惡意代碼似乎不能運行時,在使用調試或反彙編惡意代碼搜索其反虛擬機探測代碼之前,應該考慮使用一個卸載了VMware
Tools的虛擬機。VMware中有一些未文檔化的功能可以幫助減輕反虛擬機技術的探測。將下面的代碼放到VMware的.vmx文件中,以減輕虛擬機被探測的可能。
isolation.tools.getPtrLocation.disable = "TRUE"
isolation.tools.setPtrLocation.disable = "TRUE"
isolation.tools.setVersion.disable = "TRUE"
isolation.tools.getVersion.disable = "TRUE"
monitor_control.disable_directexec = "TRUE"
monitor_control.disable_chksimd = "TRUE"
monitor_control.disable_ntreloc = "TRUE"
monitor_control.disable_selfmod = "TRUE"
monitor_control.disable_reloc = "TRUE"
monitor_control.disable_btinout = "TRUE"
monitor_control.disable_btmemspace = "TRUE"
monitor_control.disable_btpriv = "TRUE"
monitor_control.disable_btseg = "TRUE"
參數directexec可以使用戶模式下的代碼被模擬執行而不是直接在硬件上運行,因此它可以挫敗一些反虛擬機技術。
前四條設置被VMware後門命令使用,它們的作用是使得運行在Guest系統中的VMware
Tools不能獲取宿主系統的信息。這些設置會禁用VMware
Tools的一些有用功能,並可能對虛擬機性能有嚴重負面影響。所以,僅當其他技術無效時再添加這些選項。當然,也可以將惡意代碼在其他虛擬環境或者物理主機上運行。
同反調試技術一樣,要想發現惡意代碼中的反虛擬機技術需要在長期調試過程中積累更多經驗。例如,看到一個代碼在一個條件跳轉處過早終止,這可能就是反虛擬機技術造成的結果。一如既往地警惕這種類型的問題,然後查看其之前的代碼,來確定它到底執行了什麼操作。
和反調試技術一樣,通過修改條件跳轉指令或者使用NOP指令覆蓋來繞過相關探測。
最後讓我們總結一下提到的內容:
騰訊2016遊戲安全技術競賽有一道題,大概意思就是給一個exe,要求編寫一個Tencent2016C.dll,並導出多個接口函數 CheckVirtualPCX、CheckVMWareX、CheckVirtualBoxX。X為1-100之間的數字。函數功能是檢測自己是否處於相應的虛擬機中,是返回TRUE,否則返回FALSE。函數的原型都是 typedef
BOOL (WINAPI*
Type_CheckXXXXXX)();。
編譯好dll之後,放在Tencent2016C.exe的同目錄,運行Tencent2016C.exe,點擊檢測按鈕,在物理機中運行時函數接口輸出為0,在VMware虛擬機、VirtualBox虛擬機和VirtualPC虛擬機中運行時,相關的接口輸出1。我們把提到的知識綜合一下完成這道題目。
解題的參考代碼和題目相關信息:https://github.com/houjingyi233/test-virtual-machine/
1.《惡意代碼分析實戰》第17章反虛擬機技術(本文的主體框架)
2.這個惡意軟件“奇葩”的反虛擬機技巧
3.天樞戰隊官方博客(本文大部分代碼的來源)
4.虛擬機檢測技術剖析
5.Detect if your program is running inside a Virtual Machine
閱讀更多 看雪學院 的文章