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.
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!
Muy buen artículo amigo.
ResponderEliminarUn saludo.
De verdad que da gusto leer este código. Haces fácil lo que es difícil.
ResponderEliminarA 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-
Muchas gracias por los ánimos!
ResponderEliminarjusto lo que buscaba, es difil pero muy bien explicado, muchas gracias
ResponderEliminar