El formato Portable Executable (PE) es la especificación estándar utilizada por los sistemas operativos Windows para archivos ejecutables, librerías DLL, controladores de dispositivos (.sys), ejecutables del panel de control (.cpl) y componentes del ciclo de carga.
Derivado del estándar COFF (Common Object File Format), un archivo PE actúa como una estructura de datos organizada que provee al loader del sistema operativo toda la información necesaria para mapear el binario en la memoria virtual y transferir de forma segura el control al puntero de instrucciones.
Para la seguridad ofensiva, entender el formato PE a nivel de byte es casi obligatorio: muchas técnicas de evasión estática, modificación de la IAT (Import Address Table), inyecciones de código en secciones huérfanas (Cave Hacking) o el parseo en memoria (In-Memory Execution) dependen al 100% de esta estructura.
MS-DOS y DOS Stub Headers
Todo archivo ejecutable PE inicia con dos componentes diseñados para garantizar la compatibilidad con sistemas MS-DOS de 16 bits.
IMAGE_DOS_HEADER (64 Bytes)
Estructura fija localizada en el offset cero (0x00) del binario. Aunque contiene 19 miembros, el Windows solo evalúa críticamente dos campos fundamentales:
e_magic(2 Bytes / WORD): El número mágico de identificación. Su valor estático es0x5A4D, representado en ASCII por los caracteresMZ, en honor al diseñador de MS-DOS Mark Zbikowski. Si este marcador no se encuentra al inicio de la lectura, Windows rechaza el archivo por considerarlo corrupto.e_lfanew(4 Bytes / DWORD): Ubicado exactamente en el desplazamiento0x3Cdentro del encabezado DOS. Este campo almacena un desplazamiento (offset) que indica la posición exacta en el disco donde inicia el encabezado real de Windows: los NT Headers.
DOS Stub
Se sitúa inmediatamente después del DOS Header. Su única función operativa es desplegar en pantalla el mensaje condicional This program cannot be run in DOS mode si el binario es ejecutado en un entorno puro de línea de comandos de 16 bits, interrumpiendo la ejecución con un código de salida.
NT Headers (IMAGE_NT_HEADERS)
La dirección proporcionada por el campo e_lfanew apunta directamente a la estructura , que define la configuración de bajo nivel y la arquitectura del binario.
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;Esta estructura se divida en tres bloques:
Signature (4 bytes)
Representa la firma de validación del subsistema PE. Sirve para almacenar de forma invariable los bytes en formato hexadecimal 0x00004550, los cuales corresponden a la cadena ASCII PE\0\0.
IMAGE_FILE_HEADER (20 bytes)
La struct almacena metadatos genéricos sobre las capacidades de hardware del binario. Algunos de los campos destacables son:
Machine: Identifica la arquitectura de CPU. Valores estándar:0x014Cpara arquitecturas x86 (32 bits) y0x8664para arquitecturas x64 (64 bits).NumberOfSections: Define cuántas secciones componen el cuerpo del binario PE. Es fundamental al recorrer el archivo, ya que indica dónde termina la tabla de secciones.TimeDataStamp: Registra la marca de tiempo en la que se generó el binario.
IMAGE_OPTIONAL_HEADER (spoiler: no es opcional)
A pesar de que su nombre indique "Optional", este encabezado es obligatorio para todo binario que se cargue en la memoria virtual de Windows (imágenes de ejecución). Sus campos describen los parámetros de control críticos de memoria:
Magic: Determina si el binario es una imagen PE32 (valor0x010Bpara 32 bits) o PE32+ (valor0x020Bpara 64 bits).AddressOfEntryPoint: Dirección Relativa Virtual (RVA) que apunta a la primera instrucción de código que debe ejecutar la CPU (el punto de entrada del programa).ImageBase: Dirección de memoria virtual preferida por el compilador donde el sistema operativo debería mapear el binario al inicializarse.SectionAlignment/FileAlignment: Especifican las reglas de alineación interna en bytes para las secciones cuando están cargadas en la memoria virtual (normalmente un marco de página de 4 KB) o cuando se encuentran almacenadas físicamente en el disco (normalmente sectores de 512 bytes).
Data Directories
Ubicada al final del , se encuentra una matriz contigua de estructuras denominada DataDirectory. Por defecto consta de 16 entradas fijas, donde cada elemento define el tamaño y la ubicación relativa (RVA) de tablas analíticas complejas del sistema.
Para propósitos ofensivos y de evasión, las entradas prioritarias son las siguientes:
| Índice | Directorio de Datos | Contenido Técnico | Uso aplicado en evasión |
|---|---|---|---|
| 0 | Export Directory |
Tabla de funciones exportadas por el archivo (común en archivos .dll). |
Permite identificar los vectores expuestos. Se parsea dinámicamente para evadir monitoreo estático. |
| 1 | Import Directory |
Contiene la lista de librerías DLL externas y funciones que el programa requiere para ejecutarse. | El malware puede alterar estas dependencias para mitigar detecciones por firmas en la IAT. |
| 2 | Resource Directory |
Indexa los recursos del sistema (iconos, diálogos, manifiestos y binarios adjuntos). | Sección que se puede aprovechar para ocultar shellcode u otro tipo de contenido. |
| 5 | Base Relocation Table |
Tabla con las correcciones de direcciones necesarias si el binario no puede cargarse en su ImageBase preferida debido a ASLR. |
Aprovechable para inyecciones de tipo Reflective DLL Injection o Process Hollowing, donde el código debe reajustar sus punteros dinámicamente. |
| 14 | CLR Runtime Header |
Metadatos que indican que el archivo requiere el entorno .NET Framework de Microsoft. | Identifica binarios compilados bajo lenguajes administrados (C# / F#), requiriendo flujos de evasión orientados a la interfaz AMSI. |
Tabla de secciones y secciones físicas
Después de los NT Headers, se encuentra una matriz contigua de estructuras de tipo . Cada entrada de esta tabla describe los permisos de hardware, el tamaño y la ubicación física de las porciones de código y datos que forman parte del archivo ejecutable.
Cada encabezado de sección define flags de características que se traducen de forma directa en las Constantes de Protección de Memoria de la CPU (ej. permisos de Lectura, Escritura y Ejecución).
Las secciones convencionales que se encuentran dentro de un ejecutable son:
.text: Contiene las instrucciones binarias que se ejecutarán. Sus características de hardware se configuran estrictamente comoEXECUTE / READ-ONLY. (Cualquier intento de modificación en caliente sobre esta sección dispara DEP)..rdata: Almacena variables constantes, las tablas de importación (IAT) y las especificaciones de cadenas estáticas. Sus flags de memoria se limitan aREAD-ONLYcon el bit No-Execute activo..data: Aloja las variables globales inicializadas por el programa. Se le otorgan permisos deREAD / WRITEsin capacidades de ejecución de código..rsrc: Almacena datos estáticos del entorno de interfaz, manifiestos de ejecución, firmas digitales y recursos gráficos. Su acceso es solo de lectura.



