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:
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:
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:
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:
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
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:
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:
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
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...