反虛擬機器技術總結

Editor發表於2018-04-08

惡意程式碼編寫者經常使用反虛擬機器技術逃避分析,這種技術可以檢測自己是否執行在虛擬機器中。如果惡意程式碼探測到自己在虛擬機器中執行,它會執行與其本身行為不同的行為,其中最簡單的行為是停止自身執行。近年來,隨著虛擬化技術的使用不斷增加,採用反虛擬機器技術的惡意程式碼數量逐漸下降。惡意程式碼編寫者已經開始意識到,目標主機是虛擬機器,也並不意味著它就沒有攻擊價值。隨著虛擬化技術的不斷髮展和普通應用,反虛擬機器技術可能變得更加少見。這裡研究最常見的反虛擬機器技術(包括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反虛擬機器技術


Red Pill通過執行sidt指令獲取IDTR暫存器的值。虛擬機器監視器必須重新定位Guest系統的IDTR,來避免與Host系統的IDTR衝突。因為在虛擬機器中執行sidt指令時,虛擬機器監視器不會得到通知,所以會返回虛擬機器的IDTR。Red Pill通過測試這種差異來探測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



本文由看雪論壇 houjingyi 原創轉載請註明來自看雪社群



相關文章