jueves, 3 de julio de 2008

Articulo de Programacion #4 (Parte 2)

De modo usuario a modo kernel, SYSENTER

Vimos en la entrada anterior como OpenProcess llama a ntdll.NtOpenProcess con los parámetros arreglados para llamar a esta funcion, pero aun no hemos visto la verdadera funcionalidad de NtOpenProcess y es lo que vamos a analizar ahora. Hay algo que podemos notar, en la librería ntdll.dll podemos ver dos versiones de esta funcion NtOpenProcess y ZwOpenProcess, en ntdll.dll las dos funciones exportadas apuntan a la misma, ZwOpenProcess es solo un alias de NtOpenProcess, lo cual ocurre con todas las Apis nativas, teniendo sus versiones Zw y Nt, sin embargo en el kernel, exactamente en la imagen ntoskrnl.exe, asi no ocurre. Podemos ver entonces la funcion NtOpenProcess en la librería ntdll.dll:

ntdll!ZwOpenProcess:
7c91dd7b b87a000000 mov eax,7Ah
7c91dd80 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c91dd85 ff12 call dword ptr [edx]
7c91dd87 c21000 ret 10h

Podemos analizar el código anterior, como primeras instrucciones, vemos que mueve el valor 7Ah en eax, este valor viene siendo su índice en la KeServiceDescriptorTable en el kernel. Esta tabla contiene un arreglo de punteros, esta variable es llamada KiServiceTable, la cual es usada por el kernel para saber que funcion llamar en su transición a modo kernel. Despues vemos la siguiente instrucción, el cual mueve a edx la dirección de memoria del puntero que contiene la funcion SystemCallStub, el cual es llamado KiFastSystemCall contenido también en ntdll, el cual es la funcion que usa SYSENTER para hacer la transición a modo kernel, la cual podemos ver acontinuacion:

ntdll!KiFastSystemCall:
7c91eb8b 8bd4 mov edx,esp
7c91eb8d 0f34 sysenter
7c91eb8f 90 nop
7c91eb90 90 nop
7c91eb91 90 nop
7c91eb92 90 nop
7c91eb93 90 nop
7c91eb94 c3 ret

Podemos ver en la primera instrucción que ahora almacena en edx el valor de la pila actual, con esto tenemos lo siguiente:

EAX=ServiceDescriptorTableIndex, EDX=UserModeStack

Es decir, en eax esta el índice de la funcion que corresponde en KiServiceTable y en edx esta el stack de modo usuario que será copiado a un stack del kernel. Finalmente, la siguiente instrucción sysenter realiza la transición a modo kernel. Sin embargo esta transición conlleva a mas cosas aun. Cuando el cpu realiza la transición a modo kernel, necesita de una funcion en este modo que despache el pedido, el cual en el kernel es llamado KiFastCallEntry, y el cpu guarda esta dirección en los registros llamados “Model Specific Registers” (MSR). Uno de ellos del cual hablamos que contiene la dirección de KiFastCallEntry es el registro SYSENTER_EIP_MSR, sin embargo no es el único registro, también existen otros dos elementales, el SYSENTER_CS_MSR el cual contiene el selector de segmento de código, es decir, el selector que nos dice que KiFastCallEntry reside en el segmento del kernel, y el SYSENTER_ESP_MSR que nos dice en que dirección esta el stack de modo kernel que usara la transición. Antes de analizar esta funcion es importante que expliquemos que es la KeServiceDescriptorTable. La KeServiceDescriptorTable es un arreglo de cuatro estructuras del tipo SERVICE_DESCRIPTOR_TABLE:

typedef struct _SERVICE_DESCRIPTOR_TABLE {
PVOID *ServiceTable;
PULONG CounterTable;
ULONG TableSize;
PUCHAR ArgumentTable;
} SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

Los cuales están declarados en el kernel de la siguiente forma:

SERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable[4];
SERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTableShadow[4];

Podemos ver que son dos tipos de estas tablas, las dos residen en ntoskrnl.exe, pero una de ellas no esta exportada, hablo de la KeServiceDescriptorTableShadow. Podemos ver que cada una de estas tablas tienen 4 campos, siendo la mas importante el arreglo de punteros llamado ServiceTable. ServiceTable tiene tantos elementos como TableSize tenga indicado el cual sirve de guía para verificar que KiFastCallEntry no se exceda en cuanto al índice del arreglo. ServiceTable es un arreglo de punteros que contienen las direcciones de memoria de cada uno de los Servicios NT del kernel, cuando analizamos la llamada a SYSENTER vimos que en eax almacenaba un numero, justamente este numero vendría siendo el índice de esta funcion en este arreglo. Sin embargo en el kernel hay declarados oficialmente dos tipos de ServiceTable, uno de ellos se llama KiServiceTable, este tiene todas las funciones de los servicios nt del kernel, por ejemplo NtWriteVirtualMemory, NtWriteFile, NtMapViewOfSection, etc…, hay otra cuyo nombre es W32pServiceTable y tiene las funciones del driver win32k.sys que vendría siendo el kernel de las funciones gui, por ejemplo, NtUserSendInput, NtUserCreateWindowEx, NtUserGetDc, etc… Sin embargo, W32pServiceTable no se encuentra dentro de ntoskrnl.exe sino en win32k.sys, esta tabla no esta accesible para threads que se ejecuten en el contexto del proceso de System, es decir, si se intenta acceder desde el DriverEntry de un driver, lo mas probable es que de una excepción de acceso de memoria invalido. KeServiceDescriptorTable solo usa su primera estructura, las otras tres están sin inicializar, en cambio KeServiceDescriptorTableShadow tiene dos inicializadas, las otras dos están sin inicializar. KeServiceDescriptorTable tiene en su única y primera estructura el campo de ServiceTable inicializado como la tabla KiServiceTable. El campo CounterTable no es utilizado, solo en versiones de debug es un contador que se incrementa cada vez que el servicio sea llamado. El campo TableSize contiene el numero de Servicios que KiServiceTable tiene, EAX no puede exceder ese numero tiene que ser siempre menor o simplemente KiFastCallEntry retornara sin ejecutar nada. Finalmente el campo ArgumentTable es como la KiServiceTable, pero es un arreglo de chars que contiene el numero de bytes que tienen de longitud todos los argumentos que son pasados a la funcion que se va a ejecutar de KiServiceTable. La KeServiceDescriptorTableShadow en su primera estructura es exactamente igual como la primera y única estructura de KeServiceDescriptorTable, pero su segunda estructura es diferente ya que contiene los servicios de win32k.sys, teniendo como ServiceTable al arreglo de punteros W32pServiceTable y claro esta, con sus correspondientes TableSize, CounterTable y ArgumentTable.

Precisamente estas tablas son la principal interface de las Apis de Windows con las Apis nativas del kernel, todos los procesos terminan usando las misma tablas, ya que la memoria del kernel es compartida por todos los procesos, asi pues, cualquier cambio que se haga a la misma afectara a todos, y eso es precisamente lo que hacen diversos softwares, desde antivirus, rootkits, antispywares, etc… Modifican tanto la KiServiceTable o la W32pServiceTable con sus propias rutinas que denominaremos hooks, interceptan los parámetros y ellos deciden si llamar después a la funcion original o simplemente bloquearla. Sin embargo estos métodos se verán en próximos artículos.

En este articulo se vio lo básico de la interfaz entre modo usuario y modo kernel. En el próximo articulo ya nos adentraremos en el kernel y analizaremos la funcion KiFastCallEntry viendo el proceso de llamar al Servicio nt a través del índice y el Stack pasado, hasta la próxima xD.

No hay comentarios: