函式存在於NTDLL.DLL動態連結庫中。NTDLL.DLL負責ring3與ring0之間的通訊。當使用子系統方式進行系統呼叫的時候,ntdll.dll和SSDT會配合使用。
關於ZwQuerySystemInformation這個函式可以用來查詢程式資訊、核心資訊、硬體資訊(例如CPU數目)、控制程式碼資訊、時間資訊等54個系統資訊。
該函式的原型是
NTSTATUS WINAPI ZwQuerySystemInformation(
__in SYSTEM_INFORMATION_CLASSSystemInformationClass,
__in_out PVOIDSystemInformation,
__in ULONGSystemInformationLength,
__out_opt PULONGReturnLength
);
至於第一個引數SYSTEM_INFORMATION_CLASS是一個列舉結構。列舉了所有的54個系統資訊。該結構在最後將會列舉出來。
一、使用者模式下的ZwQuerySystemInformation
在使用者模式下必須用LoadLibrary與GetProcAddress來獲取該函式地址。
程式碼如下,
先宣告一個函式。
typedef NTSTATUS (WINAPI *NTQUERYSYSTEMINFORMATION)(INSYSTEM_INFORMATION_CLASS,IN OUT PVOID,INULONG,OUTPULONG);
載入NTDLL.DLL,獲取函式地址。
NTQUERYSYSTEMINFORMATIONZwQuerySystemInformation = NULL;
ZwQuerySystemInformation =
(NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll.dll,"ZwQuerySystemInfromation");
舉例:列舉程式資訊
要想獲取程式資訊,必須使用第二個引數,第二個引數指向一塊記憶體。必須使用引數1中每個系統資訊對應的結構體來將該記憶體進行轉換。
假設我們要列舉程式資訊,必須使用下列結構,該結構描述了程式名,執行緒數,指向下一個模組的指標,建立時間等等。結構描述如下:
typedef struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta; //構成結構序列的偏移量;
ULONG ThreadCount; //執行緒數目;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime; //建立時間;
LARGE_INTEGER UserTime; //使用者模式(Ring 3)的CPU時間;
LARGE_INTEGER KernelTime; //核心模式(Ring 0)的CPU時間;
UNICODE_STRING ProcessName; //程式名稱;
KPRIORITY BasePriority; //程式優先權;
HANDLE ProcessId; //程式識別符號;
HANDLE InheritedFromProcessId; //父程式的識別符號;
ULONG HandleCount; //控制程式碼數目;
ULONG Reserved2[2];
VM_COUNTERS VmCounters; //虛擬儲存器的結構;
IO_COUNTERS IoCounters; //IO計數結構;
SYSTEM_THREADS Threads[1]; //程式相關執行緒的結構陣列;
}SYSTEM_PROCESSES,*PSYSTEM_PROCESSES;
迴圈程式如下:
PSYSTEM_PROCESSES psp=NULL;
//先為引數2設為空,dwNeedSize獲取儲存該結構體的記憶體大小
status = ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, NULL, 0, &dwNeedSize);
//若使用者提供的緩衝區大小不夠,則返回STATUS_INFO_LENGTH_MISMATCH,並返回實際需要的緩衝區大小
if ( status ==STATUS_INFO_LENGTH_MISMATCH ) {
pBuffer = new BYTE[dwNeedSize];
status =ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, (PVOID)pBuffer,dwNeedSize, NULL);
if ( status ==STATUS_SUCCESS )
{
psp = (PSYSTEM_PROCESSES)pBuffer; //強制轉換
printf("PID 執行緒數工作集大小程式名\n");
do {
printf("%-4d",psp->ProcessId);
printf(" %3d",psp->ThreadCount);
printf(" %8dKB",psp->VmCounters.WorkingSetSize/1024);
wprintf(L" %s\n",psp->ProcessName.Buffer);
psp = (PSYSTEM_PROCESSES)((ULONG)psp +psp->NextEntryDelta );
}while ( psp->NextEntryDelta != 0 );//迴圈遍歷
}
delete []pBuffer;
pBuffer =NULL;
}
二、核心模式下的ZwQuerySystemInformation
核心模式下的ZwQuerySystemInformation的地址的獲取沒有應用層那麼麻煩。直接宣告一下該函式即可。
NTSYSAPI
NTSTATUS
NTAPI ZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
注意這裡的開頭使用了一個NTKERNELAPI,這個巨集我不知道是幹啥的,就到幾個群裡問了一下,得到了答案,它是在winddk.h這個標頭檔案中宣告的,如下:
#if (defined(_NTDRIVER_) || defined(_NTDDK_) || defined(_NTIFS_) || defined(_NTHAL_)) && !defined(_BLDR_)
#define NTKERNELAPI DECLSPEC_IMPORT // wdm
#else
#define NTKERNELAPI
#endif
函式照上面的方法宣告之後就可以直接用了,如下是我的程式碼,基本和ring3沒多大區別:
//////////////////////////////////////////////////////////////////////////
//
// 使用ZwQuerySystemInformation函式列舉程式
//
//////////////////////////////////////////////////////////////////////////
VOID
EnumProcessList1()
{
ULONG cbBuffer = 0x10000;
ULONG dwCount = 0;
PVOID pBuffer = NULL;
PSYSTEM_PROCESS_INFORMATION pInfo;
pBuffer = ExAllocatePool(PagedPool, cbBuffer);
// 獲取程式資訊
KdPrint(("We Use ZwQuerySystemInformation!"));
ZwQuerySystemInformation( SystemProcessesAndThreadsInformation,
pBuffer,
cbBuffer,
NULL);
pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
for( ; ; )
{
dwCount++;
if (pInfo->ProcessId == 0)
{
KdPrint(("[%6d] System Idle Process", pInfo->ProcessId));
}
else
{
KdPrint(("[%6d] %wZ", pInfo->ProcessId, pInfo->ProcessName));
}
if (pInfo->NextEntryDelta == 0)
{
break;
}
pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta);
}
KdPrint(("ProcessCount = %d", dwCount));
ExFreePool(pBuffer);
}
這是一個C程式碼程式,該程式是在ring3層寫的,主要內容是獲取CPU個數,列舉程式,列舉核心模組。該程式碼是從網上下載的,因為要用到這個函式,所以小小地研究了一下。
------------------------------------------------------------------------------------------------------------------------------------------
簡單說,即呼叫第11號功能,列舉一下核心中已載入的模組。
部分程式碼如下:
//功能號為11,先獲取所需的緩衝區大小
ZwQuerySystemInformation(SystemModuleInformation,NULL,0,&needlen);
//申請記憶體
ZwAllocateVirtualMemory(NtCurrentProcess(),(PVOID*)&pBuf,0,&needlen,MEM_COMMIT,PAGE_READWRITE);
//再次呼叫
ZwQuerySystemInformation(SystemModuleInformation,(PVOID)pBuf,truelen,&needlen);
......
//最後,釋放記憶體
ZwFreeVirtualMemory(NtCurrentProcess(),(PVOID*)&pBuf,&needlen,MEM_RELEASE);
突出過程,省略了錯誤判斷,和呼叫其它的功能時操作並沒有什麼區別。
關鍵在返回的內容中,緩衝區pBuf的前四個位元組是已載入的模組總數,記為ModuleCnt,接下來就是共有ModuleCnt個元素的模組資訊陣列了。
//該結構如下:
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Count;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
//模組詳細資訊結構如下:
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
HANDLE Section;
PVOID MappedBase;
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT PathLength;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;
一個for迴圈,迴圈ModuleCnt次就OK了。
基於此,寫了三個簡單的函式。
void ShowAllModules(char *pBuf)
{
//函式功能:輸出所有模組資訊
//引數pBuf:ZwQuerySystemInformation返回的緩衝區首址
PSYSTEM_MODULE_INFORMATION_ENTRY pSysModuleInfo;
DWORD Modcnt=0;
Modcnt=*(DWORD*)pBuf;
pSysModuleInfo=(PSYSTEM_MODULE_INFORMATION_ENTRY)(pBuf+sizeof(DWORD));
for (DWORD i=0;i<Modcnt;i++)
{
printf("%d\t0x%08X 0x%08X %s\n",pSysModuleInfo->LoadOrderIndex,pSysModuleInfo->Base,pSysModuleInfo->Size,pSysModuleInfo->ImageName);
pSysModuleInfo++;
}
}
void GetOSKrnlInfo(char *pBuf,DWORD *KernelBase,char *szKrnlPath)
{
//函式功能:返回系統核心(ntoskrnl.exe或ntkrnlpa.exe)的基址和路徑
//引數pBuf:ZwQuerySystemInformation返回的緩衝區首址
//引數KernelBase:接收返回的系統核心的基址
//引數szKrnlPath:接收返回的核心檔案的路徑
PSYSTEM_MODULE_INFORMATION_ENTRY pSysModuleInfo;
DWORD Modcnt=0;
*KernelBase=0;
Modcnt=*(DWORD*)pBuf;
pSysModuleInfo=(PSYSTEM_MODULE_INFORMATION_ENTRY)(pBuf+sizeof(DWORD));
//其實第一個模組就是了,還是驗證一下吧
if (strstr((strlwr(pSysModuleInfo->ImageName),pSysModuleInfo->ImageName),"nt"))
{
*KernelBase=(DWORD)pSysModuleInfo->Base;
GetSystemDirectory(szKrnlPath,MAX_PATH);
lstrcat(szKrnlPath,strrchr(pSysModuleInfo->ImageName,'\\'));
}
}
void DetectModule(char *pBuf,DWORD dwAddress,char *ModulePath)
{
//函式功能:找出給定地址所在的模組
//引數pBuf:緩衝區地址,同上
//引數dwAddress:要查詢的核心地址
//引數ModulePath:接收返回的模組路徑
PSYSTEM_MODULE_INFORMATION_ENTRY pSysModuleInfo;
DWORD Modcnt=0;
Modcnt=*(DWORD*)pBuf;
pSysModuleInfo=(PSYSTEM_MODULE_INFORMATION_ENTRY)(pBuf+sizeof(DWORD));
for (DWORD i=0;i<Modcnt;i++)
{
if ((dwAddress>=(DWORD)pSysModuleInfo->Base)&&(dwAddress<(DWORD)pSysModuleInfo->Base+pSysModuleInfo->Size))
{
lstrcpy(ModulePath,pSysModuleInfo->ImageName);
}
pSysModuleInfo++;
}
}
該功能是通過遍歷PsLoadedModuleList實現的,所以要隱藏的話,最簡單的方法還是斷鏈~~
更高階的方法比如抹DriveObject,抹PE資訊等等,以後再玩~