Site hosted by Angelfire.com: Build your free website today!

PSAPI y Tú - Los primeros años (Procesos y Módulos)

Por: The RudeBoy
Email: rudeboy@gmx.de
Traducción: nuMIT_o r

     Muchos de ustedes probablementa han leído el artículo de Iczelion's aquí en cornsoup sobre el uso de toolhelp32 para obtener una lista de procesos y de módulos de Win9x. Bien, no sé si recuerdas que hacia el final del ensayo él menciona que para hacer esto en winnt debes usar psapi.dll. Técnicamente, esto no es tan cierto, ya que psapi.dll obtiene información llamando a funciones ubicadas en winnt.dll. Sin embargo, estas funciones pueden cambiar, pero las funciones de psapi.dll se mantendrán iguales incluso en las siguientes versiones de Windows NT (incluyendo Windows 2000).

     No voy a profundizar en todas las funciones en psapi.dl. Simplemente me concentraré en las funciones que tienen que ver directamente con Procesos y Módulos.

Estas funciones son:

EnumProcesses
EnumProcessModules
GetModuleBaseName
GetModuleFileNameEx
GetModuleInformation
GetProcessMemoryInfo

     La primera función que veremos será EnumProcesses. Esta función llena un array de DWORD's con el Process ID de todos los procesos que corren actualmente en el sistema. He aquí la declaración de la función:

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

lpidProcess es un puntero al array de DWORD's donde los PID serán almacenados.
cb es el tamaño de lpidProcess. (# de elementos * sizeof DWORD).
cbNeeded es un puntero a una dword que contendrá el tamaño del array necesario para lpidProcess.

Desafortunademente, nunca sabrás el tamaño del array lpidProcess que necesitarás antes de llamara aEnumProcesses. el valor de cbNeeded te dirá el número de bytes necesraios para que el array alcance el cb. La manera de saber que tienes todos los PID's en la lista es comparar cb con cbNeeded. Si cbNeeded es menor que cb, ya tienes todos los PID's y puedes continuar después de tu bucle. A continuación presento el código (MASM) para llenar el array de PID's:
 
local dSize:DWORD       ; lo necesitará cb 
local dwSize2:DWORD     ; cb 
local lpdwPID:DWORD     ; Array de PID's 
local dwPIDCnt:DWORD    ; Número de PID's en el array 

       mov eax, 256     ; # de PIDS en el primer intento 
       shl eax, 2       ; multiplicado por 4 (sizeof(DWORD)) 
       mov dwSize2, eax ; almacenar este valor 
       mov lpdwPID, 0 
@loopy: 
       cmp lpdwPID, 0   ; si el array ya apunta a una dirección, tenemos que 
       jz Size2OK       ; liberar y relocalizar memoria 
       invoke GetProcessHeap ; obtener el montículo del proceso actual 
       mov ebx, eax     ; masm no debería compilar sin esto :/ 
       invoke HeapFree, ebx, 0, addr lpdwPID ; Libera el espacio del motículo 
       mov edx, dwSize2 
       shl edx, 1       ; doblar el tamaño del montículo a ser localizado 
       mov dwSize2, edx 
Size2OK: 
       invoke GetProcessHeap ; obtener el montículo del proceso actual 
       invoke HeapAlloc, eax, 0, dwSize2 ; localizar todo el espacio necesario 
       test eax, eax    ; asegurar que la llamada a HeapAlloc fue exitosa 
       jnz AllocOK 
       jmp ErrorReturn 
AllocOK: 
       mov lpdwPID, eax ; Hacer que lpdwPID apunte a la memoria localizada 
       invoke EnumProcesses, lpdwPID, dwSize2, addr dwSize ; call EnumProcesses 
       test eax, eax    ; Probar el éxito de la llamada 
       jnz EnumProcsOK 
       invoke GetProcessHeap ; Si fue infructuosa, liberar memoria y salir 
       invoke HeapFree, eax, 0, lpdwPID 
       jmp ErrorReturn 
EnumProcsOK: 
       mov eax, dwSize 
       cmp eax, dwSize2 ; comparar el tamaño del array con el tamaño necesario 
       jz @loopy        ; si son los mismos, mantener el bucle 
       shr eax, 2       ; el # de PID es regresado = tamaño del array/4 
       mov dwPIDCnt, eax 
       ret 
ErrorReturn: 
       invoke GetLastError ; Reportar el código de error ;) 
       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, ahora tenemos todo el montón de PID's, ¿qué vamos a hacer con él? Bien, realmente un PID no hace mucho por uno, pero nos permite llamar a OpenProcess para obtener el handle del proceso, que si es muy útil. Cuando llamamos a OpenProcess las mejores banderas [flags] a usar son PROCESS_QUERY_INFORMATION y PROCESS_VM_READ. Si intentas con PROCESS_ALL_ACCESS, muchos de los procesos no abrirán, pero si intentas sólo con PROCESS_QUERY_INFORMATION, algunas de las funciones de psapi.dll no trabajarán apropiadamente. Incluso con estas banderas [flags] algunos procesos del sistema no abrirán y tendrás que tratar a estos procesos de forma separada. Una vez que tengas el handle del proceso, puedes llamar a EnumProcessModules, cuyo prototipo es :

 

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


hProcess es el handle al proceso.
lphModule es un puntero a un array de DWORDs, que será llenado con handles de módulos
cb
tamaño del array anterior.
lpcbNeeded puntero a un dato tamaño DWORD, será el número de bytes necesarios.

La misma regla aplicadas a EnumProcesses se aplican aquí, no sabes cuántos módulos habrán. Sin embargo, frecuenemente sólo tienes que obtener el primer módulo a partir de la lista. Hacer esto es muy simple, como lo muestra el siguiente código de ejemplo. Si quieres obtener todos los módulos, modifca el código para que en vez de llamar a EnumProcesses se llame a EnumProcessModules. Esto asegurará que localices la memoria necesaría para tu aplicación.

local hProcess:DWORD ; Handle del proceso 
local hModule:DWORD  		; handle DEL Módulo 
local dwBytes:DWORD  		; No usado realmente ;) 
       mov edi, lpdwPID 	; Puntero a nuestro array de PIDs 
 ; Ahora llamamos a openprocess con el primer PID en el array 
       invoke OpenProcess, PROCESS_QUERY_INFORMATION + PROCESS_VM_READ, \ 
                     FALSE, dword ptr [edi] 
       mov hProcess, eax 	; Mover el valor regresado a Process Handle 
       test eax, eax ; EStarseguro que la llamada fue exitosa 
       jz OpenProcessFailed 
; Llamar a EnumProcessModules (sólo obtendremos el handle del primer módulo) 
       invoke EnumProcessModules, hProcess, addr hModule, 4, addr dwBytes 
       test eax, eax        ; Estar seguro de que la llamada fue exitosa
       jz EnumFailed 

     ¡Maravilloso! Ahora tenemos un handle de Proceso y un handle de Módulo. Esto es muy bueno y estupendo siempre que corramos aplicaciones de 32 bits. Windows NT maneja aplicaciones de 16 bits corriéndolas a través de una Máqina DOS Virtual (VDM: Virtual DOS Machine). Así que, con el fin de hacer una lista de los procesos de aplicaciones de 16 bits, hay que encontrar la VDM (NTVDM.EXE), y usar sus PID para llamar a VDMEnumTaskWOWEx. Esta función está localizada en vdmdbg.dll y toma entre sus argumentos la dirección de un procedimiento tipo callback que es llamado para cada aplicación de 16bits que está corriendo en el sistema. Estos son los prototipos:


         VDMEnumTaskWOWEx PROTO dwProcessID:DWORD, lpCallbackProc:DWORD, lParam:DWORD

Enum16Callback PROTO dwThreadID:DWORD, hMod16:WORD, hTask16:WORD, \
             lpszModName:DWORD, lpszFileName:DWORD, lParam:DWORD


Ahora, para VDMEnumTaskWOWEx aquí está la información:
dwProcessID es el PID de NTVDM.EXE
lpCallbackProc es un punetro a un procedimeinto callback
lParam es una variable definida por el usuario pasada al procedimiento callback

Y para el procedimento callback:
dwThreadID el ID del hilo, *dummy* ;)
hMod16 el handle al módulo de 16bits.
hTask16 el handle a la tarea de 16bits.
lpszModName puntero al nombre del módulo
lpszFileName puntero al nombre del archivo (la diferencia entre estos dos es explicada en la siguiente sección)
lParam Revisa en la información por VDMEnumTaskWOWEx

Código de ejemplo:

 
       invoke lstrcmpi, addr szFileName, addr szNtVDM ;  Prueba si este es el 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         ; salva los registros que usamos (ocurrirán cosas malas si no hacemos esto) 
       inc dwCount      ; ésta es una variable de nuestra listview 
       mov ebx, dwCount ; todo lo demás es el código del listview
       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 ; Queremos desplegar el nombre del módulo 
       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 ; Si quieres continuar con la enumeración de módulos de 16bit,  
                                      ; debes regeresar false 
       pop ebx 
       ret 
Enum16Proc ENDP 

Back to our Process Handle and Module Handle, ¿qué podemos hacer con ellos? Bien, aquí es donde el resto de los procedimientos serán cubiertos es este artículo. GetModuleBaseName y GetModuleFileNameEx son muy similares. La diferencia es que GetModuleFileNameEx regresa todo el camino [path] completo al módulo y GetModuleBaseName sólo regersa el nombre del archivo. Aquí están los prototipos:

 

GetModuleBaseName PROTO hProcess:DWORD, hModule:DWORD, lpBaseName:DWORD, nSize:DWORD
GetModuleFileNameEx PROTO hProcess:DWORD, hModule:DWORD, lpFilename:DWORD, nSize:DWORD

Si, es correcto, son casi idénticas.
hProcess handle al proceso. *(Haven't you figured this out by now?)*
hModule handle al módulo. *(Bet you couldn't have guessed that.)*
lpBaseName/Filename puntero al buffer buffer que recibirá el nombre Base/Archivo.
nSize tamaño del buffer mencionado arriba. *(I get bored and use big words sometimes.)*

Next in our bag of psapi.dll function es GetModuleInformation. Esta es una simple función que llena una estructura de información sobre el módulo. Ninguna de estas informaciones es particularmente difícil de obtener sin esta función, pero como se encuentran allí, serán cubiertas. He aquí el prototipo:

 

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


hProcess es el handle al proceso.
hModule es el handle al módulo.
lpmodinfo
es el puntero a una estructura MODULEINFOt. (La cubriré en un minuto)
cb
es el tamaño de la estructura. (HINT: invoke blahblah, sizeof MODULEINFO) ;)

La estrcutura MODULEINFO:

  
MODULEINFO STRUCT 
lpBaseOfDll DWORD ? ;Valor imagebase del módulo (lo mismo que hModule) SizeOfImage DWORD ? ;Tamaño de la imágen (del encabezado PE en memoria) EntryPoint DWORD ? ;Punto de netrada del programa (del encabezado PE en memoria) MODULEINFO ENDS

GetProcessMemoryInfo es el último procedimientio que será tratado aquí. Como GetModuleInformation, llena una estructura con información sobre el proceso.* 'On With the Prototype!' you say*:

 

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


hProcess handle al proceso.
ppsmemCounters un puntero a la estructura llenada por la función.
cb tamaño de la estructura.

PROCESS_MEMORY_COUNTERS STRUCT 
       cb                             DWORD ? ; Tamaño de la estructura 
       PageFaultCount                 DWORD ? ; El # de Fallos de Página 
       PeakWorkingSetSize             DWORD ? ; Tamaño pico del conjunto activo [working set]
       WorkingSetSize                 DWORD ? ; Tamaño actual del conjunto activo [working set]
       QuotaPeakPagedPoolUsage        DWORD ? ; Uso Pico de Reserva Paginada
       QuotaPagedPoolUsage            DWORD ? ; Uso actual de Reserva Paginada
       QuotaPeakNonPagedPoolUsage     DWORD ? ; Uso Pico de Reserva No Paginada 
       QuotaNonPagedPoolUsage         DWORD ? ; Uso actual de Reserva No Paginada 
       PagefileUsage                  DWORD ? ; Uso del archivo de paginación 
       PeakPagefileUsage              DWORD ? ; Tamaño pico del archivo de páginación 
 PROCESS_MEMORY_COUNTERS ENDS 

Para tu información, aquí está la definición de Microsoft del conjunto que funcuiona, de la documentación de Win32 SDK:
"El conjunto de trabajo activo ['working set'] of a process es el conjunto de páginas de memoria visibles actualmente para el proceso en memoria RAM física. Estas páginas son residentes y están disponibles para que la use una aplicación sin producir un fallo de página. El tamaño del trabajo activo ['working set'] de un proceso está especifcado en bytes. El tamaño mínimo y el máximo de los tamaños en el trabajo activo ['working set'] afecta la conducta de la paginación de la memoria virtual de un proceso."

Ok, clase. Esto concluye la lectura de hoy sobre psapi.dll. Espero que hayas aprendido algo.

Descargas:
Archivo zip con los archivos psapi y vdmdbg .dll, .lib y sus correspondientes archivos.inc -> aquí
Un ejemplo de código muy poco comentado, feo, *lame* -> aquí (para compilar nscesitarás el acrhivo dlls.zip)

Saludos a:
+YoSHi - mantén esos lamer logs lo suficiente y podemos publicar un libro. ;)
CrackZ - *even if you don't like our yo momma! jokes...*
Coreknee - buen sitio...
Tin0r - <Tin> soy un poco lento, eso es todo
JosephCo - <josephCo-> algo no está correcto en esa oración :)
ok, estoy en deuda con muchas muchas personas, así que aquí pongo una lista rápida: The Phrozen Coders, the #c4n crew, the #win32asm crew, Nitallica, DaVinci, Klinkers, db, esotarius, tD, ok, es suficiente...