Click here to Skip to main content
15,867,594 members
Articles / Containers / Virtual Machine
Article

Enumerating processes on NT/Win9x

Rate me:
Please Sign up or sign in to vote.
4.94/5 (14 votes)
9 Sep 20024 min read 226.1K   3.5K   49   47
A simple class encapsulating tlhelp32 and PSAPI

Sample running on Win2k

Motivation

Many programmers write software that require the set of running processes to be enumerated. Unfortunately there is no standard method of doing this. On Windows 95 and Windows 98 there are the ToolHelp API functions (found in Kernel32.dll). For some reason the Microsoft NT team didn't like the ToolHelp functions and decided not to add them to Windows NT. Instead they provided their own set of Process Status functions, the PSAPI, and added them to an external module (residing in psapi.dll). Finally, on Windows 2000, the developers chose to provide both methods of enumeration.

What this is

CEnumProcess is a simple class for enumerating running processes using either PSAPI or ToolHelp. The preferred method is decided on runtime. It contains two classes: CEnumProcess::CProcessEntry and CEnumProcess::CModuleEntry for storing results.

How it works

When creating an instance of the class, it attempts to load the appropriate modules and find PSAPI/ToolHelp related functions. Based on which set of functions it finds the class sets the enumeration method to the most appropriate. For example, this is the code for finding the PSAPI related functions:

// Try to load psapi.dll	
PSAPI = ::LoadLibrary(TEXT("PSAPI"));
if (PSAPI)  
{
     // Find PSAPI functions
     FEnumProcesses = (PFEnumProcesses)::GetProcAddress(PSAPI, 
                                                      TEXT("EnumProcesses"));
     FEnumProcessModules = (PFEnumProcessModules)::GetProcAddress(PSAPI, 
                                                      TEXT("EnumProcessModules"));
#ifdef UNICODE
     FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI, 
                                                      TEXT("GetModuleFileNameExW"));
#else
     FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI, 
                                                      TEXT("GetModuleFileNameExA"));
#endif
}

Usage

There are seven public functions in the class:
  • int GetAvailableMethods()
  • int GetSuggestedMethod()
  • int SetMethod(int method)
  • BOOL GetProcessNext(CProcessEntry *pEntry)
  • BOOL GetProcessFirst(CProcessEntry* pEntry)
  • BOOL GetModuleNext(DWORD dwPID, CModuleEntry* pEntry)
  • BOOL GetModuleFirst(DWORD dwPID, CModuleEntry* pEntry)
The enumerationmethod related functions sets/returns one of the values found in the namespace ENUM_METHOD below:
namespace ENUM_METHOD 
{const int NONE    = 0x0;
 const int PSAPI   = 0x1;
 const int TOOLHELP= 0x2;
 const int PROC16  = 0x4;
} 

ENUM_METHOD::NONE is used for the unlikely event that no method can be found, like if an NT-user has deleted psapi.dll. Under Windows 2000, GetAvailableMethods returns ENUM_METHOD::PSAPI + ENUM_METHOD::TOOLHELP. The suggested method will be to use the ToolHelp API. If 16-bit processes should be enumerated ENUM_METHOD::PROC16 should be added to the enumerationmethod. This is usually done by default.

More interesting are the GetProcess/GetModule functions. They take a pointer to a CProcessEntry/CModuleEntry as input, and returns TRUE/FALSE depending on success or failure in enumeration. The useful members of the classes are as follows:

CProcessEntry

LPTSTR lpFilename;  // name of file
DWORD  dwPID;	    // ID of the process	
WORD   hTask16;	    // If this is a 16-bit process, 
                    // return task handle here, otherwise 0

The member hTask16 is only used on NT and Win2k. If hTask16 is anything other than 0 on these operating systems, this is a 16-bit process. In that case lpFilename will be the path of the 16-bit process, and dwPID will be the identifier of the NTVDM currently run under (see "A WORD on 16-bit processes").

Note that if using ToolHelp under Win2k lpFileName will not be the full path of the file. Try using GetModuleFirst to get the first module loaded (the .exe itself), and then retrive the full path from CModuleEntry.

CModuleEntry

LPTSTR lpFilename;    // path of file
PVOID pLoadBase;      // Loading address
PVOID pPreferredBase; // Address specified in fileheader

All this means you can write code like this, and it will work under both Win9x and NT.

CEnumProcess enumeration;
CEnumProcess::CProcessEntry entry;

for (BOOL OK = enumeration.GetProcessFirst(&entry); OK; 
     OK = enumeration.GetProcessNext(&entry) )
     {
         // Do something useful
         TRACE("PID = %X, process = %s\n", entry.dwPID, entry.lpFilename);
     }

Why the preferred base is important

I have found that many modules does not load at their preferred baseaddress. If a module does not load at its preferred base, the application using it will require more memory and take a performance hit when initializing. This is because a module can only be mapped to its .dll on disk if the baseaddress is equal to the loadaddress. This was the reason for including the preferred base in the moduleentry in the first place. Usually is it due to lazy programmers, who do not rebase their modules from the the default address (0x10000000, or Visual Basic 0x11000000). It could also be hard to predict where a module can find some place to load. The function for finding the preferred base is found below:

PVOID CEnumProcess::GetModulePreferredBase(DWORD dwPID, PVOID pModBase)
{if (ENUM_METHOD::NONE == m_method) return NULL; 
 HANDLE hProc = OpenProcess(PROCESS_VM_READ, FALSE, dwPID);
 if (hProc)
 {IMAGE_DOS_HEADER idh = {0};
  IMAGE_NT_HEADERS inh = {0};
  //Read DOS header
  ReadProcessMemory(hProc, pModBase, &idh, sizeof(idh), NULL);

  if (IMAGE_DOS_SIGNATURE == idh.e_magic) // DOS header OK?
      // Read NT headers at offset e_lfanew 
      ReadProcessMemory(hProc, (PBYTE)pModBase + idh.e_lfanew,
                        &inh, sizeof(inh), NULL);

  CloseHandle(hProc); 
    
  if (IMAGE_NT_SIGNATURE == inh.Signature) //NT signature OK?
   // Get the preferred base...
   return (PVOID) inh.OptionalHeader.ImageBase; 
 }
 return NULL; //didn't find anything useful..
}
For those new to the PE format, these are standard headers of an executable file. When an executable is loaded into memory, the entire file is mapped - even the headers. A lot of useful information can be found here, like the size of the mapped file.

A WORD on 16-bit processes

Under Windows 95 and Windows 98 ToolHelp API treats 16-bit applications just like any other process. This is not the case under NT. Instead CEnumProcess will return the name of the NT virtual DOS machine (NTVDM) it is currently run under. This means that if you run the testapplication and find some processnames like "ntvdm.exe", you have found a 16-bit process. If ENUM_METHOD::PROC16 is currently set, the next entries returned by GetNextProcess will be the 16-bit processes that currently run in this virtual machine. This is done by using the VDMDBG API - a set of functions for debugging 16-bit applications. VDMDBG allows enumeration of modules, but only if the process is acting as a debugger. This require that the 16-bit debugger WOWDEB.EXE task runs in the VM currently debugged. Attaching as a debugger is not a very good idea, though. There is no way of unattaching and if the debugger process terminates so will the debugee. For that reason enumeration of 16-bit modules is not included in the class.

Known bugs/limitations

Some processes has a sequrity attribute that prevents reading its memory. This means that modules can not be enumerated. If using PSAPI, not even the filename can be retrieved.

Contact

Send suggestions, improvements and comments to me.

History

10 Sep 2002 - updated downloads

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Sweden Sweden
Been interested in computers since he got his hands on an ABC 80, and finally managed to get it to play a simple tune (the ABC 80 didn't have a soundchip, just some "prerecorded" sounds you could address with assembler. It did, however, create some sweet?? music when reading a program from tape).

Mostly selftaught in Visual Studio, he is currently working as a freelance programmer.

Comments and Discussions

 
QuestionLicense? Pin
Member 1363478220-Jan-18 20:04
Member 1363478220-Jan-18 20:04 
Generalpacket sniffer Pin
sura_b4_u16-May-05 2:09
sura_b4_u16-May-05 2:09 
Generalreference sample letter personal Pin
Anonymous7-Mar-05 13:31
Anonymous7-Mar-05 13:31 
GeneralRe: reference sample letter personal Pin
Ravi Bhavnani7-Mar-05 14:47
professionalRavi Bhavnani7-Mar-05 14:47 
GeneralI could not run this program!! Pin
aakillaa9-Feb-05 20:28
aakillaa9-Feb-05 20:28 
GeneralDrive output to a text file Pin
hamudi-doodi8-Dec-04 10:58
hamudi-doodi8-Dec-04 10:58 
GeneralRe: Drive output to a text file Pin
moliate12-Dec-04 11:55
moliate12-Dec-04 11:55 
GeneralGetModuleBaseName question Pin
Tom Wright10-Feb-04 8:04
Tom Wright10-Feb-04 8:04 
GeneralRe: GetModuleBaseName question Pin
moliate10-Feb-04 10:22
moliate10-Feb-04 10:22 
GeneralRe: GetModuleBaseName question Pin
Tom Wright10-Feb-04 16:57
Tom Wright10-Feb-04 16:57 
QuestionHow to enumerate threads of a process Pin
Member 5517561-Sep-03 18:06
Member 5517561-Sep-03 18:06 
AnswerRe: How to enumerate threads of a process Pin
moliate5-Sep-03 6:45
moliate5-Sep-03 6:45 
Generalsize of process/module Pin
Michael Hendrickx26-Jun-03 14:16
Michael Hendrickx26-Jun-03 14:16 
Hi,

I'm trying to read in 'all' memory from a certain process.. I can load them all, get all the modules in it, but when I want to read the memory out of it.. using the following (relevant) snippets of code:

PROCESS_MEMORY_COUNTERS pmc;
GetProcessMemoryInfo(pHnd, &pmc, sizeof(PROCESS_MEMORY_COUNTERS))){
memSize = pmc.WorkingSetSize;

Then,

ReadProcessMemory(pHnd,(void *)startAddress,value,memSize,0);

fails. (startAddress is, obviously, the base address of the modules, and value is a malloc'ed chunk of memory.. )

When I set memSize to 8192 everything works just fine.. Does anyone know what I am doing wrong? Confused | :confused:

Thank you,
Michael Hendrickx

GeneralRe: size of process/module Pin
moliate26-Jun-03 22:22
moliate26-Jun-03 22:22 
GeneralRe: size of process/module Pin
Anonymous7-Mar-05 13:31
Anonymous7-Mar-05 13:31 
QuestionRun time problem with VC++ 7 ? Pin
atakacs12-Apr-03 10:46
atakacs12-Apr-03 10:46 
QuestionRemove MFC dependencies ? Pin
Jerry Evans26-Feb-03 7:51
Jerry Evans26-Feb-03 7:51 
AnswerRe: Remove MFC dependencies ? Pin
moliate27-Feb-03 10:17
moliate27-Feb-03 10:17 
GeneralZwQuerySystemInformation Pin
Eugene Polonsky5-Dec-02 12:55
Eugene Polonsky5-Dec-02 12:55 
GeneralRe: ZwQuerySystemInformation Pin
moliate9-Dec-02 7:40
moliate9-Dec-02 7:40 
GeneralRe: ZwQuerySystemInformation Pin
Eugene Polonsky9-Dec-02 8:21
Eugene Polonsky9-Dec-02 8:21 
GeneralRe: ZwQuerySystemInformation Pin
moliate14-Dec-02 16:50
moliate14-Dec-02 16:50 
Generalretrieveing process version Pin
Black ghost2-Oct-02 21:32
Black ghost2-Oct-02 21:32 
GeneralRe: retrieveing process version Pin
moliate3-Oct-02 1:44
moliate3-Oct-02 1:44 
GeneralThe Source has been updated... Pin
moliate11-Sep-02 4:36
moliate11-Sep-02 4:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.