您的位置:首页 > 产品设计 > UI/UE

函数ZwQuerySystemInformation小结

2013-08-07 10:34 736 查看
函数存在于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信息等等,以后再玩~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: