IntermedioELFLinuxanalisis estaticoreverse engineeringformato binario

Formato ELF de Linux: Estructura y Analisis para Malware

Estructura completa del formato ELF (Executable and Linkable Format) de Linux. ELF header, program headers, section headers, simbolos, linking dinamico y analisis de malware en binarios Linux.

MalwareIntel Research··12 min lectura
Serie: Análisis de Binarios — Parte 4

ELF: el formato binario de Linux

Mientras Windows domina el escritorio, Linux gobierna servidores, contenedores, dispositivos IoT y la infraestructura cloud. El malware que apunta a estos entornos (botnets como Mirai, cryptominers, backdoors de APTs) usa el formato ELF. Con el crecimiento del malware Linux, especialmente en contenedores y dispositivos IoT, entender ELF es cada vez mas critico para analistas de malware.

ELF (Executable and Linkable Format) se usa en Linux, FreeBSD, Solaris, Android y otros sistemas basados en Unix. A diferencia del PE de Windows que evolucion desde DOS, ELF fue disenado desde el inicio para ser flexible y extensible.

Estructura general del formato ELF

Un archivo ELF tiene tres componentes principales:

ComponentePosicionFuncion
ELF HeaderInicio del archivo (offset 0)Identifica el archivo, apunta al resto
Program Header TableDespues del ELF HeaderSegmentos para ejecucion
Section Header TableFinal del archivo (tipicamente)Secciones para linking y analisis

La diferencia conceptual clave: los program headers son la vista de ejecucion (como el kernel carga el binario en memoria), y los section headers son la vista de linking (como el linker organiza los datos). Un binario puede ejecutarse sin section headers, pero no sin program headers.

ELF Header: los primeros 64 bytes

El ELF Header siempre esta al inicio del archivo y ocupa 52 bytes (32-bit) o 64 bytes (64-bit):

CampoTamanoDescripcion
e_ident[0..3]4 bytesMagic: 0x7F 'E' 'L' 'F'
e_ident[4]1 byteClase: 1=32-bit, 2=64-bit
e_ident[5]1 byteEndianness: 1=little, 2=big
e_ident[6]1 byteVersion ELF (siempre 1)
e_ident[7]1 byteOS/ABI: 0=System V, 3=Linux
e_type2 bytesTipo: ET_EXEC, ET_DYN, ET_REL
e_machine2 bytesArquitectura: x86, x86_64, ARM
e_entry4/8 bytesDireccion del entry point
e_phoff4/8 bytesOffset de Program Header Table
e_shoff4/8 bytesOffset de Section Header Table
e_phnum2 bytesNumero de program headers
e_shnum2 bytesNumero de section headers
e_shstrndx2 bytesIndice de la seccion de strings

Verificacion con readelf

readelf -h sample_elf

# Salida tipica:
# 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:                              DYN (Position-Independent Executable)
# Machine:                           Advanced Micro Devices X86-64
# Entry point address:               0x1060
# Start of program headers:          64 (bytes into file)
# Start of section headers:          14280 (bytes into file)
# Number of program headers:         13
# Number of section headers:         31

Tipos de ELF relevantes para malware

TipoValorDescripcionContexto malware
ET_EXEC2Ejecutable con direccion fijaBinarios estaticos, malware simple
ET_DYN3Shared object / PIEEjecutables modernos, librerias
ET_REL1Archivo objeto reubicableRootkits (modulos kernel .ko)
ET_CORE4Core dumpAnalisis forense

Nota sobre PIE: Los ejecutables modernos de Linux se compilan como Position-Independent Executables (tipo ET_DYN, no ET_EXEC). Esto permite ASLR. Si un malware es ET_EXEC con direccion fija, probablemente es antiguo o esta disenado para entornos sin ASLR.

Program Headers: los segmentos de ejecucion

Los program headers definen como el kernel carga el binario en memoria. Cada entrada describe un segmento:

CampoDescripcion
p_typeTipo de segmento (PT_LOAD, PT_DYNAMIC, etc.)
p_flagsPermisos: PF_R (read), PF_W (write), PF_X (execute)
p_offsetOffset del segmento en el archivo
p_vaddrDireccion virtual donde se carga
p_memszTamano en memoria
p_fileszTamano en archivo

Tipos de segmentos importantes

TipoValorDescripcion
PT_LOAD1Segmento cargado en memoria
PT_DYNAMIC2Informacion de linking dinamico
PT_INTERP3Ruta del interprete (ld-linux.so)
PT_NOTE4Notas del sistema (build ID, ABI)
PT_GNU_STACK0x6474E551Permisos del stack
PT_GNU_RELRO0x6474E552Read-only relocations
readelf -l sample_elf

# Program Headers:
#   Type           Offset   VirtAddr           FileSiz  MemSiz   Flg
#   INTERP         0x000318 0x0000000000000318  0x00001c 0x00001c R
#   LOAD           0x000000 0x0000000000000000  0x000628 0x000628 R
#   LOAD           0x001000 0x0000000000001000  0x000185 0x000185 R E
#   LOAD           0x002000 0x0000000000002000  0x0000f4 0x0000f4 R
#   LOAD           0x002db8 0x0000000000003db8  0x000258 0x000260 RW
#   DYNAMIC        0x002dc8 0x0000000000003dc8  0x0001f0 0x0001f0 RW
#   GNU_STACK      0x000000 0x0000000000000000  0x000000 0x000000 RW

Indicadores de malware en program headers:

  • Stack ejecutable (PT_GNU_STACK con flag X): El stack normalmente no deberia ser ejecutable. Si lo es, el binario puede ejecutar shellcode en el stack.
  • Segmento LOAD con permisos RWX: Similar a las secciones RWX en PE, indica codigo auto-modificable.
  • Sin PT_INTERP: El binario es estaticamente enlazado, no depende de librerias compartidas. Comun en malware que quiere funcionar en cualquier distribucion Linux.
  • MemSiz mucho mayor que FileSiz en un segmento LOAD: Se expande en runtime, similar al patron de packers en PE.

Section Headers: la vista de analisis

Las section headers describen las secciones del binario. Son opcionales para la ejecucion pero esenciales para el analisis:

Secciones estandar en ELF

SeccionContenidoEquivalente PE
.textCodigo ejecutable.text
.dataVariables inicializadas.data
.bssVariables no inicializadas.bss
.rodataDatos de solo lectura, strings.rdata
.symtabTabla de simbolosNo tiene equivalente directo
.strtabStrings de nombres de simbolosParte de .rdata
.dynsymSimbolos dinamicosParte de Import/Export Table
.dynstrStrings de simbolos dinamicosParte de Import/Export Table
.pltProcedure Linkage TableParte de IAT
.gotGlobal Offset TableIAT
.got.pltGOT para PLTIAT
.dynamicInformacion de linking dinamicoImport Directory
.initCodigo de inicializacionTLS callbacks
.finiCodigo de finalizacionNo tiene equivalente
.init_arrayPunteros a funciones de initTLS callbacks
.note.*Notas del compiladorDebug Directory
.commentString del compiladorRich Header
readelf -S sample_elf

# Section Headers:
#   [Nr] Name              Type             Address           Off    Size
#   [ 0]                   NULL             0000000000000000  000000 000000
#   [ 1] .interp           PROGBITS         0000000000000318  000318 00001c
#   [14] .text             PROGBITS         0000000000001060  001060 000125
#   [16] .rodata           PROGBITS         0000000000002000  002000 000011
#   [25] .data             PROGBITS         0000000000004000  003000 000010
#   [26] .bss              NOBITS           0000000000004010  003010 000008

Secciones stripped: el problema del analisis

El comando strip elimina los section headers innecesarios para la ejecucion, reduciendo el tamano del binario y dificultando el analisis:

# Binario normal
file sample
# sample: ELF 64-bit LSB pie executable, x86-64, dynamically linked, not stripped

# Despues de strip
strip sample
file sample
# sample: ELF 64-bit LSB pie executable, x86-64, dynamically linked, stripped

El malware suele estar stripped para dificultar el reverse engineering. Sin la tabla de simbolos (.symtab), GDB y otros debuggers no pueden mostrar nombres de funciones, obligando al analista a trabajar con direcciones.

Linking dinamico: .plt, .got y .dynamic

El mecanismo de linking dinamico en ELF es equivalente a la IAT del PE, pero usa un enfoque diferente basado en PLT (Procedure Linkage Table) y GOT (Global Offset Table).

Como funciona la resolucion lazy

  1. El codigo llama a una funcion externa (ej. printf) a traves de la PLT.
  2. La primera vez, la PLT redirige al resolver del linker dinamico (ld-linux.so).
  3. El resolver busca la funcion en las librerias cargadas.
  4. Escribe la direccion real en la GOT.
  5. Las llamadas siguientes van directamente de la PLT a la GOT (ya resuelta).
# Ver imports dinamicos
readelf -d sample_elf | grep NEEDED
#  0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
#  0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]

# Ver simbolos dinamicos importados
readelf --dyn-syms sample_elf

# O con objdump
objdump -T sample_elf

Funciones sospechosas en imports ELF

FuncionLibreriaIndicador
execve / systemlibcEjecucion de comandos
fork / clonelibcCreacion de procesos
socket / connect / sendlibcComunicacion de red
ptracelibcAnti-debug o inyeccion
mmap / mprotectlibcManipulacion de memoria
dlopen / dlsymlibdlCarga dinamica (equiv. LoadLibrary)
unlink / renamelibcEliminacion de archivos
chmodlibcCambiar permisos
setuid / setgidlibcEscalada de privilegios

Simbolos: la informacion que revela el autor

La tabla de simbolos contiene nombres de funciones, variables y otros identificadores. En un ELF no stripped, es una fuente invaluable de informacion:

# Tabla de simbolos completa
readelf -s sample_elf

# Buscar funciones especificas
readelf -s sample_elf | grep -i "crypt\|encrypt\|socket\|exec"

# nm es otra herramienta para simbolos
nm sample_elf
# T = funcion definida en .text
# U = funcion undefined (importada)
# D = variable en .data

En binarios compilados con Go, Rust o C++ (no stripped), los nombres de funciones revelan estructura del codigo, modulos y paquetes usados. El malware compilado en Go es especialmente revelador porque Go incrusta los nombres de paquetes en los simbolos.

Analisis de protecciones de seguridad

La herramienta checksec verifica que protecciones tiene habilitadas un ELF:

checksec --file=sample_elf

# RELRO           STACK CANARY      NX            PIE
# Full RELRO      Canary found      NX enabled    PIE enabled
ProteccionQue haceImplicacion si ausente
NX (No Execute)Stack no ejecutableShellcode puede ejecutarse en stack
PIEDirecciones aleatorias (ASLR)Direcciones predecibles
RELRO (Full)GOT de solo lecturaGOT overwrite posible
Stack CanaryDetecta buffer overflowStack smashing factible
FORTIFYComprobaciones en funciones de stringMenos proteccion contra overflow

Patrones de malware:

  • Sin NX ni PIE: binario compilado sin protecciones, probablemente malware simple o antiguo.
  • Sin RELRO: permite GOT overwrite, tecnica usada por exploits pero rara en malware standalone.

Analisis estatico de ELF: flujo de trabajo

Paso 1: Identificacion basica

file sample_elf
# ELF 64-bit LSB pie executable, x86-64, dynamically linked,
# BuildID[sha1]=abc123, for GNU/Linux 3.2.0, stripped

sha256sum sample_elf
# a1b2c3d4e5f6... sample_elf

Paso 2: Headers y secciones

readelf -h sample_elf    # ELF header
readelf -l sample_elf    # Program headers (segmentos)
readelf -S sample_elf    # Section headers
readelf -d sample_elf    # Dynamic section (imports)

Paso 3: Strings y datos

strings -a sample_elf | head -50
strings -el sample_elf    # Strings en UTF-16 (raro en Linux)

Paso 4: Protecciones

checksec --file=sample_elf

Paso 5: Dependencias

ldd sample_elf
# No ejecutar ldd en binarios sospechosos (ejecuta el binario).
# Alternativa segura:
readelf -d sample_elf | grep NEEDED

Importante: Nunca ejecutar ldd en un binario sospechoso. ldd ejecuta parcialmente el binario para resolver dependencias. Usar readelf -d en su lugar.

Paso 6: Desemsamblado

objdump -d sample_elf | head -100    # Desensamblado completo
objdump -d -M intel sample_elf       # Sintaxis Intel

Tecnicas especificas de malware Linux

Binarios estaticamente enlazados

El malware puede compilarse con todas las librerias incluidas (static linking) para no depender de las librerias del sistema objetivo:

file static_malware
# ELF 64-bit LSB executable, x86-64, statically linked, stripped

Ventaja para el atacante: funciona en cualquier distribucion Linux sin depender de versiones de libc. Desventaja: tamano mucho mayor (varios MB vs cientos de KB).

Binarios Go en malware

Go se ha convertido en un lenguaje popular para malware por:

  • Compilacion estatica por defecto (funciona en cualquier Linux)
  • Cross-compilation trivial (compilar para Linux desde Windows)
  • Tamano grande dificulta analisis (binarios de 5-20 MB)
  • Concurrencia nativa (goroutines para C2 y tareas paralelas)

Familias en Go: GoBrut, Kaiji, Chaos, BotenaGo.

# Detectar binarios Go
strings sample_elf | grep "go.buildid"
strings sample_elf | grep "runtime.main"

Rootkits como modulos del kernel

Los rootkits de Linux se distribuyen como modulos del kernel (.ko), que son archivos ELF de tipo ET_REL:

file rootkit.ko
# rootkit.ko: ELF 64-bit LSB relocatable, x86-64

# Examinar informacion del modulo
modinfo rootkit.ko
readelf -s rootkit.ko | grep -i "init_module\|cleanup_module"

Memfd_create: ejecucion sin archivo

El malware avanzado de Linux usa memfd_create para crear un archivo en memoria, escribir un ELF en el y ejecutarlo con fexecve. El binario nunca toca el disco:

# Detectar en imports
readelf --dyn-syms sample_elf | grep "memfd_create\|fexecve"

Herramientas esenciales para ELF

HerramientaFuncion
readelfExaminar todas las estructuras ELF
objdumpDesensamblar y examinar secciones
nmListar simbolos
stringsExtraer strings
fileIdentificar tipo de archivo
checksecVerificar protecciones
lief (Python)Parsear ELF programaticamente
GhidraDecompilacion y analisis avanzado
GDBDebugging
strace / ltraceTrazar syscalls y llamadas a librerias

Conclusion

El formato ELF es el pilar del analisis de malware en Linux. Su estructura de program headers y section headers, su mecanismo de linking dinamico con PLT/GOT, y las particularidades de binarios Go y modulos del kernel forman el conocimiento base para cualquier analista que trabaje con amenazas en servidores, contenedores y dispositivos IoT.

La clave es dominar las herramientas nativas (readelf, objdump, strings) y entender que un malware Linux stripped y estaticamente enlazado requiere tecnicas de analisis diferentes a un PE de Windows con sus imports expuestas en la IAT.

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.