· 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: Via 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.

Share:
Back to Blog