Malware Development

Procesos e hilos

Procesos

Se define como proceso al contenedor utilizado por el sistema operativo que, mediante un conjunto de recursos, ejecuta la instancia o lógica de un programa. Un proceso en sí mismo es pasivo; no ejecuta código directamente, sino que sirve de marco de trabajo.

Para entender cómo funciona un proceso, se deben tener en cuenta los siguientes componentes esenciales:

  • Programa: El código ejecutable o lógica (un archivo .exe o .dll) que se mapea dentro del espacio de memoria virtual del proceso.
  • Espacio de memoria virtual (privado): Un conjunto de direcciones virtuales asignadas exclusivamente al proceso para almacenar su código, datos, la pila (stack) y el montón (heap).
  • Contexto de seguridad (Token de acceso): Un objeto que identifica la identidad del usuario, sus grupos y los privilegios del proceso. El sistema operativo lo utiliza para validar si el proceso tiene permiso para acceder a determinados recursos (archivos, claves de registro, etc.).
  • Hilos (Threads): La unidad mínima de ejecución. Son los que realmente ejecutan el código del programa dentro del espacio de memoria. Aunque nacen dentro del proceso, es el kernel del sistema operativo quien se encarga de administrarlos y asignarles tiempo de CPU.

Process Environment Block (PEB)

Los procesos en Windows presentan una structo estructura interna que almacena información sobre sí mismos conocida como PEB. A diferencia de otras estructuras de control que gestiona el kernel, el PEB reside en el espacio de memoria de usuario (User Mode) [[2. Arquitectura de Windows]]. Esto significa que el propio proceso puede leer y modificar muchos de sus campos directamente sin interactuar con el sistema operativo.

Sirve como una tabla de referencia que contiene información relevante acerca del proceso, como el ID del proceso, parámetros, librerías DLL utilizadas, ubicación del programa ejecutable y más.

A continuación se lista de forma parcial la estructura PEB (winternl.h)

typedef struct _PEB {
BYTE                          Reserved1[2];
BYTE                          BeingDebugged;
BYTE                          Reserved2[1];
PVOID                         Reserved3[2];
PPEB_LDR_DATA                 Ldr;
PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
PVOID                         Reserved4[3];
PVOID                         AtlThunkSListPtr;
PVOID                         Reserved5;
ULONG                         Reserved6;
PVOID                         Reserved7;
ULONG                         Reserved8;
ULONG                         AtlThunkSListPtr32;
PVOID                         Reserved9[45];
BYTE                          Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE                          Reserved11[128];
PVOID                         Reserved12[1];
ULONG                         SessionId;
} PEB, *PPEB;

En la structse pueden observar miembros y elementos relevantes a la hora de analizar un proceso:

  • BeingDebugged: Un solo byte que se establece en 1 si el proceso está siendo analizado por un depurador (como x64dbg o WinDbg).

  • Ldr: Un puntero hacia a la estructura PEB_LDR_DATA. Esta estructura almacena información sobre las DLLs y módulos cargados (direcciones en memoria de las librerías, tamaño, etc).

typedef struct _PEB_LDR_DATA {

ULONG                   _Length_;
BOOLEAN                 _Initialized_;
PVOID                   _SsHandle_;
LIST_ENTRY              _InLoadOrderModuleList_;
LIST_ENTRY              _InMemoryOrderModuleList_;
LIST_ENTRY              _InInitializationOrderModuleList_;

} PEB_LDR_DATA, *PPEB_LDR_DATA;
  • ProcessParameters: Puntero a una estructura (RTL_USER_PROCESS_PARAMETERS) que almacena información sobre los parámetros de ejecución del proceso, como los argumentos de la línea de comando, ruta del directorio actual, nombre del binario, etc.
typedef struct _RTL_USER_PROCESS_PARAMETERS {


ULONG                   _MaximumLength_;
ULONG                   _Length_;
ULONG                   _Flags_;
ULONG                   _DebugFlags_;
PVOID                   _ConsoleHandle_;
ULONG                   _ConsoleFlags_;
HANDLE                  _StdInputHandle_;
HANDLE                  _StdOutputHandle_;
HANDLE                  _StdErrorHandle_;
UNICODE_STRING          _CurrentDirectoryPath_;
HANDLE                  _CurrentDirectoryHandle_;
UNICODE_STRING          _DllPath_;
UNICODE_STRING          _ImagePathName_;
UNICODE_STRING          _CommandLine_;
PVOID                   _Environment_;
ULONG                   _StartingPositionLeft_;
ULONG                   _StartingPositionTop_;
ULONG                   _Width_;
ULONG                   _Height_;
ULONG                   _CharWidth_;
ULONG                   _CharHeight_;
ULONG                   _ConsoleTextAttributes_;
ULONG                   _WindowFlags_;
ULONG                   _ShowWindowFlags_;
UNICODE_STRING          _WindowTitle_;
UNICODE_STRING          _DesktopName_;
UNICODE_STRING          _ShellInfo_;
UNICODE_STRING          _RuntimeData_;
RTL_DRIVE_LETTER_CURDIR _DLCurrentDirectory[0x20]_;



} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

Si fuéramos a buscar información sobre las structs mencionadas anteriormente en la documentación de Windows se observaría que la mayoría de miembros se encuentran en estado Reserved.

Esto significa que Microsoft decide no publicar información acerca de estos miembros con el objetivo de evitar exponer demasiada información del sistema operativo.

Sin embargo, existen proyectos como ntinternals.net , https://vergiliusproject.com o https://www.geoffchappell.com/studies/windows/ cuyo objetivo es obtener la función y nombre de cada miembro.

Thread Environment Block (TEB)

El Thread Environment Block (TEB) es otra estructura relevante identificada dentro de un proceso en el sistema operativo Windows. Sirve para almacenar el estado y la información específica de cada hilo.

Mientras que un proceso tiene un único PEB, tendrá tantos TEBs como hilos en ejecución posea. Al igual que el PEB, el TEB reside en el espacio de usuario, lo que permite al hilo acceder a sus propios datos de configuración de forma rápida y sin necesidad de realizar transiciones al kernel mediante llamadas al sistema (syscalls).

Dentro del desarrollo de malware y capacidades ofensivas, el TEB sirve como herramienta para localizar el PEB en memoria, gestionar las excepciones de los hilos y obtener identificadores relevantes.

A continuación, se muestran los campos más relevantes del TEB:

typedef struct _TEB {
NT_TIB                NtTib;
PVOID                 EnvironmentPointer;
CLIENT_ID             ClientId;
PVOID                 ActiveRpcHandle;
PVOID                 ThreadLocalStoragePointer;
PPEB                  ProcessEnvironmentBlock;
ULONG                 LastErrorValue;
// ... (omitidos miembros reservados o internos del sistema)
} TEB, *PTEB;

Se listan a continuación algunos de los miembros más relevantes de la struct:

  • NtTib (Thread Information Block): Es la primera estructura almacenada dentro del TEB. Contiene información estructural sobre el hilo, como los límites de la pila (donde empiezan y terminan el StackBase y el StackLimit). Esto es crucial para técnicas ofensivas que analizan o modifican la pila (Stack Spoofing o Stack Walking). Además, en sistemas x86 (32 bits), el NtTib apunta al primer elemento de la lista del SEH (Structured Exception Handling).

  • ClientId: Esta estructura contiene el UniqueProcess (el codiciado PID o Process ID) y el UniqueThread (el TID o Thread ID). Como mencionamos antes, si un binario necesitara conocer su propio PID (o el del hilo actual) de forma sigilosa, puede extraerlo directamente de aquí sin llamar a las APIs vigiladas como GetCurrentProcessId() o GetCurrentThreadId().

  • ProcessEnvironmentBlock: Puntero hacia el PEB. Como el PEB no tiene una dirección fija en memoria debido al ASLR (Address Space Layout Randomization), la forma estándar y limpia en que el programa localiza el PEB en el espacio de usuario es consultando este campo del TEB.

  • ThreadLocalStoragePointer (TLS): Apunta al almacenamiento local del hilo. Los callbacks de TLS (TLS Callbacks) son funciones que se ejecutan antes de que el punto de entrada principal (el main o WinMain) del programa comience a correr. En el caso de malware, se suele utilizar los callbacks de TLS para ejecutar código de inyección o técnicas de evasión antes de que los depuradores o soluciones de seguridad como antivirus y EDRs analicen el binario.

Accediendo al Thread Environment Block

Dado que no podemos usar APIs comunes (de la forma habitual) si queremos pasar desapercibidos, los procesadores modernos proporcionan un camino directo mediante registros de segmento. El código en ensamblador (ASM) o C puede aprovechar estos registros para leer la dirección base del TEB del hilo actual:

  • En sistemas de 32 bits (x86): El TEB se encuentra apuntado por el registro de segmento FS.
  • En sistemas de 64 bits (x64): El TEB se encuentra apuntado por el registro de segmento GS.

Por ejemplo, para saltar del TEB al PEB en una arquitectura de 64 bits, el binario debería ejecutar una instrucción equivalente a esta (leyendo el offset 0x60 donde vive el puntero al PEB dentro del TEB):

PPEB pPeb = (PPEB)__readgsqword(0x60);