函式ZwQuerySystemInformation小結

whatday發表於2013-08-07
函式存在於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資訊等等,以後再玩~


 

相關文章