Anatomía de un PE: Entendiendo los Ejecutables de Windows para Análisis de Malware
Guía técnica completa del formato PE (Portable Executable) de Windows desde la perspectiva del análisis de malware. Headers, secciones, imports, exports, relocations, resources y cómo el malware manipula cada componente.
El formato que todo analista de malware debe dominar
Todo análisis de malware en Windows comienza con la misma pregunta: ¿qué es este archivo? Y la respuesta está en el formato PE (Portable Executable). El PE es la estructura que Windows usa para ejecutables (.exe), bibliotecas (.dll), drivers (.sys) y otros binarios. Entender este formato es el requisito previo para cualquier forma de análisis de malware, estático o dinámico.
Este artículo descompone el formato PE desde la perspectiva del analista de malware: no como una referencia de documentación (para eso está la especificación de Microsoft), sino como una guía de qué buscar, qué es normal, qué es sospechoso y qué revela el comportamiento probable del malware.
Estructura general del PE
Un archivo PE tiene la siguiente estructura, de principio a fin:
Offset 0x0000 ┌─────────────────────────┐
│ DOS Header │ 64 bytes, incluye magic "MZ"
├─────────────────────────┤
│ DOS Stub │ Programa DOS legacy ("This program...")
├─────────────────────────┤
│ PE Signature │ 4 bytes: "PE\0\0"
├─────────────────────────┤
│ COFF File Header │ 20 bytes: machine, sections, timestamp
├─────────────────────────┤
│ Optional Header │ Variable: entry point, image base, subsystem
│ (PE32 o PE32+) │ Data Directories (imports, exports, resources...)
├─────────────────────────┤
│ Section Headers │ Array de secciones (.text, .data, .rsrc, etc.)
├─────────────────────────┤
│ Section Data │ Contenido real de cada sección
│ .text (código) │
│ .data (datos) │
│ .rdata (read-only) │
│ .rsrc (recursos) │
│ .reloc (relocations)│
└─────────────────────────┘
DOS Header y DOS Stub
DOS Header (IMAGE_DOS_HEADER)
Los primeros 64 bytes del archivo. Los campos relevantes para análisis de malware:
| Campo | Offset | Tamaño | Significado para el analista |
|---|---|---|---|
e_magic | 0x00 | 2 bytes | Magic number: 0x5A4D ("MZ"). Si no empieza con MZ, no es un PE válido |
e_lfanew | 0x3C | 4 bytes | Offset al PE signature. Valor típico: 0x80-0x100. Valores muy grandes pueden indicar contenido oculto entre el DOS stub y el PE header |
DOS Stub
Programa DOS legacy que imprime "This program cannot be run in DOS mode." cuando se ejecuta en DOS. En la práctica, este espacio se ignora en Windows moderno.
Indicador de malware: algunos malware almacenan datos cifrados o shellcode en el espacio entre el DOS stub y el PE header (la zona entre e_lfanew y el DOS stub). Si e_lfanew apunta muy lejos (0x1000+), puede haber contenido oculto.
PE Signature y COFF File Header
PE Signature
4 bytes en el offset indicado por e_lfanew: "PE\0\0" (0x50450000). Confirma que es un PE válido.
COFF File Header (IMAGE_FILE_HEADER)
20 bytes inmediatamente después de la PE signature:
| Campo | Tamaño | Significado |
|---|---|---|
Machine | 2 bytes | Arquitectura: 0x14C (x86), 0x8664 (x64), 0xAA64 (ARM64) |
NumberOfSections | 2 bytes | Cuántas secciones tiene el PE |
TimeDateStamp | 4 bytes | Timestamp de compilación (Unix epoch) |
PointerToSymbolTable | 4 bytes | Normalmente 0 en PEs modernos |
NumberOfSymbols | 4 bytes | Normalmente 0 |
SizeOfOptionalHeader | 2 bytes | Tamaño del Optional Header |
Characteristics | 2 bytes | Flags: IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_DLL, etc. |
TimeDateStamp: qué revela
El timestamp de compilación es uno de los primeros indicadores que examina un analista:
| Valor | Interpretación |
|---|---|
| Fecha reciente y razonable | Compilación legítima o malware reciente |
| Fecha futura | Timestamp manipulado (el atacante lo falsificó) |
| Fecha muy antigua (ej. 1970, 1990) | Timestamp eliminado/corrupto o herramienta de compilación vieja |
| 0x00000000 | Timestamp borrado deliberadamente |
| Delphi epoch (1992-06-19) | Compilado con Delphi/Borland (timestamp base diferente) |
Muchas familias de malware falsifican el timestamp para dificultar la atribución temporal. Otros lo dejan en 0 o usan fechas futuras. Un timestamp legítimo que coincide con la fecha de aparición de la muestra añade confianza al análisis.
Optional Header
A pesar de su nombre, el Optional Header es obligatorio en ejecutables PE. Contiene información crítica para la carga del binario:
Campos clave para análisis de malware
| Campo | Significado | Indicadores sospechosos |
|---|---|---|
Magic | 0x10B (PE32, 32-bit) o 0x20B (PE32+, 64-bit) | |
AddressOfEntryPoint | RVA donde comienza la ejecución | Entry point en una sección inusual (no .text) indica packing |
ImageBase | Dirección de carga preferida | 0x00400000 típico para .exe, 0x10000000 para .dll |
SectionAlignment | Alineación de secciones en memoria | Típicamente 0x1000 (4 KB) |
FileAlignment | Alineación de secciones en disco | Típicamente 0x200 (512 bytes) |
SizeOfImage | Tamaño total en memoria | Valor inusualmente grande puede indicar secciones ocultas |
Subsystem | Tipo de aplicación | 2 = GUI, 3 = Console. Malware a veces usa GUI sin ventana |
DllCharacteristics | Flags de seguridad | ASLR (0x40), DEP (0x100), CFG (0x4000). Ausencia indica compilación antigua o deliberada |
CheckSum | Checksum del archivo | Drivers (.sys) requieren checksum válido. Ejecutables normales lo ignoran |
AddressOfEntryPoint y detección de packers
El entry point es donde Windows transfiere el control de ejecución al programa. En un PE legítimo, el entry point está al inicio de la sección .text. En un PE empaquetado:
- Entry point en la última sección (donde está el unpacking stub)
- Entry point en una sección con nombre inusual (
.UPX0,.aspack,.themida) - Entry point fuera de cualquier sección definida
Data Directories
El Optional Header termina con un array de Data Directories: punteros a estructuras especiales del PE:
| Index | Nombre | Uso en análisis de malware |
|---|---|---|
| 0 | Export Table | Funciones que el PE exporta (DLLs principalmente) |
| 1 | Import Table | Funciones importadas: la fuente de info más valiosa |
| 2 | Resource Table | Recursos embebidos (iconos, strings, datos arbitrarios) |
| 4 | Certificate Table | Firma digital (Authenticode) |
| 5 | Base Relocation Table | Datos de reubicación para ASLR |
| 6 | Debug Directory | Información de depuración (PDB path) |
| 11 | Bound Import | Imports pre-resueltas (optimización legacy) |
| 12 | IAT (Import Address Table) | Tabla de direcciones de funciones importadas |
| 14 | CLR Runtime Header | Header .NET (indica que es un assembly .NET) |
Import Table: la mina de oro del análisis estático
Por qué las imports son tan importantes
Cada función que un PE llama de una DLL externa aparece (normalmente) en la Import Table. Las imports revelan qué capacidades tiene el malware sin necesidad de ejecutarlo.
APIs de Windows que indican comportamiento malicioso
| API | DLL | Comportamiento indicado |
|---|---|---|
CreateRemoteThread | kernel32.dll | Inyección de código en otro proceso |
VirtualAllocEx | kernel32.dll | Asignación de memoria en otro proceso (inyección) |
WriteProcessMemory | kernel32.dll | Escritura en memoria de otro proceso (inyección) |
NtUnmapViewOfSection | ntdll.dll | Process hollowing |
SetWindowsHookEx | user32.dll | Hooking de teclado/mouse (keylogger) |
InternetOpenUrl | wininet.dll | Descarga de archivos de Internet |
URLDownloadToFile | urlmon.dll | Descarga directa a disco |
CryptEncrypt | advapi32.dll | Cifrado (posible ransomware) |
RegSetValueEx | advapi32.dll | Escritura en registro (persistencia) |
CreateService | advapi32.dll | Creación de servicio Windows (persistencia, escalada) |
AdjustTokenPrivileges | advapi32.dll | Manipulación de privilegios (escalada) |
IsDebuggerPresent | kernel32.dll | Anti-debugging |
GetTickCount | kernel32.dll | Timing checks (anti-sandbox) |
Imports resueltas dinámicamente
El malware sofisticado evita poner APIs sospechosas en la Import Table. En vez de eso, resuelve las funciones en runtime:
// En vez de importar directamente:
// #include <windows.h>
// CreateRemoteThread(...)
// El malware resuelve la funcion en runtime:
typedef HANDLE (WINAPI *pCreateRemoteThread)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
HMODULE hKernel32 = GetModuleHandle("kernel32.dll");
// O mas sigiloso: usando hash en vez de string
pCreateRemoteThread myCreateRemoteThread = (pCreateRemoteThread)GetProcAddress(hKernel32, "CreateRemoteThread");
myCreateRemoteThread(hProcess, NULL, 0, pRemoteCode, NULL, 0, NULL);
Esto se llama dynamic API resolution. El resultado: la Import Table solo muestra GetModuleHandle y GetProcAddress (o LoadLibrary + GetProcAddress), ocultando las APIs realmente usadas.
Detección: herramientas como CAPA (Mandiant) y FLOSS detectan dynamic API resolution buscando patrones de hash de API names y llamadas a GetProcAddress.
Import hashing
Muchas familias de malware usan hashes en vez de strings para resolver APIs:
// Ejemplo: hash de "CreateRemoteThread" = 0x72A68B3C
FARPROC resolve_api(DWORD hash) {
// Recorrer PEB -> PEB_LDR_DATA -> InLoadOrderModuleList
// Para cada modulo, recorrer Export Table
// Calcular hash de cada nombre de funcion
// Si coincide con el hash buscado, devolver la direccion
}
Esto elimina completamente los strings de nombres de API del binario. Las herramientas de análisis estático necesitan tablas de hashes conocidos para resolver estas referencias.
Secciones del PE
Secciones estándar
| Sección | Contenido | Flags típicos |
|---|---|---|
.text | Código ejecutable | IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ |
.data | Variables globales inicializadas | IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE |
.rdata | Datos de solo lectura (strings, imports, exports) | IMAGE_SCN_MEM_READ |
.bss | Variables globales no inicializadas | IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE |
.rsrc | Recursos (iconos, diálogos, manifests, datos arbitrarios) | IMAGE_SCN_MEM_READ |
.reloc | Tabla de relocaciones (ASLR) | IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_DISCARDABLE |
.edata | Export table | IMAGE_SCN_MEM_READ |
.idata | Import table | IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE |
Indicadores de malware en secciones
| Indicador | Significado |
|---|---|
Sección .text con flag WRITE | Código auto-modificable (self-modifying code, unpacking) |
Nombres de sección inusuales (.UPX0, .aspack, .vmp0) | Packer conocido |
| Entropía de sección mayor a 7.0 (de 8.0 max) | Datos cifrados o comprimidos (packing) |
| Sección con tamaño en disco = 0 pero tamaño en memoria mayor a 0 | Sección que se llena en runtime (unpacking) |
| Muy pocas secciones (1-2) | Posible packing agresivo |
| Muchas secciones (más de 10) | Posible ofuscación o malware complejo |
Entry point en sección no-.text | Packing o inyección |
Entropía como indicador
La entropía mide la aleatoriedad de los datos. En el contexto de PE analysis:
| Entropía | Significado |
|---|---|
| 0 - 3.0 | Datos con patrones repetitivos (strings ASCII, código simple) |
| 3.0 - 6.0 | Código compilado normal, datos mixtos |
| 6.0 - 7.0 | Posible compresión o datos binarios |
| 7.0 - 7.9 | Altamente comprimido o cifrado (fuerte indicador de packing) |
| 7.9 - 8.0 | Datos aleatorios o cifrado fuerte |
Un PE legítimo tiene la sección .text con entropía ~6.0-6.5. Un PE empaquetado tiene secciones con entropía mayor a 7.0.
Resources: datos embebidos
La sección .rsrc (resources) contiene datos embebidos en el PE. En binarios legítimos: iconos, diálogos, manifests, version info. En malware:
- Payload cifrado: el dropper almacena el payload real como un recurso cifrado. Al ejecutarse, lee el recurso, lo descifra en memoria y lo ejecuta
- Configuración: datos de configuración del malware (C2 URLs, claves de cifrado)
- Segundo stage: otro PE embebido como recurso
- Documentos señuelo: PDFs o documentos que se muestran al usuario mientras el malware se instala en background
Detección: examinar los recursos con herramientas como Resource Hacker. Recursos con nombres genéricos (RT_RCDATA con IDs numéricos), alta entropía o tamaño grande son sospechosos.
Debug Directory y PDB Path
El Debug Directory puede contener la ruta al archivo PDB (Program Database) de símbolos de depuración:
C:\Users\admin\Desktop\malware_project\Release\payload.pdb
D:\work\rat_builder\x64\Release\client.pdb
El PDB path es valioso porque:
- Revela el nombre del proyecto y el directorio de desarrollo
- Puede contener el nombre de usuario del desarrollador
- Permite vincular muestras diferentes del mismo desarrollador
- A veces contiene rutas en cirílico (indicando desarrollador ruso)
Nota: muchas familias de malware eliminan o falsifican el PDB path. Su ausencia no indica nada; su presencia es un bonus de inteligencia.
Firma digital (Authenticode)
Los PE pueden tener firma digital (Authenticode) que certifica la identidad del publisher:
| Estado | Significado |
|---|---|
| Firma válida de publisher conocido | Binario legítimo (alta confianza) |
| Firma válida de publisher desconocido | Posible certificado robado o comprado |
| Firma inválida (certificado expirado/revocado) | Certificado robado, o malware firmado antes de la revocación |
| Sin firma | La mayoría del malware. También muchos binarios legítimos |
Certificados robados: grupos como Lazarus, APT41 y operadores de ransomware han usado certificados de firma de código robados para firmar malware. Esto permite bypass de controles que confían en binarios firmados.
Herramientas de análisis de PE
| Herramienta | Tipo | Uso principal |
|---|---|---|
| PE-bear | GUI | Visualización completa de estructura PE |
| pestudio | GUI | Triage rápido: imports, strings, entropy, indicators |
| CFF Explorer | GUI | Editor de headers PE, visualización de estructuras |
| Detect It Easy (DIE) | GUI | Detección de compilador, packer, linker |
| pefile (Python) | Librería | Scripting y análisis automatizado de PEs |
| CAPA (Mandiant) | CLI | Detección automática de capacidades (APIs, comportamiento) |
| FLOSS (Mandiant) | CLI | Extracción de strings ofuscadas y stack strings |
strings / strings -a | CLI | Extracción de strings ASCII y Unicode |
| Exeinfo PE | GUI | Detección de packers y protectors |
| HashMyFiles | CLI/GUI | Cálculo de hashes (MD5, SHA1, SHA256) |
Workflow de análisis estático de un PE
- Hash: calcular MD5, SHA1, SHA256. Buscar en VirusTotal
- Strings: extraer strings con
stringsy FLOSS. Buscar URLs, IPs, rutas, mensajes - PE headers: examinar timestamp, entry point, secciones, imports con pestudio
- Entropía: calcular entropía por sección. Entropía mayor a 7.0 indica packing
- Packer detection: ejecutar DIE para identificar el packer
- Imports: analizar la Import Table. Categorizar las APIs por comportamiento
- CAPA: ejecutar para detección automática de capacidades
- Resources: examinar recursos embebidos con Resource Hacker
- PDB path: buscar en el Debug Directory
- Firma: verificar Authenticode con sigcheck (Sysinternals)
Lo que viene después
Este artículo ha cubierto la estructura del formato PE y cómo analizarlo. El siguiente artículo profundiza en una de las técnicas más comunes de malware en Windows: la inyección de DLLs. Cómo funciona, qué variantes existen (classic DLL injection, reflective loading, DLL side-loading) y cómo detectar cada una.
Fuentes y referencias
- Microsoft. "PE Format Specification." Microsoft Docs, updated 2024.
- Sikorski, M. & Honig, A. "Practical Malware Analysis." No Starch Press, 2012.
- Andriesse, D. "Practical Binary Analysis." No Starch Press, 2018.
- Mandiant. "CAPA: Identifying Malware Capabilities." https://github.com/mandiant/capa
- Mandiant. "FLOSS: FLARE Obfuscated String Solver." https://github.com/mandiant/flare-floss
- hasherezade. "PE-bear." https://github.com/hasherezade/pe-bear
- Winitor. "pestudio." https://www.winitor.com/
- horsicq. "Detect It Easy." https://github.com/horsicq/Detect-It-Easy
- erocarrera. "pefile: Python PE parsing." https://github.com/erocarrera/pefile
- MITRE ATT&CK. "Obfuscated Files or Information (T1027)." https://attack.mitre.org/techniques/T1027/
Preguntas frecuentes
Libros recomendados
Artículos relacionados
DLL Injection en Windows: Técnicas, Variantes y Detección
Windows API Calls Más Usadas por Malware: Guía de Referencia para Analistas
Evasión de Antivirus: Ofuscación, Packers y Crypters
YARA Avanzado: Módulos PE, Math, Hash y Técnicas de Hunting Profesional
Analisis de DLLs y Handles en Memoria: dlllist, handles y Deteccion de Inyeccion
Analisis de Procesos en Memoria Windows: pslist, pstree y Deteccion de Anomalias
Este contenido tiene fines exclusivamente educativos y de investigación en ciberseguridad defensiva. No se proporcionan binarios maliciosos ni payloads ejecutables. El uso indebido de esta información es responsabilidad exclusiva del usuario. Leer disclaimer completo.