Es difícil poner un punto de partida para adentrarnos en este ámbito de la ciberseguridad. Un punto que permita comprender aspectos complejos y no cotidianos y a la vez sea asequible su entendimiento, que se pueda decir que se parte de un nivel 0, básico, pero que no cuente demasiado cosas básicas que todo informático debería saber.
Así, se ha decidido empezar por el PEB, ¿Qué es? ¿Por qué es importante en desarrollo de malware?
Un poco de teoría:
Es interesante comprender que hay detrás de los archivos ejecutables de Windows, ya sea un archivo con extensión EXE común a todos nosotros o extensiones (quizás más desconocidas) como DLL, o SYS.
Para empezar, hay que saber que todos son el mismo tipo de archivo, lo que Windows llama Portable Executable Format. Entender la estructura de estos archivos, es objeto de siguientes artículos. Sin embargo, como mínimo si hay que saber que, cuando se ejecuta uno de estos archivos, Windows crea un proceso, con al menos un hilo, que serán los encargados de ejecutar el código del archivo.
Este proceso creado, se podría decir que es el encargado de dar todas las herramientas necesarias al ejecutable para que este se lance correctamente.
Un ejemplo: cuando se escribe un código en C que imprime por pantalla un “Hola mundo” mediante el típico printf, pasan cosas por debajo que los usuarios comunes, incluso desarrolladores, podemos llegar a desconocer.
Printf es una función definida y declarada en una librería llamada ucrtbase.dll. Sin embargo, esta es una función a muy alto nivel y sabemos que Windows tiene un kernel que no conoce estas funciones. ¿Cómo se logra entonces que el kernel obtenga una información que pueda digerir? Mediante las APIs nativas de Windows o WinAPIs.
En resumidas cuentas, cuando creamos un programa con un printf y lo ejecutamos, sabemos que se crearán una serie de llamadas desde muy alto nivel como printf hasta de muy bajo nivel como podría ser ZwWriteFile. Entender en profundidad las llamadas a funciones de WinAPIs, será objeto de otro artículo.
De nuevo la pregunta vuelve a ser ¿Cómo un proceso provee de las herramientas necesarias al archivo ejecutable para lanzarse correctamente? La respuesta es sencilla, teniendo un control sobre toda la información que un ejecutable necesita para lanzarse. Es decir, como hemos visto, nuestro programa necesita como mínimo a la DLL llamada ucrtbase.dll. ¿Cómo obtiene el ejecutable (y carga en su memoria) la dirección de la DLL para usar sus funciones como printf? Mediante el PEB.
El Process Enviroment Block, es una estructura de datos de Windows que contiene información para el arranque de procesos, y por ende ejecutables, como su propio PID, si el proceso está siendo debuggeado y, sobre todo, punteros a otras estructuras como LDR_DATA que terminarán conteniendo la información sobre las DLL necesarias para el ejecutable como su dirección.
Esta estructura está documentada parcialmente por Windows. Tan solo nos dan información clara sobre 7 de sus variables de las 19 que tiene.
Sabemos que todas las variables de tipo byte ocuparan un byte de espacio en la memoria y las variables PVOID serán punteros a algo y por tanto ocuparan 8 bytes de memoria. Es necesario saber cómo mínimo su tamaño para poder calcular bien las direcciones de memoria y los desplazamientos cuando estemos depurando el código o intentando acceder a ellas en nuestros códigos.
Menos mal que tenemos a la comunidad que investiga en estos temas y nos ofrecen datos no oficiales, pero si más precisos que la propia documentación de Microsoft. Un ejemplo es la página https://www.vergiliusproject.com/ donde podremos consultar la gran mayoría de estructuras de Windows. Recomendamos visitar la estructura PEB de esta web https://www.vergiliusproject.com/kernels/x64/windows-11/24h2/_PEB. En algo se parecen y sirve para hacerse una idea real de sus variables.
Si atendemos a la documentación oficial de Windows, lo que más interesa (por ahora) del PEB es la variable definida como Ldr. Esta variable será nuevamente una estructura de Windows del tipo _PEB_LDR_DATA y como el PEB, está documentada parcialmente por Microsoft.
Según la documentación oficial esta es su forma.
Nuestro objetivo es entender el PEB, como es la estructura de control de los procesos y como permite a los ejecutables (PE) que usen todas las DLL que necesiten, entre otras cosas.
PEB_LDR_DATA tiene una variable de tipo LIST_ENTRY llamada InMemoryOrderModuleList formada por dos punteros. Estos punteros indicaran la dirección de memoria de todas las DLL que necesite el ejecutable, tanto la DLL siguiente como la DLL anterior, creando una lista doblemente enlazada de DLL a cargar.
Estos punteros, según la documentación de Microsoft, se llaman Flink y Blink.
En este punto es interesante reflexionar sobre como esta lista doblemente enlazada da toda la información a un ejecutable sobre qué DLL cargar. Si pensamos un poco, como mínimo será necesario saber el nombre de la DLL y la ruta absoluta de donde está almacenada en disco para poder cargarla en memoria virtual.
Tenemos una lista doblemente enlazada, formada por struct _LIST_ENTRY donde flink apunta a la dirección del siguiente _LIST_ENTRY del que se obtendrá el siguiente flink y así sucesivamente. ¿Dónde está la información de cada DLL necesaria?
Pues esto es muy sencillo. Cada _LIST_ENTRY pertenece a una estructura mayor llamada _LDR_DATA_TABLE_ENTRY. Nuevamente es una estructura de Windows documentada parcialmente por Microsoft.
Como se puede apreciar, en la segunda línea contiene la estructura LIST_ENTRY (ojo que no es un puntero a la estructura si no que los 16 bytes de los dos punteros están ahí metidos). También se puede observar las variables DllBase, EntryPoint o FullDllName que contiene, ya sí, toda la información necesaria para ir cargando todas las DLL que el ejecutable necesite.
En resumen, el PEB es una estructura de Windows que contiene toda la información necesaria del proceso. Gracias a ella, un ejecutable puede cargar todas las DLL necesarias, para que las funciones del programa y las necesarias para el kernel, se carguen. Como se ha aprendido, todo esto mediante una serie de estructuras que mantienen en orden toda la información.
Manos a la obra:
Para analizar y observar el PEB y toda su estructura y sus estructuras se va a hacer uso de WinDBG, el depurador oficial de Windows. Además, se va a crear un pequeño programa en C con ayuda de VisualStudio que corrobore, todos los datos y sirva para reforzar los conocimientos y comprender, que desde un ejecutable se puede acceder a ellos.
En primer lugar, empezaremos usando WinDBG. Este depurador, nos va a permitir tanto lanzar un ejecutable desde disco y analizar su proceso, como attachearnos o adherirnos a un proceso de un ejecutable que este corriendo.
En este primer caso, vamos a lanzar nuestro programa con el printf de “Hola mundo” de cero.
Una vez lanzado, vamos a usar el primer comando que será !peb (WinDBG es case sensitive). Con ello, el depurador nos mostrará toda la información sobre esta estructura y subestructuras.
Gracias al comando !peb, ya se pueden observar todas estas estructuras y direcciones de memoria. En primer lugar, la primera flecha apunta a la dirección de memoria del PEB. No se ha dicho aún, pero obviamente esta dirección es una dirección en la memoria virtual y el PEB es único para cada proceso.
Un proceso puede tener números hilos o threads. Siendo esto posible y sabiendo que cada hilo tendrá su propia estructura TEB (Thread Enviroment Block) ¿Cada TEB de los hilos de un mismo proceso tendrá su propio PEB?
La siguiente flecha nos muestra la dirección de la variable Ldr, que tendrá el puntero a la primera estructura LIST ENTRY.
Por último, se puede observar cómo desde el PEB se ha obtenido todas las DLL cargadas, entre ellas ucrtbased.dll, encargada del printf.
WinDBG nos permite, además, hacer un volcado de memoria y ver el contenido de estas, así que vamos a ir viendo la memoria y como almacena estas estructuras.
En primer lugar, se va a comenzar accediendo a la estructura PEB (0x00000042393fc000)
Tras un desplazamiento de 0x18, se puede llegar a la variable Ldr, que es un puntero a la estructura. Vamos a seguirlo tanto a esta dirección como las siguientes hasta llegar al primer módulo cargado.
Este es el resultado. La única pista que daré será que con el comando du hemos hecho el volcado de la memoria de la variable FullDllName del primer módulo cargado. El resto de los recuadros, separados por colores, debéis tratar de identificarlos 😉.
Próximos pasos:
¿Por qué es necesaria toda esta estructura y saber manejarse con el depurador como desarrolladores de malware?
Las medidas de seguridad como EDRs o el propio Windows Defender, tratan cada día de evolucionar, de detectar cosas a niveles más bajos mismamente como cadenas repetidas de opcodes. Esto hace que debamos entender lo máximo posible del funcionamiento a bajo de nivel de lo que desarrollamos.
En particular, comprender la existencia del PEB, que datos tiene y como puedo acceder a ellos nos abre el camino a implementar medidas anti hooking, implementar medidas anti debugging o para realizar técnicas más avanzadas como Reflective Loader Injection donde será necesario cargar manualmente todas las dependencias del ejecutable.
Se ha creado un exe que obtiene alguno de los datos vistos anteriormente.
Se deja a gusto del lector, coger el archivo .c y modificarlo para intentar obtener información sobre el resto de las variables de la estructura PEB.