domingo, 29 de mayo de 2011

Obtener la dirección base de kernel32.dll consultando el PEB

En la entrada de Usando GetProcAddress y LoadLibrary para localizar APIs vimos que una de las primeras cosas que hay que hacer es localizar la dirección de memoria donde se carga la dll kernel32.dll. Para ello se utilizaba un código que suponía que en la pila se encontraba una dirección de retorno a una función de dicha dll:

;Obtiene la dirección base de Kernel32
ObtenerKernel32 proc
 mov eax, dword ptr [esp+0Ch];Nos quedamos con la dirección de retorno hacia la funcion createprocess
 and eax, 0FFFFF000h   ;Paginas de 1000h
searchMZ:
 sub eax, 1000h
 cmp word ptr[eax], "ZM"
 jnz searchMZ
 ret

ObtenerKernel32 endp

Esta forma no parece muy correcta, puede que en alguna versión de Windows pasada o futura no funcione bien. Además se supone que se conoce en que posición de la pila estará el valor de retorno que comentaba, esto por ejemplo puede no saberse exactamente si el código se usa para explotar una vulnerabilidad mediante una shellcode que use ese método.

Por suerte existe otra forma de obtener la dirección base de kernel32.dll.

Citando a la wikipedia:

En computación, el Win32 Thread Information Block (TIB) es una estructura de datos en los sistemas Win32 específicamente en la arquitectura x86 que almacena información a cerca del thread que se esta ejecutando.

El TIB esta indocumentado oficialmente para Windows 9x.La serie Windows NT incluye una estructura NT TIB en winnt.h que lo documenta .Wine incluye declaraciones para extender (una parte especifica del subsistema) TIB

El TIB puede ser usado para obtener buena cantidad de información en el proceso sin llamar ninguna API de Win32.Por ejemplo la emulación de GetLastError() , GetVersion(). A través del puntero en el PEB se puede obtener acceso a la tabla de importación (IAT) , los argumentos pasados al proceso etc …

La dirección de esta estructura esta apuntada por el registro FS y en la misma página de wikipedia que he comentado antes pueden verse los campos que tiene. Uno de ellos es el Process Enviroment Block, PEB. Una estructura que contiene información sobre el proceso que ejecuta el hilo:

FS:[0x30] –> Linear address of Process Environment Block (PEB)

En MSDN podemos ver de que se compone la estructura PEB:

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;  //+0x0C
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
} PEB, *PPEB;

Mediante esta estructura podemos saber si un proceso está siendo depurado, los parámetros del proceso y lo que mas nos interesa los módulos que ha cargado el Loader de Windows. En estos módulos se encuentra el propio ejecutable y todas las DLLs que ha cargado Windows para poder ejecutarlo, entre ellas estará kernel32.dll. El campo que nos da esta información es el siguiente:

PPEB_LDR_DATA  Ldr;

Que se encuentra a un desplazamiento de 0x0C desde el principio de la estructura PEB.

La estructura PEB_LDR_DATA a su vez también está documentada en MSDN:

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList; // +0x14
} PEB_LDR_DATA, *PPEB_LDR_DATA;

En MSDN describen esa lista cómo:

InMemoryOrderModuleList The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks.

Con la siguiente estructura:

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

Y por último la tabla donde apuntan Flink y Blink:

typedef struct _LDR_DATA_TABLE_ENTRY {
  PVOID Reserved1[2];
->LIST_ENTRY InMemoryOrderLinks;
  PVOID Reserved2[2];
  PVOID DllBase; //+0x10 desde –>
  PVOID EntryPoint;
  PVOID Reserved3;
  UNICODE_STRING FullDllName; //+0x1C desde –>
  BYTE Reserved4[8];
  PVOID Reserved5[3];
  union {
    ULONG CheckSum;
    PVOID Reserved6;
  };
  ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

Hay que fijarse que los punteros Flink y Blink de la lista doblemente enlazada no apuntan al comienzo de la estructura LDR_DATA_TABLE_ENTRY sino al campo InMemoryOrderLinks, como he marcado con la flecha –>. Los desplazamientos los he puestos relativos a este punto.

De esta última estructura nos interesan los campos:

PVOID DllBase; 

Que contiene la dirección base donde se ha cargado el módulo y:

UNICODE_STRING FullDllName;

Que es la ruta y nombre del módulo. El tipo UNICODE_STRING está definido también en MSDN:

typedef struct _LSA_UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;

Perfecto! Ya tenemos todo lo necesario para lograr nuestro objetivo. La idea es primero acceder al TIB, desde este localizar la estructura PEB, después acceder al valor Ldr a la estructura PPEB_LDR_DATA, utilizar el valor InMemoryOrderModuleList para recorrer la lista mediante el puntero Flink(Follow link) y en cada tabla LDR_DATA_TABLE ver si corresponde al módulo kernel32.dll comparando con el campo FullDllName, si no es así ir a la siguiente tabla, si sí que corresponde con kernel32.dll quedarnos con la dirección base DllBase que es lo que buscamos.

A continuación dejo el código en ensamblador que realiza todas estas tareas. Para hacerlo lo mas genérico he comparado el nombre del módulo con kernel32.dll de forma que no sea sensible a minúsculas o mayúsculas lo que hace un poco mas largo el código:

.386
.model flat, stdcall
option casemap:none
assume fs:nothing

.data
 Kernel32Name db 'K',0,'E',0,'R',0,'N',0,'E',0,'L',0,'3',0,'2',0,'.',0,'D',0,'L',0,'L',0
 Kernel32NameLength dw 24
.code
codigo:
 call ObtenerKernel32PEB
 ret

;Obtiene la dirección de memoria donde se encuentra cargada kernel32.dll y la guarda en eax
ObtenerKernel32PEB proc
 mov eax, [fs:30h]    ;PEB
 mov eax, [eax+0Ch]    ;PEB.Ldr, Loader data
 add eax, 14h     ;PEB.Ldr.InMemoryOrderModuleList.Flink, Puntero al primer modulo
NextTable:
 mov eax, [eax]      ;Nos movemos a la siguiente tabla LDR_DATA_TABLE_ENTRY
 mov ecx, 0
 mov esi, offset [Kernel32Name] ;En esi guardamos la dirección del nombre kernel32.dll en unicode
 mov cx, word ptr [eax+1Ch]  ;LDR_DATA_TABLE_ENTRY.FullDllName.Length, longitud de la cadena unicode
 mov edi, [eax+20h]    ;LDR_DATA_TABLE_ENTRY.FullDllName.Buffer, puntero a la cadena unicode con el nombre del módulo
 ;No nos interesa la ruta entera solo los últimos caracteres, el nombre del fichero
 add di, cx      ;Nos situamos al final de la cadena
 sub di, Kernel32NameLength  ;Y restamos el tamaño de la cadena a comparar
 mov cx, Kernel32NameLength
ComparaCadenas:
 mov bl, byte ptr [edi]
 cmp bl, 'a'      ;Si bl >= 'a' convertir a mayusculas
 jb Mayuscula
 sub bl, 20h      ;Pasa de minusculas a mayusculas
Mayuscula:
 cmp bl, byte ptr[esi]
 jnz NextTable     ;Si no es kernel32.dll mirar en la siguiente tabla
 inc edi
 inc esi
 dec cx
 jnz ComparaCadenas
 ;Si llega aquí es que la cadena era kernel32.dll, cogemos el dato que nos interesa del modulo
 mov eax, [eax+10h]    ;eax = LDR_DATA_TABLE_ENTRY.DllBase
 ret
 
ObtenerKernel32PEB endp

end codigo

Es bastante lioso manejar tantas estructuras y entender el código en ensamblador, lo mejor para entenderlo es seguirlo poco a poco con un depurador, ver las estructuras en memoria y muuucha paciencia.

Y así obtendríamos la dirección de memoria donde está cargada la librería kernel32.dll, ahora ya podríamos seguir con el proceso que realizábamos en la otra entrada de buscar las funciones GetProcAddress y LoadLibrary.

Aquí dejo una entrada de LordRNA donde hace lo mismo de forma muy similar Gracias a @The_Swash también por su solución al mismo problema.

Un saludo!

3 comentarios:

  1. Muy buen artículo amigo.
    Un saludo.

    ResponderEliminar
  2. De verdad que da gusto leer este código. Haces fácil lo que es difícil.

    A esta entrada le doy un 10. Por la explicación inicial y por los comentarios del código. Chapó. De verdad que da gusto!

    Saludos-

    ResponderEliminar
  3. Muchas gracias por los ánimos!

    ResponderEliminar