· Asparux · malware · 4 min read
PEB/TEB
El PEB, o Bloque de Entorno de Proceso, es la estructura que representa un proceso en modo de usuario.
Vamos a realizar una explicación mas de estar por casa, imagina que estamos organizando una fiesta en tu casa. Tu casa sería el proceso y tú serías el proceso en sí mismo. Ahora, el PEB sería como una lista detallada de todo lo que necesitas y todo lo que sabes sobre cómo está organizada tu fiesta en ese momento.
Dentro de ese PEB de la fiesta, tendríamos información importante, como si estás actualmente ocupado haciendo algo en la fiesta (por ejemplo, si estás cocinando o limpiando), dónde está ubicado el centro de la fiesta (la dirección base de la casa), qué amigos han llegado a la fiesta (los módulos cargados), cuánta comida y bebida tienes (tamaño del montón del proceso), entre otros detalles también relevantes.
Digamos que necesitas encontrar tu lista de invitados para ver quién ha llegado a la fiesta. Esto sería similar a buscar en el PEB la lista de módulos cargados en el proceso. El PEB te proporciona acceso a toda esta información esencial para que puedas tomar decisiones sobre cómo continuar con tu fiesta.
Dentro de PEB, encontramos todo lo que las aplicaciones de usuario (y nosotros) necesitamos y debemos saber sobre un proceso específico.
Es en PEB donde encontramos información como:
- Si el proceso se está depurando actualmente (
bool BeingDebugged
). - Dirección base del proceso (
uintptr ImageBase
). - Los módulos cargados dentro del proceso (
PPEB_LDR_DATA LoaderData
). - Tamaño del montón del proceso (
uint32 HeapSegmentReserve
). - Entre muchos otros…
El PEB se define por la siguiente estructura:
type PEB struct {
reserved1 [2]byte
BeingDebugged byte
BitField byte
reserved3 uintptr
ImageBaseAddress uintptr
Ldr *PEB_LDR_DATA
ProcessParameters *RTL_USER_PROCESS_PARAMETERS
reserved4 [3]uintptr
AtlThunkSListPtr uintptr
reserved5 uintptr
reserved6 uint32
reserved7 uintptr
reserved8 uint32
AtlThunkSListPtr32 uint32
reserved9 [45]uintptr
reserved10 [96]byte
PostProcessInitRoutine uintptr
reserved11 [128]byte
reserved12 [1]uintptr
SessionId uint32
}
Como ya habrás notado, el elemento que más nos interesa es PPEB_LDR_DATA LoaderData
, allí encontramos los módulos cargados por el proceso.
Este campo es de tipo PPEB_LDR_DATA
, definido por la siguiente estructura:
type PEB_LDR_DATA struct {
reserved1 [8]byte
reserved2 [3]uintptr
InMemoryOrderModuleList LIST_ENTRY
InLoadOrderModuleList LIST_ENTRY
InInitializationOrderModuleList LIST_ENTRY
}
Tenemos 3 elementos de este tipo LIST_ENTRY
(te habrás dado cuenta que esta es una lista de módulos cargados). Lo que buscamos es el InInitializationOrderModuleList
.
¿Y cómo revisamos esta lista hasta llegar a nuestro módulo de destino?
Debido a que es de tipo LIST_ENTRY
, este campo tiene un Flink
y un Blink
.
type LIST_ENTRY struct {
Flink *LIST_ENTRY //Forward Link
Blink *LIST_ENTRY //Back Link
}
Accediendo a un Flink
(Forward Link), pasamos al siguiente elemento de la lista, mientras que con un Blink
(Backward Link), accedemos al elemento anterior de la lista.
De esta manera, podemos recorrer los elementos de la lista hasta encontrar nuestro objetivo ntdll.dll
.
Pero espera, ¿cómo accedemos al PEB a través de nuestro código?
Tanto la arquitectura x86 como la x64 tienen registros especiales que contienen el TEB de cada hilo.
El registro en cuestión es FS para x86 y GS x64.
type TEB struct {
NtTib NtTib
EnvironmentPointer uintptr
ClientId ClientId
ActiveRpcHandle uintptr
ThreadLocalStoragePointer uintptr
ProcessEnvironmentBlock uintptr
LastErrorValue uint32
}
Como habrás notado, tenemos un puntero tipo PPEB
llamado ProcessEnvironmentBlock
, es a través de él que accedemos a nuestro PEB.
Tenemos dos formas de acceder al PEB, podemos hacerlo mediante ensamblaje en línea, o con funciones intrínsecas del sistema operativo.
Vía Assembler:
O, la forma más sencilla, utilizamos funciones intrínsecas:
PPEB Peb = (PPEB)__readfsdword(0x30); //32bit process
PPEB Peb = (PPEB)__readgsqword(0x60); //64bit process
PLDR_MODULE pLoadModule;
pLoadModule = (PLDR_MODULE)((PBYTE)Peb->LoaderData->InMemoryOrderModuleList.Flink->Flink – 0x10); //Mismo proceso visto anteriormente
Y de esta manera, tenemos acceso al PEB y TEB actuales de nuestro proceso e hilo, respectivamente.
CONCLUSIONES
Como hemos visto, con el PEB/TEB destacamos la importancia de estos dos en la programación de windows. El PEB contiene información vital sobre un proceso, mientras que el TEB proporciona información sobre un subproceso específico. Ambas estructuras son esenciales para comprender y manipular el entorno de ejecución de un programa en el sistema operativo windows.
¿Que pasaría si un ciberdelincuente consigue manipular las estructuras PEB/TEB?
- Evadir la detección de AV o EDR.
- Inyección de código.
- Interceptar llamadas al sistema.
- Ataques RootKit.
En resumen, esta manipulación por parte de un ciberdelincuente puede proporcionar una serie de ventajas para el malware en términos de evasión de detecciones, inyección de código, intercepción de llamadas al sistema y facilitación de ataques de rootkit.