使用PoolTag識別主機指紋
寫在前面的話
通常情況下,惡意軟體會對它所在的主機進行指紋識別,以便發現更多的資訊。這個過程會分析一些特定的資料,用來判斷惡意軟體是否在VM中執行,除此之外,還會檢測其他軟體的存在。例如,惡意軟體經常試圖找出系統監控工具是否在執行(procmon, sysmon等)和安裝了哪些AV軟體。在本文中,我們將介紹另一種被惡意軟體濫用的主機進行指紋識別的方法。
指紋識別的常用方法
首先,我們介紹幾種惡意軟體檢測VM環境的常見方法:
1.列舉程序
2.列舉載入的模組
3.列舉檔案
5.列舉載入的驅動程式
6.開啟特定裝置物件的控制代碼
7.列舉系統資源(CPU核心,RAM,螢幕解析度等)
PoolTag瞭解一下
如果您對Windows核心驅動程式的開發和分析有一定的經驗,那麼我想您應該熟悉 ExAllocatePoolWithTag
函式,該函式用於在核心級別上分配記憶體塊。這裡的關鍵部分是 Tag'
引數,用於為特定的分配提供某種標識。如果出現錯誤,例如由於記憶體損壞這種問題,我們可以使用指定的標記(最多4個字元)來將緩衝區與分配記憶體塊的核心驅動程式中的程式碼路徑關聯起來。這種方法可以檢測核心驅動程式的存在,因此,在核心中載入模組的軟體可能會繞過上面提到的指紋方法,這些方法依賴於驅動程式可能更改的資訊。換句話說,從惡意軟體作者的角度來看,它是用來檢測某些真正重要的東西很好選擇。例如,安全或監控軟體可能試圖通過在核心級別註冊回撥過濾器來隱藏其程序和檔案。分析師可能會試圖通過從登錄檔中刪除惡意軟體通常搜尋的東西來強化虛擬機器環境。但是,安全軟體供應商或分析人員可能並不會修改他們自己的程式或系統中VM環境使用的特定核心驅動程式,從而修改核心池分配的標記。
獲取PoolTag資訊
可以通過呼叫 NtQuerySystemInformation
函式併為 SysteminformationClass
引數選擇 SystemPoolTagInformation (0x16 )
來獲取此資訊 。MSDN上記錄了部分上述功能和相關的 SysteminformationClass
可能值,幸運的是,通過一些研究,我們找到研究人員完成的一些文件。Alex Ionescu在他的NDK專案中記錄了許多關於Windows內容。為了證明這個,我們編寫了一個可以自己的獲取和解析PoolTag資訊的小工具,但是如果你想用GUI方式,推薦使用PoolMonEx這個工具。原始碼如下:

您可以將其與 Nbtk
標記的 PoolMonEx
分配結果進行比較,如下所示。

QueryPoolTagInfo.cpp
#include "Defs.h" #include <iostream> using namespace std; int main() { NTSTATUS NtStatus = STATUS_SUCCESS; BYTE * InfoBuf = nullptr; ULONG ReturnLength = 0; _ZwQuerySystemInformation ZwQuerySystemInformation = (_ZwQuerySystemInformation)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwQuerySystemInformation"); do{ NtStatus = ZwQuerySystemInformation(SystemPoolTagInformation, InfoBuf, ReturnLength, &ReturnLength); if (NtStatus == STATUS_INFO_LENGTH_MISMATCH) { if (InfoBuf != nullptr) { delete[] InfoBuf; InfoBuf = nullptr; } InfoBuf = new (nothrow) BYTE[ReturnLength]; if (InfoBuf != nullptr) memset(InfoBuf, 0, ReturnLength); else goto Exit; } } while (NtStatus != STATUS_SUCCESS); PSYSTEM_POOLTAG_INFORMATION pSysPoolTagInfo = (PSYSTEM_POOLTAG_INFORMATION)InfoBuf; PSYSTEM_POOLTAG psysPoolTag = (PSYSTEM_POOLTAG)&pSysPoolTagInfo->TagInfo->Tag; ULONG count = pSysPoolTagInfo->Count; cout << "Count: " << count << endl << endl; for (ULONG i = 0; i < count; i++) { cout << "PoolTag: "; for (int k = 0; k < sizeof(ULONG); k++) cout << psysPoolTag->Tag[k]; cout << endl; if (psysPoolTag->NonPagedAllocs != 0) { cout << "NonPaged Allocs: " << psysPoolTag->NonPagedAllocs << endl; cout << "NonPaged Frees: " << psysPoolTag->NonPagedFrees << endl; cout << "NonPaged Pool Bytes Used: " << psysPoolTag->NonPagedUsed << endl; } else { cout << "Paged Allocs: " << psysPoolTag->PagedAllocs << endl; cout << "Paged Frees: " << psysPoolTag->PagedFrees << endl; cout << "Paged Pool Bytes Used: " << psysPoolTag->PagedUsed << endl; } psysPoolTag++; cout << endl << "-------------------------------" << endl; cout << endl << "-------------------------------" << endl << endl; } if (InfoBuf != nullptr) delete[] InfoBuf; Exit: cin.get(); return 0; }
Defs.h
#include <Windows.h> #define SystemPoolTagInformation (DWORD)0x16 #define STATUS_SUCCESS 0 #define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 typedef DWORD SYSTEM_INFORMATION_CLASS; typedef struct _SYSTEM_POOLTAG { union { UCHAR Tag[4]; ULONG TagUlong; }; ULONG PagedAllocs; ULONG PagedFrees; SIZE_T PagedUsed; ULONG NonPagedAllocs; ULONG NonPagedFrees; SIZE_T NonPagedUsed; }SYSTEM_POOLTAG, *PSYSTEM_POOLTAG; typedef struct _SYSTEM_POOLTAG_INFORMATION { ULONG Count; SYSTEM_POOLTAG TagInfo[ANYSIZE_ARRAY]; }SYSTEM_POOLTAG_INFORMATION, *PSYSTEM_POOLTAG_INFORMATION; typedef NTSTATUS(WINAPI *_ZwQuerySystemInformation)( _In_SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_PVOIDSystemInformation, _In_ULONGSystemInformationLength, _Out_opt_ PULONGReturnLength );
目標PoolTag資訊
為了更好的瞭解獲取的 PoolTag
資訊,有必要進行分析。通過搜尋對 locatepoolwithtag
函式的呼叫,我們可以記錄這些驅動程式使用的特定標籤,並將它們保留在我們的列表中。我們知道任何驅動程式都可以隨意使用任何標記,那麼,嘗試找到一些不太常見的、標準Windows核心驅動程式或物件沒有使用的標記是有非常意義的。話雖如此,但這種檢測特定驅動因素的方法可能會產生誤報。
PoolTag示例列表
>為了進一步證明,我們從特定的驅動程式中收集了一些PoolTag資訊。
VMWare
>vm3dmp.sys(標籤:VM3D)vmci.sys(標籤:CTGC,CTMM,QPMM等……)vmhgfs.sys(標籤:HGCC,HGAC,HGVS,HGCD等……)vmmemctl.sys(標籤:VMBL)vsock.sys(標籤:vskg,vskd,vsks等…)
Process Explorer
>procexp152.sys(標籤:PEOT,PrcX等…)
程序監視器
>procmon23.sys(標籤:Pmn)
SYSMON
>sysmondrv.sys(標籤:Sys1,Sys2,Sys3,SysA,SysD,SysE等…)
Avast Internet Security
> aswsnx.sys(標籤:’Snx’,Aw ++)(我們在第一個中使用單引號,因為它以空格字元結尾)aswsp.sys(標籤:pSsA,AsDr)
結論
就像其他方法一樣,這個方法也有優缺點。比如這種方法不容易被規避,特別是在64位Windows中,核心補丁保護(Patch Guard)不允許我們修改核心函式等,因此可以直接掛鉤 NtQuerySystemInformation
。此外,此方法不受來自使用者空間程序的特定程序,檔案和登錄檔項的訪問的驅動程式的影響。所以該方法可以用於識別指紋主機。通過搜尋作業系統中引入的Windows物件的特定標記,我們可以確定其主要版本。例如,通過比較不同版本的Windbg附帶的 poolTag
資訊 (pooltag.txt)
,在本例中為 Windows 8.1 x64
和 Windows 10 x64(Build 10.0.15063)
,我們能夠找到 Windows 10中使用的 PoolTag
通過 netio.sys
核心驅動程式,如 Nrsd
, Nrtr
, Nrtw
,但不在Windows 8.1中我們後來使用兩個虛擬機器進行了驗證,我們確實可以在Windows 10中找到至少有兩個上述標籤的池分配,而我們的Windows 8.1虛擬機器中沒有這些。話雖這麼說,核心驅動程式開發使用基於分配它們的模組及其用途有意義的標記是一種常見且良好的做法。另一方面,如前所述, PoolTag
可以隨便用,因此我們必須小心使用。最後要提到的是 PoolTag
資訊一直在變化,換句話說,記憶體塊經常被分配和解除分配,因此我們在選擇要搜尋的 PoolTag
時應該記住這一點 。
*參考來源labs,由周大濤編譯,轉載請註明來自FreeBuf.COM