miércoles, 4 de agosto de 2010

Usando la estructura KPCR en nuestro beneficio

En la búsqueda de procesos o drivers maliciosos ocultos cargados en memoria resultan de vital interés determinadas variables que contienen información relevante respecto al estado del kernel en el sistema en que Windows se está ejecutando. En este artículo y probablemente otro venidero descubriremos como acceder al contenido de estas variables, las estructuras que las contienen y algunas cosas más.

El "truco", inicialmente publicado por Edgar Barbosa en el paper "Finding some non-exported kernel variables in Windows XP", y posteriormente completado por Alex Ionescu en su artículo "Getting Kernel Variables from KdVersionBlock, Part 2" para rootkit.com se basa en el contenido de la estructura KPCR.

Esta estructura, ubicada en la dirección de memoria apuntada por el registro FS para sistemas x86 y el registro GS en sistemas x64, es utilizada por el kernel como almacén de información específica para cada procesador disponible en el sistema. En Windows XP/2003 siempre podremos encontrarla en la dirección 0xffdff000 pero en Windows Vista, y como consecuencia de la funcionalidad ASLR, esta dirección no es fija. Por último indicar, tal como aparece reflejado en la quinta edición de Windows Internals, que en sistemas IA64 la estructura KPCR siempre se ubicará en la dirección 0xe0000000ffff0000.

Vamos a analizar el contenido de la KPCR haciendo un poco de local kernel debugging con Windbg pero para poder continuar, y dado que lo único que tengo a mano es un Windows Vista, primero tendremos que preparar el sistema para ello.

Kernel Debugging en Windows Vista

Si abrimos WinDbg, menú File, Kernel Debug... y en el diálogo que aparece seleccionamos la pestaña Local y pulsamos Aceptar sólo obtendremos un mensaje de error indicando que será necesario arrancar el sistema en modo debug:


Como suelo trastear habitualmente con el proceso de local kernel debugging agregaré una entrada en el cargador de arranque de Windows Vista para no tener que activar dicha funcionalidad y reiniciar el sistema en cada ocasión. Los pasos:

  1. Copiamos la entrada configurada por defecto y le asignamos, por ejemplo, Debugentry como descripción. El número que aparece entre llaves se conoce como GUID y representa un identificador único asignado por Windows para la nueva entrada generada en el cargador de arranque.
    C:\Users\javi>bcdedit /copy {current} /d DebugEntry
    La entrada se copió correctamente en {2663707c-6f45-11df-8ecc-005056c00008}.
  2. Modificamos el orden de aparición de las entradas del cargador de arranque para establecer Debugentry como segunda opción:
    C:\Users\javi>bcdedit /displayorder {current} {2663707c-6f45-11df-8ecc-005056c00008}
    La operación se ha completado correctamente.
  3. Por último activamos la funcionalidad de debug para la segunda de las opciones mostradas durante el arranque:
    C:\Users\javi>bcdedit /debug {2663707c-6f45-11df-8ecc-005056c00008} on
    La operación se ha completado correctamente.

Una vez reiniciado el sistema ya podemos abrir una sesión de local kernel debug. Es importante resaltar que este tipo de debug no es el más adecuado ya que para hacer las cosas bién deberíamos utilizar dos sistemas, uno el que analiza y otro el analizado, o incluso una máquina virtual como sistema a analizar, pero para aprender un poco como funciona Windows internamente este tipo de debugging es más que aceptable.

Analizando la KPCR (Kernel Processor Control Region)

La extensión del depurador en modo kernel !pcr nos mostrará, por defecto, información del primer procesador o procesador 0. Si en un sistema con varios núcleos, ya sean físicos o lógicos, indicamos expresamente el número de procesador obtendremos información específica del mismo. Un ejemplo para la salida del comando en mi sistema:
lkd> !pcr
KPCR for Processor 0 at 81d40800:
Major 1 Minor 1
NtTib.ExceptionList: 8a513814
NtTib.StackBase: 00000000
NtTib.StackLimit: 00000000
NtTib.SubSystemTib: 80151000
NtTib.Version: 002a76c5
NtTib.UserPointer: 00000001
NtTib.SelfTib: 7ffde000
 
SelfPcr: 81d40800
Prcb: 81d40920
Irql: 00000002
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 81bff400
GDT: 81bff000
TSS: 80151000
 
CurrentThread: 8469a218
NextThread: 00000000
IdleThread: 81d44640
 
DpcQueue:

El resultado muestra un resumen de los valores asignados a cada uno de los campos de la estructura KPCR. De hecho, ahora que ya conocemos la dirección en memoria para la estructura correspondiente al primer procesador del sistema podremos consultar su contenido y formato con el comando:
lkd> dt nt!_KPCR 81d40800
+0x000 NtTib : _NT_TIB
+0x000 Used_ExceptionList : 0x80e96a3c _EXCEPTION_REGISTRATION_RECORD
+0x004 Used_StackBase : (null)
+0x008 Spare2 : (null)
+0x00c TssCopy : 0x80151000 Void
+0x010 ContextSwitches : 0x2b37f0
+0x014 SetMemberCopy : 1
+0x018 Used_Self : 0x7ffde000 Void
+0x01c SelfPcr : 0x81d40800 _KPCR
+0x020 Prcb : 0x81d40920 _KPRCB
+0x024 Irql : 0x2 ''
+0x028 IRR : 0
+0x02c IrrActive : 0
+0x030 IDR : 0xffffffff
+0x034 KdVersionBlock : 0x81d3fc70 Void
+0x038 IDT : 0x81bff400 _KIDTENTRY
+0x03c GDT : 0x81bff000 _KGDTENTRY
+0x040 TSS : 0x80151000 _KTSS
+0x044 MajorVersion : 1
+0x046 MinorVersion : 1
+0x048 SetMember : 1
+0x04c StallScaleFactor : 0x7cb
+0x050 SpareUnused : 0 ''
+0x051 Number : 0 ''
+0x052 Spare0 : 0 ''
+0x053 SecondLevelCacheAssociativity : 0 ''
+0x054 VdmAlert : 0
+0x058 KernelReserved : [14] 0
+0x090 SecondLevelCacheSize : 0
+0x094 HalReserved : [16] 0
+0x0d4 InterruptMode : 0
+0x0d8 Spare1 : 0 ''
+0x0dc KernelReserved2 : [17] 0
+0x120 PrcbData : _KPRCB

Para el propósito de este artículo resultan de interés concretamente dos campos de la estructura anterior: el primero de ellos, SelfPcr, se encuentra en el offset 0x01c y, como podemos observar, no es más que un puntero a la estructura en sí misma. El otro campo interesante sería KdVersionBlock, que se ubica en el offset 0x034 y también es un puntero. Lo curioso de este último campo es que en Windows 2000 aparecía definido como Reserved2 y su valor siempre era 0x0, es decir, era un campo reservado. Pero ahora, ¿a dónde apunta este campo?

DBGKD_GET_VERSION64 o lo que conlleva tirar del hilo

Vamos a obtener un volcado de la memoria utilizando el puntero contenido en el campo KdVersionBlock de la estructura KPCR y mostrando, por defecto, 32 DWORDS (128 bytes):
lkd> dd 81d3fc70
81d3fc70 1772000f 00030006 030c014c 0000002e
81d3fc80 81c48000 ffffffff 81d5fc70 ffffffff
81d3fc90 81f63fec ffffffff 81f63fec 81f63fec
81d3fca0 00000000 00000000 4742444b 00000330
81d3fcb0 81c48000 00000000 81cf3ab8 00000000
81d3fcc0 00000000 00000000 00080128 00010018
81d3fcd0 81cf5000 00000000 00000000 00000000
81d3fce0 81d5fc70 00000000 81d55990 00000000

Los primeros 10 DWORDS se corresponden con la estructura _DBGKD_GET_VERSION64, así que dejemos que WinDbg interprete el contenido de la memoria por nosotros:
lkd> dt nt!_DBGKD_GET_VERSION64 81d3fc70
+0x000 MajorVersion : 0xf
+0x002 MinorVersion : 0x1772
+0x004 ProtocolVersion : 0x6 ''
+0x005 KdSecondaryVersion : 0 ''
+0x006 Flags : 3
+0x008 MachineType : 0x14c
+0x00a MaxPacketType : 0xc ''
+0x00b MaxStateChange : 0x3 ''
+0x00c MaxManipulate : 0x2e '.'
+0x00d Simulation : 0 ''
+0x00e Unused : [1] 0
+0x010 KernBase : 0xffffffff`81c48000
+0x018 PsLoadedModuleList : 0xffffffff`81d5fc70
+0x020 DebuggerDataList : 0xffffffff`81f63fec

También existe la estructura _DBGKD_GET_VERSION32, o sea, una versión de 32 bits, pero parece que ha sido deshechada en favor de la anterior. Podemos obtener el tipo y función para cada uno de los campos si analizamos el fichero de cabecera windbgexts.h que acompaña a las Debugging Tools de Microsoft y que localizaremos en:

C:\Program Files\Debugging Tools for Windows (x86)\sdk\inc

Una vez hecho el inciso volvamos al resultado anterior y centrémonos en uno de los campos: DebuggerDataList. Resulta que se trata de la cabecera de una lista doblemente enlazada cuya finalidad es contener punteros por si se definen nuevos "debug data blocks" para extensiones del depurador. Lo más importante es que esta lista siempre tendrá al menos un miembro, el cual será utilizado por el depurador para acceder a diferentes variables definidas por el kernel y que serán almacenadas en una estructura especial, _KDDEBUGGER_DATA64 (de nuevo la versión de 32 bits ha sido deshechada). Para empezar obtengamos la dirección donde encontraremos esta estructura:
lkd> dd 81f63fec
81f63fec 81d3fc98 81d3fc98 00000000 00000000
81f63ffc 00000000 00000000 00000000 00000000
81f6400c 00000000 00000000 00000000 00000000
81f6401c 00000000 00000000 00000000 00000000
81f6402c 00000000 00000000 00000000 00000000
81f6403c 00000000 00000000 00000000 00000000
81f6404c 00000000 00000000 00000000 00000000
81f6405c 00000000 00000000 00000000 00000000

Como en esta ocasión la estructura _KDDEBUGGER_DATA64 se encontrará, habitualmente, justo a continuación de la estructura _DBGKD_GET_VERSION64, pero dado que esto no es imprescindible es mejor asegurarnos con el procedimiento anterior; en este sentido también será muy revelador encontrar el valor hexadecimal 4742444b (KDBG en ASCII) en el offset 0x16. Una vez confirmado que la dirección de memoria es 81d3fc98 podemos seguir interpretando el resto del volcado, es decir:
81d3fc90                    81f63fec 81f63fec
81d3fca0 00000000 00000000 4742444b 00000330
81d3fcb0 81c48000 00000000 81cf3ab8 00000000
81d3fcc0 00000000 00000000 00080128 00010018
81d3fcd0 81cf5000 00000000 00000000 00000000
81d3fce0 81d5fc70 00000000 81d55990 00000000

Lamentablemente no podremos utilizar windbg directamente para que interprete los valores por nosotros:
lkd> dt nt!_KDDEBUGGER_DATA64 81d3fc98
Symbol nt!_KDDEBUGGER_DATA64 not found.

¿o tal vez si?

Programando extensiones para el depurador

A mi parecer, una de las funcionalidades más interesantes de las herramientas de depuración de Microsoft, aka las Debugging Tools, es la posibilidad de ampliar el número de comandos disponibles mediantes extensiones del depurador. Estas extensiones se implementan como librerías dll que una vez cargadas nos permitirán utilizar los comandos que exportan.

Dado que no soy programador y no quería eternizar la aparición de este mensaje me puse a buscar en internet y utilicé las siguientes fuentes como ayuda para mi objetivo: generar una extensión que me permita obtener semiautomáticamente toda la información volcada en este artículo de forma manual.

La primera de las fuentes proviene del siguiente thread, "Récup IDT, GDT , etc. sur systèmes MP". Allí podremos descargar y entender el funcionamiento de una extensión del depurador que nos permitirá volcar la estructura _KDDEBUGGER_DATA64, entre otras cosas.

La segunda de las fuentes, "Setup A Windbg Extension In VisualStudio 2008", contiene una plantilla para el compilador MS Visual Studio 2008 (una vez convertida servirá igualmente para la versión 2010 de este compilador) preparada para que únicamente tengamos que preocuparnos de implementar nuestros propios comandos, los cuales serán exportados por dll.

Reconocidas e incluidas las fuentes, para utilizar mi extensión bastará con descargarla, extraer la dll adecuada (está la versión compilada en XP y la versión compilada en Windows Vista) en el siguiente directorio:

C:\Program Files\Debugging Tools for Windows (x86)\winext

Ahora habrá que cargarla en el depurador y lanzar adecuadamente el comando exportado, pasándole como parámetro la direccion de la estructura KPCR, para obtener todo el contenido mostrado en este mensaje pero de forma "automágica":
lkd> .load AvidExt.dll
lkd> !mycmd 81d40800
 
Direccion de la estructura _KPCR: 81d40800
Puntero en _KPCR.KdVersionBlock : 81d3fc70
 
_DBGKD_GET_VERSION64 en 81d3fc70
+0x000 MajorVersion : 0xf
+0x002 MinorVersion : 0x1772
+0x004 ProtocolVersion : 0x6
+0x005 KdSecondaryVersion : 00
+0x006 Flags : 0x3
+0x008 MachineType : 0x14c
+0x00a MaxPacketsType : 0xc
+0x00b MaxStateChange : 0x3
+0x00c MaxManipulate : 0x2e
+0x00d Simulation : 00
+0x00e Unused : 00
+0x010 KernBase : 0xffffffff81c48000
+0x018 PsLoadedModuleList : 0xffffffff81d5fc70
+0x020 DebuggerDataList : 0xffffffff81f63fec
 
_KDDEBUGGER_DATA64 en 81d3fc98
+0x000 DBGKD_DEBUG_DATA_HEADER64 : _DBGKD_DEBUG_DATA_HEADER64
+0x000 LIST_ENTRY64 : _LIST_ENTRY64
+0x000 Flink : 0x81f63fec
+0x008 Blink : 0x81f63fec
+0x016 OwnerTag : 0x4742444b
+0x020 Size : 0x000330
+0x024 KernBase : 0xffffffff81c48000
+0x032 BreakpointWithStatus: 0xffffffff81cf3ab8
+0x040 SavedContext: 0
+0x048 ThCallbackStack: 0x128
+0x050 NextCallback: 0x8
+0x052 FramePointer: 0x18
+0x054 PaeEnabled : 0x1
+0x056 KiCallUserMode: 0xffffffff81cf5000
+0x064 KeUserCallbackDispatcher : 0
+0x072 PsLoadedModuleList : 0xffffffff81d5fc70
+0x080 PsActiveProcessHead : 0xffffffff81d55990
+0x088 PspCidTable : 0xffffffff81d559b4
Continua...

No aparecen todos los campos de la estructura _KDDEBUGGER_DATA64 (son muchos) pero servirá para hacernos una idea y de paso aprender algo más sobre las herramientas de depuración de Windows. No obstante, y dado que he incluido el código fuente, siempre puede modificarse para mostrarla en su totalidad.

Utilizando el campo PsActiveProcessHead de la estructura anterior podemos listar los procesos en ejecución. Importante resaltar que dado que estamos utilizando la lista doblemente enlazada el resultado puede haber sido alterado si se ha utilizado DKOM para ocultar algún proceso malicioso:
lkd> dt _EPROCESS -l ActiveProcessLinks.Flink -y ImageFileName 81d5599-0xa0

o también la lista de drivers de dispositivos cargados en memoria mediante el campo PsLoadedModuleList:
lkd> dt _LDR_DATA_TABLE_ENTRY -l InLoadOrderLinks.Flink -y FullDllName 81d5fc70-0x0

Por supuesto el resultado obtenido mediante el comando anterior tambien es susceptible al uso de DKOM.

Referencias

Microsoft Windows Internals, Fifth Edition
David Solomon, Mark Russinovich y Alex Ionescu

Finding some non-exported kernel variables in Windows XP

Getting Kernel Variables from KdVersionBlock, Part 2

Finding Kernel Global Variables in Windows

Récup IDT, GDT , etc. sur systèmes MP

Kernel Debugging in Windows Vista

Advanced Windows Kernel Debugging with VMWare and IDA's GDB debugger

Debugger Engine and Extension APIs
C:\Program Files\Debugging Tools for Windows (x86)\debugger.chm

Setup A Windbg Extension In VisualStudio 2008

Debug Tutorial Part 4: Writing WINDBG Extensions

Inside Out WinDbg Extensions

Easy list traversing (dt vs. !list)

2 comentarios:

vlan7 dijo...

Es increible la cantidad de referencias que siempre incluyes neo. Dan mucho de si tus entradas.

¿Para cuando estara a punto el dominio para Neo System Forensics? ;)

Un saludo compañero de la red.

neofito dijo...

Gracias!! Respecto al dominio ... estar esta, ahora solo falta llenarlo ;)

Saludo