tutoriales.com

Análisis de Ficheros ELF: Desentrañando Binarios de Linux para Hacking Ético 🕵️‍♀️

Este tutorial te sumergirá en el mundo del análisis de ficheros ELF (Executable and Linkable Format), el formato estándar para ejecutables, librerías compartidas y archivos objeto en sistemas operativos tipo Unix como Linux. Aprenderás a utilizar herramientas esenciales para inspeccionar la estructura interna de estos binarios, comprender su funcionamiento y descubrir posibles vulnerabilidades que pueden ser explotadas en un contexto de hacking ético. Desde las cabeceras hasta los símbolos y las tablas de reubicación, desvelaremos los secretos de los binarios ELF.

Intermedio15 min de lectura7 views
Reportar error

🚀 Introducción al Mundo ELF en Hacking Ético

En el fascinante universo de la ciberseguridad, y específicamente en el hacking ético, comprender cómo funcionan los programas a un nivel bajo es una habilidad invaluable. Los sistemas operativos basados en Unix, como Linux, utilizan el formato de fichero ELF (Executable and Linkable Format) para sus ejecutables, bibliotecas compartidas y archivos objeto. Analizar estos ficheros es crucial para detectar vulnerabilidades, ingeniería inversa y desarrollo de exploits.

Este tutorial te guiará a través de las herramientas y técnicas fundamentales para diseccionar binarios ELF. Aprenderás a inspeccionar su estructura interna, identificar componentes clave y extraer información valiosa que puede ser utilizada en auditorías de seguridad.

📌 Nota: Este tutorial asume un conocimiento básico de la línea de comandos de Linux y conceptos elementales de programación, aunque no es estrictamente necesario ser un experto.

📖 ¿Qué es un Fichero ELF? Una Visión General

El formato ELF es un estándar flexible y extensible diseñado para optimizar la carga y ejecución de programas. En esencia, un fichero ELF es un contenedor que organiza diferentes tipos de datos y código de manera estructurada. Cada fichero ELF consta de una cabecera ELF, que describe la estructura general del archivo, seguida de diferentes secciones y segmentos.

Tipos de Ficheros ELF 🏷️

Existen varios tipos de ficheros ELF, cada uno con un propósito específico:

  • Relocatable File (Archivo Relocatable): Contiene código y datos adecuados para ser enlazados con otros archivos objeto o bibliotecas estáticas para crear un ejecutable o una biblioteca compartida. No es directamente ejecutable.
  • Executable File (Archivo Ejecutable): Contiene un programa que puede ser ejecutado directamente por el sistema operativo. Especifica cómo el programa debe ser cargado en memoria.
  • Shared Object File (Archivo de Objeto Compartido): Generalmente son bibliotecas compartidas (librerías *.so en Linux). Pueden ser enlazadas dinámicamente en tiempo de ejecución por múltiples programas.
  • Core File (Archivo Core): Un volcado de memoria de un proceso en el momento en que terminó anormalmente (colapsó). Útil para depuración post-mortem.
🔥 Importante: Entender la diferencia entre estos tipos es fundamental para saber qué esperar al analizar un binario.

Estructura de un Fichero ELF 🗺️

Un fichero ELF se puede ver desde dos perspectivas principales:

  1. Vista de Enlace (Linking View): Utilizada por el enlazador. Organiza el archivo en secciones (sections), que agrupan datos y código por tipo (ej. .text para código, .data para datos inicializados, .rodata para datos de solo lectura).
  2. Vista de Ejecución (Execution View): Utilizada por el cargador de programas. Organiza el archivo en segmentos (segments), que son regiones de memoria a las que se cargan las secciones. Por ejemplo, un segmento de código LOAD puede contener las secciones .text y .rodata.
Cabecera ELF Tabla Cabeceras de Programa Tabla Cabeceras de Secciones Segmentos (Vista de Ejecución) Secciones (Vista de Enlace) Datos de Secciones

🛠️ Herramientas Esenciales para el Análisis ELF

Para desentrañar los secretos de los binarios ELF, necesitaremos un conjunto de herramientas poderosas y omnipresentes en cualquier distribución de Linux. Estas utilidades forman parte del paquete GNU Binutils y son la base de nuestro análisis.

1. file - Identificando el Tipo de Fichero 🔍

La primera herramienta en nuestro arsenal es file. Es sencilla pero indispensable para obtener una descripción general del tipo de archivo con el que estamos tratando.

file /bin/ls

Salida esperada:

/bin/ls: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=..., stripped

Esta salida nos indica que /bin/ls es un fichero ELF de 64 bits, de tipo shared object (aunque se ejecuta como un programa, es en realidad un objeto compartido en sistemas modernos), para arquitectura x86-64, enlazado dinámicamente y con el intérprete (ld-linux-x86-64.so.2) especificado. La etiqueta stripped indica que los símbolos de depuración han sido eliminados.

2. readelf - El Microscopio ELF 🔬

readelf es, sin duda, la herramienta más completa para inspeccionar archivos ELF. Permite ver casi todos los aspectos de la estructura del binario, desde las cabeceras hasta las tablas de símbolos y reubicación.

Inspeccionando la Cabecera ELF

La cabecera ELF es lo primero que el sistema lee. Contiene información vital como la arquitectura, el tipo de archivo, el punto de entrada y la ubicación de las tablas de cabeceras de programa y de sección.

readelf -h /bin/ls

Salida (extracto):

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
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           AMD x86-64
  Version:                           0x1
  Entry point address:               0x1a80
  Start of program headers:          64 (bytes into file)
  Start of section headers:          24840 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29

Aquí podemos ver que es un ELF64, little endian, de tipo DYN (Shared Object), para AMD x86-64. El Entry point address es crucial, ya que indica dónde comenzará la ejecución del programa. Las ubicaciones y tamaños de las tablas de cabeceras de programa y sección también son vitales para el cargador y el enlazador.

Viendo los Segmentos de Programa

Los segmentos describen cómo se cargará el programa en memoria. readelf -l muestra la tabla de cabeceras de programa.

readelf -l /bin/ls

Salida (extracto):

Elf file type is DYN (Shared object file)
Entry point 0x1a80
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz             Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000008fc 0x00000000000008fc R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000009664 0x0000000000009664 R E    0x1000
  LOAD           0x000000000000b000 0x000000000000c000 0x000000000000c000
                 0x0000000000001f30 0x0000000000001f40 R W    0x1000
  DYNAMIC        0x000000000000b0d8 0x000000000000c0d8 0x000000000000c0d8
                 0x00000000000001e0 0x00000000000001e0 R W    0x8
  ... (otras cabeceras de programa)

Cada línea LOAD indica una región de memoria que será cargada. Las Flags (R, W, E) indican permisos de lectura, escritura y ejecución. Es particularmente interesante buscar segmentos LOAD con permisos W (escritura), ya que podrían ser objetivos para corrupción de memoria o inyección de código.

Explorando las Secciones

Las secciones contienen los datos y el código organizados lógicamente. readelf -S nos muestra la tabla de secciones.

r eadelf -S /bin/ls

Salida (extracto):

There are 30 section headers, starting at offset 0x60d8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.build-id NOTE             0000000000000280  00000280
       0000000000000024  0000000000000000   A       0     0     4
  [ 2] .rela.plt         RELA             0000000000000330  00000330
       00000000000000c0  0000000000000018   I       7    17     8
  [ 3] .init             PROGBITS         0000000000001000  00001000
       0000000000000017  0000000000000000 AX       0     0     4
  [ 4] .plt              PROGBITS         0000000000001020  00001020
       0000000000000160  0000000000000010 AX       0     0     16
  [ 5] .plt.got          PROGBITS         0000000000001180  00001180
       0000000000000008  0000000000000008 AX       0     0     8
  [ 6] .text             PROGBITS         0000000000001190  00001190
       000000000000787e  0000000000000000 AX       0     0     16
  ... (otras secciones)

Secciones clave para el análisis:

  • .text: Contiene el código ejecutable del programa.
  • .rodata: Datos de solo lectura (cadenas de texto, constantes).
  • .data: Datos inicializados (variables globales con valores iniciales).
  • .bss: Datos no inicializados (variables globales que se inicializan a cero).
  • .init, .fini: Código ejecutado al inicio y al final del programa, respectivamente.
  • .plt, .got: Tablas utilizadas para enlazar funciones dinámicamente. Cruciales para técnicas de ataque como GOT Hooking.
  • .dynsym, .symtab: Tablas de símbolos dinámicos y estáticos.

Listando Símbolos

Los símbolos son nombres de funciones y variables. readelf -s o readelf --symbols muestra la tabla de símbolos. Si el binario está stripped, muchos símbolos se habrán eliminado, dificultando la ingeniería inversa.

readelf -s /bin/ls

Salida (extracto):

Symbol table '.dynsym' contains 102 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.34 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (3)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND readdir@GLIBC_2.34 (2)
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.34 (2)
     ... (más símbolos)

Los símbolos con UND (undefined) son funciones que el programa espera encontrar en una biblioteca externa. Los símbolos GLOBAL son exportados para que otros módulos puedan usarlos.

Tablas de Reubicación

Cuando un programa usa funciones de bibliotecas compartidas, necesita que el enlazador dinámico (Dynamic Linker) resuelva sus direcciones en tiempo de ejecución. Las tablas de reubicación (.rela.dyn, .rela.plt) le indican al enlazador dónde y cómo modificar el código o los datos para apuntar a las direcciones correctas.

readelf -r /bin/ls

Salida (extracto):

Relocation section '.rela.dyn' at offset 0x3d8 contains 22 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000000e000  0000000000000008 R_X86_64_RELATIVE 0000000000000000 
000000000000e2b0  0000000000000008 R_X86_64_RELATIVE 0000000000000000 
... (más reubicaciones)

Relocation section '.rela.plt' at offset 0x330 contains 10 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
0000000000001028  0001000000000007 R_X86_64_JUMP_SLOT 0000000000000000 __libc_start_main@GLIBC_2.34 + 0
0000000000001030  0002000000000007 R_X86_64_JUMP_SLOT 0000000000000000 __gmon_start__ + 0
... (más reubicaciones)

La tabla .rela.plt es particularmente interesante para el hacking ético, ya que las entradas R_X86_64_JUMP_SLOT son los puntos que serán parcheados con las direcciones reales de las funciones de la biblioteca. Esto es explotado en técnicas como GOT hijacking.

3. objdump - El Desensamblador 💻

objdump es otra herramienta vital que puede mostrar información similar a readelf, pero su característica más potente es la capacidad de desensamblar el código ejecutable de las secciones.

Desensamblando Secciones

Para ver el código máquina en formato de assembly, usamos la opción -d o -D (para desensamblar todas las secciones ejecutables).

objdump -d /bin/ls | less

Salida (extracto de .text):

/bin/ls:     file format elf64-x86-64


Disassembly of section .init:

0000000000001000 <_init>:
  1000:       f3 0f 1e fa             endbr64 
  1004:       48 83 ec 08             sub    $0x8,%rsp
  1008:       48 8b 05 01 2f 00 00    mov    0x2f01(%rip),%rax        # 4110 <__gmon_start__>
  100f:       48 85 c0                test   %rax,%rax
  1012:       74 02                   je     1016 <_init+0x16>
  1014:       e8 e7 ff ff ff          call   1000 <__gmon_start__>
  1019:       48 83 c4 08             add    $0x8,%rsp
  101d:       c3                      ret    


Disassembly of section .plt:

0000000000001020 <.plt>:
  1020:       ff 35 1a 2f 00 00       pushq  0x2f1a(%rip)        # 4040 <_GLOBAL_OFFSET_TABLE_+0x8>
  1026:       25 00 00 00 00          and    $0x0,%eax
  102b:       f2 ff 25 21 2f 00 00    bnd jmpq *0x2f21(%rip)        # 4050 <_GLOBAL_OFFSET_TABLE_+0x10>
  1032:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

0000000000001038 <__libc_start_main@plt>:
  1038:       f3 0f 1e fa             endbr64 
  103c:       ff 25 e2 2e 00 00       jmpq   *0x2ee2(%rip)        # 3f24 <__libc_start_main@GLIBC_2.34>
  1042:       0f 1f 40 00             nopl   0x0(%rax)

...

Esto nos muestra el código máquina (en hexadecimal) y su traducción a assembly. Esencial para ingeniería inversa, análisis de vulnerabilidades como buffer overflows o format string bugs, y para entender la lógica de un programa cuando el código fuente no está disponible.

4. strings - Extrayendo Cadenas Legibles 📜

strings busca secuencias de caracteres imprimibles dentro de un fichero binario. A menudo revela mensajes de error, nombres de archivos, URLs, claves de API o cualquier texto que el programa utiliza. Es una forma rápida de obtener una pista sobre la funcionalidad del programa o información sensible.

strings /bin/ls | grep "Usage"

Salida (ejemplo):

Usage: %s [OPTION]... [FILE]...
Try '%s --help' for more information.

Esto es muy útil para identificar funciones o características ocultas, o incluso credenciales codificadas en binarios maliciosos.


💡 Casos de Uso en Hacking Ético

El análisis de ficheros ELF no es solo una curiosidad académica; es una habilidad fundamental con aplicaciones directas en el hacking ético y la ciberseguridad.

1. Ingeniería Inversa de Malware 🦠

Cuando se encuentra un binario malicioso (un troyano, un rootkit, etc.) sin su código fuente, el análisis ELF es el primer paso. Se utiliza para:

  • Identificar funciones clave (main, init, evil_function).
  • Detectar llamadas a API sospechosas (red, archivos, procesos).
  • Extraer cadenas de texto relevantes (direcciones IP de C2, nombres de archivos, claves).
  • Comprender la lógica del malware para desarrollar herramientas de detección o desinfección.

2. Búsqueda de Vulnerabilidades en Binarios (Binary Exploitation) 💥

Los exploits binarios se basan en encontrar fallos en el código o la configuración de un programa compilado. El análisis ELF ayuda a:

  • Identificar secciones con permisos de escritura y ejecución: Una sección W E (writable and executable) es un sueño para un atacante, ya que puede escribir código en ella y luego ejecutarlo.
  • Localizar la tabla GOT (Global Offset Table) y la PLT (Procedure Linkage Table): Manipular estas tablas permite redirigir llamadas a funciones de biblioteca a código arbitrario (GOT/PLT Hijacking).
  • Detectar mitigaciones de seguridad: Verificar si un binario ha sido compilado con protecciones como ASLR (Address Space Layout Randomization), DEP/NX (Data Execution Prevention/No-Execute), PIE (Position Independent Executable), o Canaries. Herramientas como checksec (no parte de Binutils, pero esencial) se basan en la información ELF para esto.
⚠️ Advertencia: Desactivar las mitigaciones de seguridad durante el desarrollo o la prueba de exploits puede ser peligroso y solo debe hacerse en entornos controlados y aislados.

3. Análisis Forense Digital 🕵️‍♂️

En un entorno forense, un analista podría necesitar:

  • Examinar binarios sospechosos encontrados en un sistema comprometido.
  • Identificar si un programa ha sido modificado o empaquetado (packed).
  • Extraer información de ficheros core para entender la causa de un fallo o crash que fue explotado.

🛡️ Protecciones y Mitigaciones ELF

Los desarrolladores y los compiladores implementan diversas protecciones para dificultar la explotación de vulnerabilidades en binarios ELF. Conocer estas mitigaciones es tan importante como saber cómo analizarlas.

MitigaciónDescripciónImpacto en Hacking ÉticoCómo verificar con readelf/objdump/checksec
------------
NX/DEPMarca las secciones de datos como no ejecutables, evitando la ejecución de código en la pila o el heap.Dificulta la inyección y ejecución directa de shellcode.readelf -l (segmentos LOAD sin E en Flags para secciones de datos). Herramienta checksec.
ASLRAleatoriza las direcciones de memoria de las librerías, pila, heap y el ejecutable en cada ejecución.Dificulta la predicción de direcciones para saltar a código malicioso. Requiere fuga de información.No directamente visible en ELF, pero checksec lo infiere si PIE está habilitado.
------------
PIECompila el ejecutable para que sea independiente de la posición (como las bibliotecas compartidas). Complementa ASLR para el binario principal.El binario principal también se carga en direcciones aleatorias.readelf -h (Type: DYN). Herramienta checksec.
CanariesPequeños valores secretos insertados en la pila antes de la dirección de retorno de una función. Si se sobrescriben, el programa termina.Previene buffer overflows simples que sobrescriben la dirección de retorno.No directamente en ELF, pero objdump -d puede mostrar instrucciones xor y comparaciones de canary. Herramienta checksec.
------------
RELRO(Relocation Read-Only) Hace que las tablas de reubicación (.got, .plt) sean de solo lectura después de que el enlazador dinámico las ha resuelto.Dificulta o impide el GOT/PLT Hijacking.readelf -l (segmento GNU_RELRO). Herramienta checksec.
¿Qué es `checksec`? `checksec` es un script de shell (a menudo incluido en distribuciones como Kali Linux) que automatiza la inspección de binarios ELF para verificar qué mitigaciones de seguridad están habilitadas. Hace uso de `readelf` y `objdump` internamente para extraer la información relevante y presentarla de forma legible. Es una herramienta indispensable en el *binary exploitation*.

Verificando Mitigaciones con checksec (ejemplo)

checksec --file=/bin/ls

Salida (ejemplo):

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RPATH:    No RPATH
    RUNPATH:  No RUNPATH
    Symbols:  No Symbols

Esta salida nos indica que /bin/ls tiene Full RELRO, Canary found, NX enabled y PIE enabled, lo que lo convierte en un binario relativamente seguro contra muchos ataques básicos. El No Symbols indica que está stripped.


🚀 Ejercicio Práctico: Analizando un Binario Simple

Vamos a poner en práctica lo aprendido con un pequeño programa en C. Crea un archivo llamado vuln.c con el siguiente contenido:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void greet(char *name) {
    char buffer[64];
    strcpy(buffer, name);
    printf("Hello, %s!\n", buffer);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s <name>\n", argv[0]);
        exit(1);
    }
    greet(argv[1]);
    return 0;
}

Compílalo sin mitigaciones de seguridad (esto es crucial para el propósito de aprendizaje):

gcc -fno-stack-protector -z execstack -no-pie -o vuln vuln.c

Ahora, analicemos este binario vuln:

  1. Tipo de fichero:
file ./vuln
*Esperarás ver un `ELF 64-bit LSB executable, x86-64, ... not stripped`.* Nótese que es `executable`, no `shared object`, y `not stripped`.

2. Cabecera ELF:

readelf -h ./vuln
*El `Entry point address` será importante. `Type: EXEC`.* Fíjate que al no usar `-no-pie` el tipo de binario sería `DYN`.

3. Cabeceras de programa y permisos:

readelf -l ./vuln
*Busca un segmento `LOAD` con permisos `RWE` (Read, Write, Execute). Esto indica una sección ejecutable y escribible, un riesgo significativo.* Observa cómo la pila (stack) también podría ser `RWE` debido a `-z execstack`.

4. Secciones:

readelf -S ./vuln
*Localiza `.text`, `.data`, `.bss`. Identifica los tamaños y ubicaciones.*

5. Símbolos:

readelf -s ./vuln
*Deberías ver `greet`, `main`, `strcpy`, `printf`. La presencia de `greet` y `strcpy` es clave.* Si hubiéramos compilado con `-s` (`strip`), estos símbolos no estarían.

6. Desensamblado de la función greet:

objdump -d ./vuln | grep -A 20 "<greet>"
*Inspecciona el código assembly. Fíjate en la llamada a `strcpy` y cómo maneja `buffer`. Un `strcpy` a un buffer de tamaño fijo es una receta para un *buffer overflow* si la entrada es demasiado grande.* Identifica el tamaño del buffer y cómo se manipula el *stack pointer* (`rsp`).

7. Verificación de mitigaciones:

checksec --file=./vuln
*Aquí deberías ver `No RELRO`, `No Canary found`, `NX disabled`, `No PIE`. Esto confirma que hemos compilado un binario vulnerable para nuestro estudio.* La ausencia de estas mitigaciones hace que el binario sea mucho más fácil de explotar.

Este pequeño ejercicio muestra cómo, al combinar las herramientas, podemos obtener una imagen clara de la arquitectura de un binario, sus debilidades y las protecciones que se le han aplicado (o no).


✨ Conclusión y Próximos Pasos

El análisis de ficheros ELF es una piedra angular en el campo del hacking ético y la ciberseguridad. Al dominar herramientas como file, readelf, objdump y strings, obtienes la capacidad de mirar más allá de la superficie de un programa, desentrañar su funcionamiento interno y descubrir vulnerabilidades ocultas. Esta habilidad es indispensable para la ingeniería inversa, el desarrollo de exploits, el análisis de malware y la auditoría de seguridad de sistemas Linux.

Continúa practicando con diferentes binarios, experimenta con las opciones de las herramientas y profundiza en la arquitectura de los sistemas operativos y la compilación de programas. Cuanto mejor entiendas cómo se construye un programa, mejor podrás identificar sus puntos débiles y protegerlo.

Paso 1: Revisa la documentación de `man readelf`, `man objdump` para explorar más opciones.
Paso 2: Practica con binarios del sistema (`/bin/bash`, `/usr/bin/ssh`).
Paso 3: Aprende sobre depuradores como `GDB` para análisis dinámico de binarios.
Paso 4: Investiga sobre *Binary Exploitation* (Buffer Overflows, Format String Bugs) para aplicar tus conocimientos ELF.

¡Sigue explorando el fascinante mundo de la ciberseguridad!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!