sábado, 22 de noviembre de 2008

Procesos e hilos de ejecución en Windows (II)

Voy a retomar el argumento de la primera entrada para centrarme, en este caso, en el objeto _EPROCESS (bloque ejecutivo de proceso) manejado por el kernel del sistema. Tal y como mencioné, se trata de la forma que tiene Windows de agrupar toda la información y recursos necesarios asociados con un proceso.

Analizaremos, utilizando la herramienta WinDbg la cual se incluye con las Microsoft Debugging Tools, los datos que conforman la estructura _EPROCESS y mencionaremos el significado de algunos de los más importantes. Todos los valores para los offsets incluidos aquí son particulares de un sistema Windows XP SP2, y esto es relevante dado que muchos de ellos variarán de una versión de Windows a otra, e incluso de un Service Pack a otro.

Formato de la estructura _EPROCESS

Abriremos en primer lugar la versión gráfica del debugger, windbg.exe, asegurándonos de haber definido correctamente la variable "SYMBOLS path" tal y como se menciona en la primera parte de esta serie, y lanzaremos la sesión de debugging del kernel de la máquina local.

Una vez obtenido el prompt del debugger ejecutaremos el siguiente comando:

lkd> dt _EPROCESS

No voy a incluir aquí el resultado obtenido para no alargar innecesariamente el post. Cada una de las entradas obtenidas tiene el siguiente formato:
+0xNN NombreCampo: TipoDato

El primer elemento indica el offset o desplazamiento a partir de la dirección en la que se localiza el proceso en la memoria, en formato hexadecimal. A continuación aparece el nombre asignado al campo y por último, el tipo de los datos para el contenido del campo.

Muchos de los tipos de datos que aparecen son a su vez estructuras de datos, por lo que podremos obtener una descripción más exhaustiva de los mismos, incluyendo tabulaciones y de modo recursivo utilizando el siguiente comando:
lkd> dt -a -b -v _EPROCESS

En principio nos centraremos en el resultado obtenido mediante el primero de los comandos, el cual nos ofrece una salida mucho más simplificada.

Campos de la estructura _EPROCESS

El primero de los elementos es el PCB o bloque de control de proceso, el cual es manejado internamente como una estructura _KPROCESS:
+0x000 Pcb              : _KPROCESS

Contiene la información básica que necesita el kernel de Windows para programar la ejecución de los threads que contiene el bloque ejecutivo de proceso. Si queremos analizar su formato interno:
lkd> dt _KPROCESS

y tal y como mencionamos anteriormente, si deseamos una salida mas prolija:
lkd> dt -a -b -v _KPROCESS

Mas adelante encontraremos dos valores de marca de tiempo para la fecha de creación y terminación del proceso. Si el proceso todavía se encuentra en ejecución el valor para la marca de tiempo de terminación sera undefined.
+0x070 CreateTime       : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER

Existen dos valores más de tiempo relacionados con un proceso, y los podremos encontrar dentro del PCB, dadas sus características. Se trata de:
+0x038 KernelTime       : Uint4B
+0x03c UserTime : Uint4B

y se corresponden con la suma total de la cantidad de tiempo durante la cual se han ejecutado los threads que componen el proceso en modo kernel y modo usuario, respectivamente. Podemos obtener los valores para todos los campos anteriores mediante la función GetProcessTimes del API de Windows.

Seguidamente hallaremos un puntero a una cadena de texto con el valor para el número de identificador unívoco del proceso:
+0x084 UniqueProcessId  : Ptr32 Void

Lista con los procesos en ejecución

Analizaremos ahora el campo ActiveProcessLinks. La entrada correspondiente es una estructura _LIST_ENTRY y su formato ampliado, extraído de la salida del comando dt con opciones, sería como sigue:
+0x088 ActiveProcessLinks : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to
+0x004 Blink : Ptr32 to

Se trata de una lista doblemente enlazada donde el primero de sus elementos, Flink:
  • Si es un valor miembro de un nodo de la lista apuntará a la siguiente entrada o a la cabeza si es el último nodo.
  • Si es un valor miembro de la cabeza de la lista apuntará a la siguiente entrada o a sí misma si la lista está vacía.
y el segundo de sus elementos, Blink:
  • Si es un valor miembro de un nodo de la lista apuntará a la entrada anterior o a la cabeza si no existe un nodo anterior.
  • Si es un valor miembro de la cabeza de la lista apuntará a la última entrada o a sí misma si la lista está vacía.
Vamos a hacer un experimento utilizando únicamente comandos del debugger:
lkd> !process 0 0

El comando anterior muestra un listado con información resumida para todos los procesos en ejecución en el sistema, es decir, para cada uno de los nodos que componen ActiveProcessLinks.

Supongamos que como resultado del comando obtenemos la siguiente información para el proceso System, el cual tiene un PID igual a 0, y algunos de los que le siguen:
PROCESS 821c8830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
DirBase: 00a9a000 ObjectTable: e1000cc0 HandleCount: 290.
Image: System
 
PROCESS 81fdd718 SessionId: none Cid: 01cc Peb: 7ffd9000 ParentCid: 0004
DirBase: 087c0020 ObjectTable: e1008128 HandleCount: 21.
Image: smss.exe
 
PROCESS 81fcd1c8 SessionId: 0 Cid: 0294 Peb: 7ffde000 ParentCid: 01cc
DirBase: 087c0040 ObjectTable: e13de838 HandleCount: 410.
Image: csrss.exe
 
...

*Los puntos suspensivos indican que se han omitido líneas.

Ejecutaremos ahora otro comando del debugger, en concreto dl, el cual nos mostrará todos los elementos que componen una lista enlazada. Para ello le pasaremos la dirección del campo ActiveProcessLinks, por lo que deberemos sumar el offset del campo (0x088) a la dirección del proceso (821c8830):
lkd> dl 821C88B8
821c88b8 81fdd7a0 80559258 00000000 00000000
81fdd7a0 81fcd250 821c88b8 00000280 0000183c
...

Analizando el resultado obtenido advertiremos que el primer bloque corresponde a la dirección de memoria donde se encuentra el campo ActiveProcessLinks del proceso, por lo que si queremos obtener la dirección del proceso tendríamos que restarle 0x088. De igual forma el segundo y tercer campo se corresponden con los valores Flink (siguiente elemento) y Blink (elemento anterior) de la estructura. Una vez realizadas las restas oportunas quedarían como sigue:
Dir Proc  NextProc LastProc
----------------------------------------------
821C8830 81FDD718 805591D0 00000000 00000000
81FDD718 81FCD1C8 821C8830 00000280 0000183c
...

No conozco el significado de los dos últimos bloques, así que si alguién sí lo sabe que se anime y aprendemos todos.

Algunos campos mas de _EPROCESS
+0x0ac PeakVirtualSize  : Uint4B
+0x0b0 VirtualSize : Uint4B

Ambos valores están relacionados con el espacio virtual de direcciones de memoria asignados a un proceso, siendo el primero el tamaño máximo para el mismo y el segundo el tamaño actual. En realidad todos los procesos del sistema tienen asignado un espacio virtual de memoria, el cual es mayor que la memoria física disponible. Dado que se trata de direcciones virtuales los valores deberán "traducirse" a una dirección de memoria válida antes de ser utilizados. Ahondaremos más en el tema a lo largo de una futura entrada.

Otro campo interesante:
+0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE

Se trata de un puntero a una estructura HANDLE_TABLE, la cual mantiene una lista con todos los manejadores abiertos por el proceso. Podemos ver las entradas que existen en la tabla para un determinado proceso mediante el siguiente comando:
lkd> !handle 0 direccionproceso

Seguimos ahora con el campo token:
+0x0c8 Token            : _EX_FAST_REF

Cuando un usuario/proceso inicia un nuevo proceso se genera un bloque ejecutivo _EPROCESS para agrupar toda la información y elementos necesarios para su ejecución. Este bloque adquiere un token, el cual define los privilegios de acceso a otros objetos o funcionalidades ofrecidas por el sistema subyacente, y por lo general, a menos que se especifique lo contrario, heredará el token de acceso del contexto de la cuenta de seguridad o proceso padre que lo inició. Podemos ver los campos del token asignado a un proceso concreto mediante el siguiente comando:
lkd> !token direcciontoken

Para obtener la dirección del token de, por ejemplo, el proceso System visto anteriormente, ejecutaremos un comando similar al siguiente (y recalco lo de similar dado que no es el único método que existe):
lkd> !process 821c8830 1
PROCESS 821c8830 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00a9a000 ObjectTable: e1000cc0 HandleCount: 304.
Image: System
VadRoot 821c4200 Vads 4 Clone 0 Private 3. Modified 7011. Locked 0.
DeviceMap e1004440
Token e10017e8
ElapsedTime 01:45:38.955
UserTime 00:00:00.000
KernelTime 00:00:13.140
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (59, 0, 345) (236KB, 0KB, 1380KB)
PeakWorkingSetSize 511
VirtualSize 1 Mb
PeakVirtualSize 2 Mb
PageFaultCount 3209
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 7

de cuyo resultado inferimos que la dirección del token para el proceso System sera e10017e8.
+0x11c VadRoot          : Ptr32 Void

El Virtual Address Descriptor (VAD) tree es una estructura utilizada por Windows para almacenar punteros a las páginas de memoria asignadas a un proceso determinado. El valor del elemento VadRoot consiste en un puntero a una dirección de memoria donde se almacena la raíz del árbol VAD, de forma que el siguiente comando:
lkd> !vad 821c4200
VAD level start end commit
821c4200 ( 0) 10 33 0 Mapped READWRITE
820397d0 ( 2) 60 60 0 Mapped READWRITE
81f70698 ( 3) 70 16f 0 Mapped READWRITE
82194288 ( 1) 7c910 7c9c5 5 Mapped Exe EXECUTE_WRITECOPY
 
Total VADs: 4 average level: 2 maximum depth: 3

nos mostrará las entradas VAD del proceso System. Puede, y debe, profundizarse más en el tema leyendo el paper "The VAD tree: A process-eye view of physical memory".

El PEB (Process Environment Block) se encuentra en el espacio de direcciones del proceso en modo usuario y almacena información relativa al proceso que necesita ser accedida por otros elementos.
+0x1b0 Peb              : Ptr32 _PEB

Podemos obtener el contenido del peb mediante el comando:
lkd> !peb direccionpeb

donde sustituiremos direccionpeb por el valor obtenido para un proceso concreto. En este enlace encontraremos la definición y tipo de los elementos que componen la esctructura PEB de Windows.

Ahora analizaremos el significado de los siguientes valores:
+0x1b8 ReadOperationCount  : _LARGE_INTEGER
+0x1c0 WriteOperationCount : _LARGE_INTEGER
+0x1c8 OtherOperationCount : _LARGE_INTEGER
+0x1d0 ReadTransferCount : _LARGE_INTEGER
+0x1d8 WriteTransferCount : _LARGE_INTEGER
+0x1e0 OtherTransferCount : _LARGE_INTEGER

Todos ellos almacenan información de accounting para la entrada/salida referente al proceso.

Un job es una forma de agrupar varios procesos de forma que sea más facil manejarlos en su conjunto. El valor del siguiente campo:
+0x244 JobStatus        : Uint4B

define el estado del Job, el cual puede ser alguno de los siguientes:
  • JobStatus_NotSubmitted:
    No ha sido añadido a la cola de planificación

  • JobStatus_Queued:
    Ha sido añadido a la cola de planificación

  • JobStatus_Running:
    Se encuentra en ejecución

  • JobStatus_Finished:
    Ha terminado correctamente

  • JobStatus_Failed:
    Una o más de las tareas que componen el job ha fallado

  • JobStatus_Cancelled:
    Ha sido cancelado por la aplicación
Por último analizaremos el campo:
+0x248 BreakOnTermination : Pos 13, 1 Bit

Si un proceso tiene establecido el valor de este parámetro a "Critical" provocará un fallo general del sistema cuando éste sea finalizado.

Y esto ha sido todo, al menos de momento.