反虛擬機器技術總結
惡意程式碼編寫者經常使用反虛擬機器技術逃避分析,這種技術可以檢測自己是否執行在虛擬機器中。如果惡意程式碼探測到自己在虛擬機器中執行,它會執行與其本身行為不同的行為,其中最簡單的行為是停止自身執行。近年來,隨著虛擬化技術的使用不斷增加,採用反虛擬機器技術的惡意程式碼數量逐漸下降。惡意程式碼編寫者已經開始意識到,目標主機是虛擬機器,也並不意味著它就沒有攻擊價值。隨著虛擬化技術的不斷髮展和普通應用,反虛擬機器技術可能變得更加少見。這裡研究最常見的反虛擬機器技術(包括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.根據當前程式資訊
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.根據特定的資料夾或檔案資訊
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.根據特定登錄檔資訊
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.根據特定服務名
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; }
二、查詢漏洞指令
1.使用Red Pill反虛擬機器技術
2.使用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埠
這種技術成功的關鍵在於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指令
BOOL CheckVMWare() { unsigned char mem[4] = {0}; __asm str mem; if ((mem[0] == 0x00) && (mem[1] == 0x40)) { return TRUE; } else { return FALSE; } }
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的反彙編視窗,我們看到三條紅色高亮顯示的指令sidt、str和sldt。
5.使用無效的操作碼
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.檢測電腦中常用軟體的使用情況
RecentFiles物件表示系統最近開啟過的歷史文件。通常,安裝了word程式的使用者可能會開啟超過2個或更多數量的文件。然而,當該惡意軟體植入到新建立的虛擬機器和word環境中後,總是狀況不斷,不能正常執行。每次測試時手動開啟一兩次,總是出現程式異常。即使是儲存了虛擬機器映象狀態,重啟除錯分析後,惡意程式仍然不能正常執行。從DKTxHE函式功能可以看出,惡意軟體以RecentFiles數量來判斷是否身處VM環境中,如果在VM環境中,它將不會執行任何惡意行為。之後,隨意建立了3個不同名稱的word文件,逐一開啟並關閉,讓歷史文件數量為3,最終成功執行並檢測到了惡意軟體。
2.探測防毒軟體公司相關的IP地址
四、虛擬機器逃逸
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 原創轉載請註明來自看雪社群
相關文章
- 虛擬機器檢測技術攻防2013-08-27虛擬機
- 容器技術和虛擬機器技術的對比2024-08-24虛擬機
- 虛擬化技術之kvm虛擬機器建立工具qemu-kvm2020-08-21虛擬機
- LUA指令碼虛擬機器逃逸技術分析2020-08-19指令碼虛擬機
- 深入Java虛擬機器之 -- 總結面試篇2019-05-06Java虛擬機面試
- 《深入理解Java虛擬機器》個人讀書總結——JAVA虛擬機器記憶體2017-05-06Java虛擬機記憶體
- 虛擬化技術之kvm虛擬機器建立工具virt-install2020-08-18虛擬機
- 虛擬機器遷移技術原理與應用2023-12-28虛擬機
- 深入理解虛擬機器、容器和Hyper技術2016-07-24虛擬機
- Java 虛擬機器總結給面試的你(下)2019-02-23Java虛擬機面試
- Java 虛擬機器總結給面試的你(中)2018-02-06Java虛擬機面試
- Java虛擬機器總結給面試的你(上)2018-02-01Java虛擬機面試
- Java虛擬機器體系結構深入研究總結2016-03-01Java虛擬機
- Dalvik虛擬機器、Java虛擬機器與ART虛擬機器2018-08-22虛擬機Java
- Linux雲端計算技術學習:虛擬機器基本結構講解2019-07-04Linux虛擬機
- 戴爾技術總監:虛擬技術將解放Linux(轉)2007-08-12Linux
- Java 虛擬機器之三:Java虛擬機器的記憶體結構2018-09-02Java虛擬機記憶體
- Qtum x86 虛擬機器技術文件連載(二)2019-01-28QT虛擬機
- 虛擬化技術(-)2015-03-18
- 虛擬化技術2015-11-18
- Linux雲端計算技術學習:虛擬機器堆記憶體結構2019-07-04Linux虛擬機記憶體
- 深入理解Java虛擬機器--個人總結(持續更新)2020-07-15Java虛擬機
- 修改VMware虛擬機器網路卡MAC地址的方法總結2012-11-26虛擬機Mac
- 虛擬化中的連結克隆技術2012-08-02
- java虛擬機器和Dalvik虛擬機器2020-04-04Java虛擬機
- Android 虛擬機器 Vs Java 虛擬機器2018-12-30Android虛擬機Java
- 虛擬化四、KVM虛擬化技術2019-03-30
- ☕【JVM技術指南】「JVM總結筆記」Java虛擬機器垃圾回收認知和調優的"思南(司南)"【下部】2021-09-14JVM筆記Java虛擬機
- 伺服器虛擬化技術深度科普2019-04-20伺服器
- 虛擬機器2011-01-13虛擬機
- 虛擬機器雙網路卡繫結2015-05-14虛擬機
- Java虛擬機器內部結構2011-08-26Java虛擬機
- 欠擬合與過擬合技術總結2021-06-14
- Docker技術( 容器虛擬化技術 )2019-10-18Docker
- Java 虛擬機器之一:Java 技術體系與平臺2018-09-01Java虛擬機
- 技術界中的虛擬機器、容器和沙箱的關係2022-11-16虛擬機
- 虛擬機器、容器與沙盒技術有什麼區別?2022-11-01虛擬機
- 連線虛擬機器oracle 和虛擬機器KEY2013-03-10虛擬機Oracle