PSAPI and You - The Early Years (Processes and Modules)

By: The RudeBoy
Email: rudeboy@gmx.de

     Many of you have probably read Iczelion's article here on cornsoup about using toolhelp32 to list processes and modules under Win9x. Well, if you noticed at the very end of his essay he mentions that to do this under winnt you must use psapi.dll. Technically, this isn't true as psapi.dll gets it's information by calling functions located in winnt.dll. However, these functions may change, but the psapi.dll functions are supposedly going to stay the same in future versions of Windows NT (including Windows 2000).

     I will not be going into all of the functions contained in psapi.dll in this essay. Here I will simply be addressing the functions dealing directly with Processes and Modules.

These functions are:

EnumProcesses
EnumProcessModules
GetModuleBaseName
GetModuleFileNameEx
GetModuleInformation
GetProcessMemoryInfo

     The first function we will look at is EnumProcesses. This function fills an array of DWORD's with the Process ID of all the processes currently running on the system. Here is the functions declaration:

EnumProcesses PROTO lpidProcess:DWORD, cb:DWORD, cbNeeded:DWORD

lpidProcess is a pointer to the array of DWORD's that the PID's will be stored.
cb is the size of lpidProcess. (# of elements * sizeof DWORD).
cbNeeded is a pointer to a dword which will contain the size of the Array needed for lpidProcess.

Unfortunately, you can never know how large the lpidProcess array will need before you call EnumProcesses. The value of cbNeeded will tell you the number of bytes needed for the array up to cb. The way you know that you have gotten all of the PID's in the list is by comparing cb to cbNeeded. if cbNeeded is less than cb, you have gotten all of the PID's and you can continue in your loop. Here is the code (MASM) to fill the array of PID's:
local dwSize:DWORD	;cbNeeded
local dwSize2:DWORD	;cb
local lpdwPID:DWORD	;Array of PID's
local dwPIDCnt:DWORD	;The number of PID's in the array
	mov	eax, 256	;# of PIDS to look for on first try
	shl	eax, 2		;multiplied by 4 (sizeof(DWORD))
	mov	dwSize2, eax	;store this value
	mov	lpdwPID, 0		
@loopy:
	cmp	lpdwPID, 0		;if the array already points to a location, we have
	jz	Size2OK			;to free and reallocate the memory
	invoke	GetProcessHeap		;get the heap of the current process
	mov    ebx, eax			;masm wouldn't let me compile without this :/
	invoke	HeapFree, ebx, 0, addr lpdwPID	;Free the space from the heap
	mov	edx, dwSize2	
	shl	edx, 1			;double the size of the heap to be allocated
	mov	dwSize2, edx
Size2OK:
	invoke	GetProcessHeap			;get the heap of the current process
	invoke	HeapAlloc, eax, 0, dwSize2	;allocate all the space we need
	test	eax, eax			;make sure the HeapAlloc call was successful
	jnz	AllocOK
	jmp	ErrorReturn
AllocOK:
	mov    lpdwPID, eax		;Make lpdwPID point to the allocated memory
	invoke  EnumProcesses, lpdwPID, dwSize2, addr dwSize	;call EnumProcesses
	test	eax, eax		;Test for the success of the call
	jnz	EnumProcsOK
	invoke	GetProcessHeap		;If unsuccessful, free the memory and exit
	invoke	HeapFree, eax, 0, lpdwPID
	jmp	ErrorReturn
EnumProcsOK:
	mov	eax, dwSize			
	cmp	eax, dwSize2		;compare the size of the array to the size needed
	jz	@loopy			;if they are the same, keep looping
	shr	eax, 2			;the # of PID's returned = size of the array/4
	mov	dwPIDCnt, eax
	ret
ErrorReturn:
	invoke	GetLastError	;Error reporting code ;)
	invoke      FormatMessage, FORMAT_MESSAGE_FROM_SYSTEM, 0, eax,0, addr szError, 255,0
	invoke	MessageBox, NULL, addr szError, addr AppName, 0
	mov	eax, -1
	ret

     Ok, now that we have a whole bunch of PID's, what are we gonna do about it? Well, truthfully a PID doesn't do a whole lot for you, but it does let you call OpenProcess to get a process handle, which is useful. When calling OpenProcess the best flags to use are PROCESS_QUERY_INFORMATION and PROCESS_VM_READ. If you try PROCESS_ALL_ACCESS, many of the processes will not open, but if you try only PROCESS_QUERY_INFORMATION, some of the psapi functions will not work properly. Even with these flags some of the system processes will not open and you will have to deal with these processes separately. Once you have a process handle, you can call EnumProcessModules, prototyped here:

EnumProcessModules PROTO hProcess:DWORD, lphModule:DWORD, cb:DWORD, lpcbNeeded:DWORD

hProcess is the handle to the process. lphModule is a pointer to an array of DWORDs, it will be filled with module handles cb size of the above array. lpcbNeeded pointer to a DWORD, this will be the number of bytes needed.
The same rules that apply with EnumProcesses apply here, you don't know how many modules there will be. However, often you will only want to get the first module from the list. Doing this is fairly simple, and example code follows. If you want to get all of the modules, modify the code for EnumProcesses to call EnumProcessModules instead. This will ensure that you are allocating the necessary memory for your application.
	local	hProcess:DWORD	;Process Handle
	local	hModule:DWORD	;Module Handle
	local	dwBytes:DWORD	;Not Really used ;)
	mov	edi, lpdwPID		;Pointer to our PID array
					;Now call openprocess with the first PID in the array
	invoke	OpenProcess, PROCESS_QUERY_INFORMATION + PROCESS_VM_READ, FALSE, dword ptr [edi]
	mov	hProcess, eax		;Move the returned value into the Process Handle
	test	eax, eax		;Make sure the call was successful
	jz	OpenProcessFailed
					;Call EnumProcessModules (we're only getting the handle to the first module)
	invoke	EnumProcessModules, hProcess, addr hModule, 4, addr dwBytes
	test	eax, eax		;Make sure the call succeeded
	jz	EnumFailed

     Cool! Now we have a Process Handle and a Module Handle. This is all fine and dandy as long as you're running only 32 bit apps. Now I must lead into a quick segway. Windows NT handles 16 bit apps by running them through a Virtual DOS Machine (VDM for short). So, in order to list the running 16 bit apps, you have to find the VDM (NTVDM.EXE), and use it's PID for a call to VDMEnumTaskWOWEx. This function is located in vdmdbg.dll. This function takes an argument to a callback proc which is called for each 16bit application running in the system. Here are the prototypes:

VDMEnumTaskWOWEx PROTO dwProcessID:DWORD, lpCallbackProc:DWORD, lParam:DWORD
Enum16Callback PROTO dwThreadID:DWORD, hMod16:WORD, hTask16:WORD, lpszModName:DWORD, lpszFileName:DWORD, lParam:DWORD

Now, for VDMEnumTaskWOWEx here's the info:
dwProcessID is the PID of NTVDM.EXE
lpCallbackProc is a pointer to the callback proc
lParam is a user-defined variable passed to the callback proc

And, for the callback proc:
dwThreadID the thread ID, dummy ;)
hMod16 the 16bit module handle.
hTask16 the 16bit task handle.
lpszModName pointer to the module name
lpszFileName pointer to the file name (the difference between these 2 is explained in the next section)
lParam look up at the info for VDMEnumTaskWOWEx

Example Code:
	invoke	lstrcmpi, addr szFileName, addr szNtVDM		;Test to see if this is the NTVDM
	test	eax, eax
	jnz	EnumFailed
	invoke	VDMEnumTaskWOWEx, dword ptr [edi], addr Enum16Proc, addr szBlank ;call VDMEnumTaskWOWEx
EnumFailed:
...
;The callback proc
Enum16Proc PROC dwThreadID:DWORD, hMod16:WORD, hTask16:WORD, szModName:DWORD, pszFileName:DWORD, lpUserDefined:DWORD
    push  ebx				;save the regs that we use (bad things happen if you don't do this)
    inc	dwCount				;this is a variable for our listview
    mov   ebx, dwCount			;everything else is listview code
	mov	eax, offset szBlank
	mov	_item.iitem, ebx
	mov	_item.pszText, eax
	mov 	_item.iSubItem, 0
	invoke 	SendDlgItemMessage, hWnd, IDC_LIST,LVM_INSERTITEM,ebx, addr _item

	mov	eax, szModName		;We're gonna display the Module Name
	mov	_item.iitem, ebx
	mov	_item.pszText, eax
	mov 	_item.iSubItem, 1
	invoke 	SendDlgItemMessage, hWnd, IDC_LIST,LVM_SETITEMTEXT,ebx, addr _item
	xor    eax, eax			;If you want to continue enumerating the 16bit apps, you have to return false
    pop   ebx
	ret
Enum16Proc ENDP

Back to our Process Handle and Module Handle, what can we do with them? Well, here is where the rest of the procs we will be covering in this article come in. GetModuleBaseName and GetModuleFileNameEx are very similar. The difference is that GetModuleFileNameEx returns the whole path to the file and GetModuleBaseName only returns the file name. Here are their prototypes:
GetModuleBaseName PROTO hProcess:DWORD, hModule:DWORD, lpBaseName:DWORD, nSize:DWORD
GetModuleFileNameEx PROTO hProcess:DWORD, hModule:DWORD, lpFilename:DWORD, nSize:DWORD

Yes, that's right, they're nearly identical.
hProcess the handle to the process. (Haven't you figured this out by now?)
hModule the handle to the module. (Bet you couldn't have guessed that.)
lpBaseName/Filename pointer to the buffer to receive the Base/File name.
nSize ize of the aforementioned buffer. (I get bored and use big words sometimes.)

Next in our bag of psapi.dll function is GetModuleInformation. This is a pretty simple function which fills in a struct full of information about the module. None of this information is particularly difficult to get without this function, but since it's there, I'll cover it. Here is the prototype:

GetModuleInformation PROTO hProcess:DWORD, hModule:DWORD, lpmodinfo:DWORD, cb:DWORD

hProcess is the handle to the process. hModule is the handle to the module. lpmodinfo is the pointer to a MODULEINFO struct. (I'll cover this in a minute) cb is the size of the struct. (HINT: invoke blahblah, sizeof MODULEINFO) ;)
The MODULEINFO struct:
 
MODULEINFO STRUCT
	lpBaseOfDll	DWORD ?		;The imagebase of the module (same as the hModule)
	SizeOfImage	DWORD ?		;Size of the image (from the PE Header in memory)
	EntryPoint	DWORD ?		;Program Entry Point (from the PE Header in memory)
MODULEINFO ENDS

GetProcessMemoryInfo is the last proc that we will be covering here. Like GetModuleInformation, it fills a struct full of info about the process. 'On With the Prototype!' you say:

GetProcessMemoryInfo PROTO hProcess:DWORD, ppsmemCounters:DWORD, cb:DWORD

hProcess the handle to the process. ppsmemCounters a pointer to the struct that the function fills in. cb the size of the the struct.
 
PROCESS_MEMORY_COUNTERS STRUCT
	cb				DWORD ?				;Size of the struct
	PageFaultCount			DWORD ?		;The # of Page Faults
	PeakWorkingSetSize		DWORD ?		;The Peak size of the Working Set
	WorkingSetSize			DWORD ?		;Current size of the Working Set
	QuotaPeakPagedPoolUsage		DWORD ?	;Peak Paged Pool Usage
	QuotaPagedPoolUsage		DWORD ?		;Current Paged Pool Usage
	QuotaPeakNonPagedPoolUsage	DWORD ?	;Peak Non Paged Pool Usage
	QuotaNonPagedPoolUsage		DWORD ?	;Current Non Paged Pool Usage
	PagefileUsage			DWORD ?		;Pagefile Usage
	PeakPagefileUsage		DWORD ?		;Peak Pagefile Usage
PROCESS_MEMORY_COUNTERS ENDS

For your information, here is Microsoft's definition of the Working Set, from the Win32 SDK Documentation:
"The working set of a process is the set of memory pages currently visible to the process in physical RAM memory. These pages are resident and available for an application to use without triggering a page fault. The size of the working set of a process is specified in bytes. The minimum and maximum working set sizes affect the virtual memory paging behavior of a process."

Ok, class. That concludes today's lecture on psapi.dll. Hope you learned something.

Downloads:
Zip file with psapi and vdmdbg .dll, .lib and .inc files -> here
Nearly Uncommented, ugly, lame example code -> here (to compile you will need the dlls.zip file)

Greets:
+YoSHi - keep those lamer logs long enough and we can publish a book. ;)
CrackZ - even if you don't like our yo momma! jokes...
Coreknee - nice site...
Tin0r - <Tin> im just a little slow, thats all
JosephCo - <josephCo-> something isn't correct about that sentence :)
ok, if i keep going like this, i'm gonna miss too many people, so here comes the quick list: The Phrozen Coders, the #c4n crew, the #win32asm crew, Nitallica, DaVinci, Klinkers, db, esotarius, tD, ok, that's enough...