欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

枚举进程(CreateToolhelp32Snapshot函数剖析)

程序员文章站 2022-07-05 11:53:58
...

之前写过一些枚举进程的代码,这两天正在回顾之前学习的代码,看到了CreateToolhelp32Snapshot函数,就顺便来看看它的具体实现吧。
实现枚举进程信息的这个过程看起来是比较easy的,只不过是一些Windows的API函数罢了:

  • CreateToolhelp32Snapshot
  • Process32First
  • Process32Next

就利用这三个函数,便可实现当前系统进程的枚举工作,但是这三个函数的具体实现,还是有待考究。
先从CreateToolhelp32Snapshot说起:
其传递两个参数,第一参数是一个宏,说明我们需要拍摄快照的类型,我们在此处是枚举进程信息,自然就传递TH32CS_SNAPPROCESS。第二个参数是进程ID,我们一般传入0,传入0之后会发生什么呢?原来是默认当前进程。

  if(th32ProcessID == 0)
  {
    th32ProcessID = GetCurrentProcessId();
  }

接下来,会在当前进程中申请一块内存空间,然后调用NtQuerySystemInformation进行进程信息查询,对查询到的信息在参数列表中进行返回。

for(;;)
    {
      (*ProcThrdInfoSize) += 0x10000;
      //在当前进程家申请内存,可读可写,并且提交物理内存
      Status = NtAllocateVirtualMemory(NtCurrentProcess(),
                                       ProcThrdInfo,
                                       0,
                                       ProcThrdInfoSize,
                                       MEM_COMMIT,
                                       PAGE_READWRITE);
      if(!NT_SUCCESS(Status))
      {
        break;
      }
//查询进程信息  并且作为参数返回
      Status = NtQuerySystemInformation(SystemProcessInformation,
                                        *ProcThrdInfo,
                                        *ProcThrdInfoSize,
                                        NULL);
      if(Status == STATUS_INFO_LENGTH_MISMATCH)
      {
      //如果长度不够,则释放内存,进入for循环再增加0x10000重新申请
        NtFreeVirtualMemory(NtCurrentProcess(),
                            ProcThrdInfo,
                            ProcThrdInfoSize,
                            MEM_RELEASE);
        *ProcThrdInfo = NULL;
      }
      else
      {
        break;
      }
    }

接下来创建一个Section对象,并进行虚拟内存映射,将返回的进程信息初始化到Section对象所在的内存,返回Section句柄

...
//创建Section对象,可读可写,内存提交
 Status = NtCreateSection(&hSection,
                           SECTION_ALL_ACCESS,
                           &ObjectAttributes,
                           &SSize,
                           PAGE_READWRITE,
                           SEC_COMMIT,
                           NULL);
...
//在当前进程进行映射,返回映射出来的地址赋值给一个PTH32SNAPSHOT指针
Status = NtMapViewOfSection(hSection,
                              NtCurrentProcess(),
                              (PVOID*)&Snapshot,
                              0,
                              0,
                              &SOffset,
                              &ViewSize,
                              ViewShare,
                              0,
                              PAGE_READWRITE);
......
//将之前查询出来的进程信息放到映射出来的地址中
    ProcessListEntry = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, DataOffset);
    ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)ProcThrdInfo;
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)ProcessInfo + ProcOffset);

      ProcessListEntry->dwSize = sizeof(PROCESSENTRY32W);
      ProcessListEntry->cntUsage = 0; /* no longer used */
      ProcessListEntry->th32ProcessID = (ULONG_PTR)ProcessInfo->UniqueProcessId;
      ProcessListEntry->th32DefaultHeapID = 0; /* no longer used */
      ProcessListEntry->th32ModuleID = 0; /* no longer used */
      ProcessListEntry->cntThreads = ProcessInfo->NumberOfThreads;
      ProcessListEntry->th32ParentProcessID = (ULONG_PTR)ProcessInfo->InheritedFromUniqueProcessId;
 ....//上面是部分赋值语句
 //写完我们要写的数据,结束映射
  Status = NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);

  if(NT_SUCCESS(Status))
  {
  //返回Section对象的句柄,以便后续使用
    *SectionHandle = hSection;
  }

至此代码大体实现已经完成,接下来会对一些不再需要的资源进行回收。
Process32First与Process32Next的实现基本一致:

//内部照样对section对象的句柄进行内存映射,读出内存中的内容
 Status = NtMapViewOfSection(hSnapshot,
                              NtCurrentProcess(),
                              (PVOID*)&Snapshot,
                              0,
                              0,
                              &SOffset,
                              &ViewSize,
                              ViewShare,
                              0,
                              PAGE_READWRITE);
//对里面的内容进行解析
    if(Snapshot->ProcessListCount > 0)
    {
      LPPROCESSENTRY32W Entries = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, Snapshot->ProcessListOffset);
      //进行结构体内容拷贝
      Snapshot->ProcessListIndex = 1;
      RtlCopyMemory(lppe, &Entries[0], sizeof(PROCESSENTRY32W));
      Ret = TRUE;
    }
    //释放内存映射
    NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);

Process32Next只不过是利用索引开始进行遍历,拷贝各个进程的信息,在这里就不做赘述,最重要的三个函数整体实现过程就是这样,我相信在此基础之上更能理解快照的含义。
附简单测试代码:

/*
typedef struct tagPROCESSENTRY32
{
	DWORD   dwSize;					//该结构体字节数,在调用Process32First前必须初始化为sizeof(PROCESSENTRY32)
	DWORD   cntUsage;				//0  很久不用了
	DWORD   th32ProcessID;          //当前进程Id
	ULONG_PTR th32DefaultHeapID;	//0	 很久不用了
	DWORD   th32ModuleID;           //0	 很久不用了
	DWORD   cntThreads;				//进程启动的执行线程数
	DWORD   th32ParentProcessID;    //父进程Id
	LONG    pcPriClassBase;         //进程创建线程的基本优先级
	DWORD   dwFlags;				//0  保留值 未使用
	TCHAR   szExeFile[MAX_PATH];    //进程名
} PROCESSENTRY32;
*/

void _tmain(int argc, _TCHAR **argv, _TCHAR **envp)
{
	_tsetlocale(LC_ALL, _T("chs"));
	TCHAR* v1 = (TCHAR*)(_T("进程枚举:"));
	_tprintf(_T("%s\n"), v1);
	KtEnumSystemProcess();
	_tprintf(_T("Input AnyKey to Exit\r\n"));
	getchar();
}
VOID KtEnumSystemProcess()
{

	HANDLE SnapHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	
	if (SnapHandle == INVALID_HANDLE_VALUE)
	{
		return;
	}
	//PROCESSENTRY32 必须这样初始化
	PROCESSENTRY32 ProcessEntry = { sizeof(PROCESSENTRY32) };

	BOOL IsOk = Process32First(SnapHandle, &ProcessEntry);
	//NtMapViewOfSection再次映射Section对象到虚拟内存中
	//进行读操作

	//与Process32First基本一致,通过索引对PTH32SNAPSHOT中的进程列表一一提取
	for (; IsOk; IsOk = Process32Next(SnapHandle, &ProcessEntry))
	{
		_tprintf(_T("ProcessName:%s "), ProcessEntry.szExeFile);
		_tprintf(_T("ThreadPriority:%d "), ProcessEntry.pcPriClassBase);
		_tprintf(_T("ProcessID:%d \r\n"), ProcessEntry.th32ProcessID);
		_tprintf(_T("ParentID:%d \r\n"), ProcessEntry.th32ParentProcessID);
	}
}

拍摄快照这个函数有一个弊端就是不能实时更新系统中的进程,如果我在拍摄好快照之后,也就是Section对象的句柄返回之后,将某个进程关闭,它还是会枚举出被我关闭的这个进程。同理,当我在拍摄快照之后,再启动一个进程,也不会枚举出进程的信息。

注:讲解代码过程中的源代码来自开源代码ReactOS

今日自勉:夜色之浓,莫过于黎明前的黑暗。

相关标签: Windows编程(Ring3)