Enumeration process (analysis of CreateToolhelp32Snapshot function)

Keywords: snapshot Windows React

I've written some code of enumeration process before. I'm reviewing the code I learned before these two days. I see the CreateToolhelp32Snapshot function. By the way, let's see its specific implementation.
The process of enumerating process information seems easy, just some Windows API functions:

  • CreateToolhelp32Snapshot
  • Process32First
  • Process32Next

By using these three functions, the enumeration of current system processes can be realized. However, the specific implementation of these three functions remains to be studied.
Start with CreateToolhelp32Snapshot:
It passes two parameters. The first parameter is a macro, which indicates the type of snapshot we need to take. Here we enumerate the process information, and naturally pass th32cs ﹣ snapprocess. The second parameter is the process ID. we usually pass in 0. What happens after passing in 0? The default is the current process.

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

Next, we will apply for a memory space in the current process, then call NtQuerySystemInformation to process information query, and return the query information in the parameter list.

for(;;)
    {
      (*ProcThrdInfoSize) += 0x10000;
      //Apply for memory at the current processor, read and write, and submit physical memory
      Status = NtAllocateVirtualMemory(NtCurrentProcess(),
                                       ProcThrdInfo,
                                       0,
                                       ProcThrdInfoSize,
                                       MEM_COMMIT,
                                       PAGE_READWRITE);
      if(!NT_SUCCESS(Status))
      {
        break;
      }
//Query process information and return it as a parameter
      Status = NtQuerySystemInformation(SystemProcessInformation,
                                        *ProcThrdInfo,
                                        *ProcThrdInfoSize,
                                        NULL);
      if(Status == STATUS_INFO_LENGTH_MISMATCH)
      {
      //If the length is not enough, release the memory, enter the for loop and add another page to reapply
        NtFreeVirtualMemory(NtCurrentProcess(),
                            ProcThrdInfo,
                            ProcThrdInfoSize,
                            MEM_RELEASE);
        *ProcThrdInfo = NULL;
      }
      else
      {
        break;
      }
    }

Next, create a Section object and map the virtual memory. Initialize the returned process information to the memory where the Section object is located and return the Section handle

...
//Create Section object, readable and writable, memory commit
 Status = NtCreateSection(&hSection,
                           SECTION_ALL_ACCESS,
                           &ObjectAttributes,
                           &SSize,
                           PAGE_READWRITE,
                           SEC_COMMIT,
                           NULL);
...
//Map in the current process, return the mapped address and assign it to a PTH32SNAPSHOT pointer
Status = NtMapViewOfSection(hSection,
                              NtCurrentProcess(),
                              (PVOID*)&Snapshot,
                              0,
                              0,
                              &SOffset,
                              &ViewSize,
                              ViewShare,
                              0,
                              PAGE_READWRITE);
......
//Put the previously queried process information into the mapped address
    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;
 ....//Above are some assignment statements
 //Finish the data we want to write, and finish the mapping
  Status = NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);

  if(NT_SUCCESS(Status))
  {
  //Returns a handle to the Section object for later use
    *SectionHandle = hSection;
  }

At this point, the code has been basically implemented, and then some resources that are no longer needed will be recycled.
The implementation of Process32First and Process32Next are basically the same:

//Internally map the handle of the section object and read out the contents in memory
 Status = NtMapViewOfSection(hSnapshot,
                              NtCurrentProcess(),
                              (PVOID*)&Snapshot,
                              0,
                              0,
                              &SOffset,
                              &ViewSize,
                              ViewShare,
                              0,
                              PAGE_READWRITE);
//Analyze the contents
    if(Snapshot->ProcessListCount > 0)
    {
      LPPROCESSENTRY32W Entries = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, Snapshot->ProcessListOffset);
      //Copy structure content
      Snapshot->ProcessListIndex = 1;
      RtlCopyMemory(lppe, &Entries[0], sizeof(PROCESSENTRY32W));
      Ret = TRUE;
    }
    //Free memory map
    NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);

Process32Next just uses the index to start traversal and copy the information of each process. I won't elaborate here. The most important three functions are implemented as a whole. I believe that based on this, I can understand the meaning of snapshot better.
With simple test code:

/*
typedef struct tagPROCESSENTRY32
{
	DWORD   dwSize;					//The number of bytes of this structure must be initialized to sizeof(PROCESSENTRY32) before calling Process32First
	DWORD   cntUsage;				//0  It's been a long time
	DWORD   th32ProcessID;          //Current process Id
	ULONG_PTR th32DefaultHeapID;	//0	 It's been a long time
	DWORD   th32ModuleID;           //0	 It's been a long time
	DWORD   cntThreads;				//Number of execution threads started by the process
	DWORD   th32ParentProcessID;    //Parent process Id
	LONG    pcPriClassBase;         //Basic priority of process creation thread
	DWORD   dwFlags;				//0  Reserved value not used
	TCHAR   szExeFile[MAX_PATH];    //Process name
} PROCESSENTRY32;
*/

void _tmain(int argc, _TCHAR **argv, _TCHAR **envp)
{
	_tsetlocale(LC_ALL, _T("chs"));
	TCHAR* v1 = (TCHAR*)(_T("Process enumeration:"));
	_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 must be initialized like this
	PROCESSENTRY32 ProcessEntry = { sizeof(PROCESSENTRY32) };

	BOOL IsOk = Process32First(SnapHandle, &ProcessEntry);
	//NtMapViewOfSection maps the Section object to virtual memory again
	//Read

	//Basically consistent with Process32First, extract the process list in PTH32SNAPSHOT one by one through index
	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);
	}
}

One disadvantage of snapshot function is that it can't update the process in the system in real time. If I close a process after taking a snapshot, that is, after the handle of the Section object returns, it will still enumerate the process that I closed. Similarly, when I start a process after taking a snapshot, I will not give the process information.

Note: the source code in the process of explaining the code comes from the open source code react OS

Today's exhortation: the darkness before dawn is as thick as the night.

Published 3 original articles, praised 0, visited 63
Private letter follow

Posted by cmw on Fri, 13 Mar 2020 00:09:04 -0700