Estructura de binarios

Agradecemos a Ezequiel Salinas Bydlowski que comparte su experiencia y conocimientos a través de este post.
Ezequiel es especialista en ciberseguridad ofensiva, pentesting y Red Team, está enfocado en detección de vulnerabilidades y fortalecimiento defensivo.

Introducción

Este posteo habla sobre los tipos de binarios que existen dentro de los distintos SO (Sistemas Operativos), la intención es explicar cómo se conforman estos a bajo nivel, lo cual es un punto de partida para entender cómo se analiza malware. Mismo en este posteo veremos algunas de las herramientas para análisis estático de binarios. Se deja para una futura publicación lo que sería análisis de APK e IPSW.

Formatos

Los formatos de los distintos binarios se dividen según el sistema en que están preparados para ser ejecutados, los binarios PE están preparados para Windows, los ELF están preparados para Linux y Mach-O formato preparado para MacOS. Cada uno de estos tres tienen diferencias entre sí, como la arquitectura que soportan, la forma de identificar sus formatos, como deben organizarse las cabeceras, pero, tienen en común algo que es el tipo de archivo, esto quiere decir que los tres funcionan como un contenedor con instrucciones y herramientas para que el sistema a través de lenguaje ASM comprenda las acciones que debe realizar.

Analizando Binarios

Lo mejor para poder entender cómo se componen los binarios es analizarlos de manera estática, para poder ver cada header y entender para que sirve, con esto podemos ver que nos encontraremos dentro de cualquier binario a la hora de analizar.

Windows: Portable Executable (PE)

 

Es un estándar de binarios para Windows de 32 y 64 bits, se enfoca en ejecutables (.exe), archivos de tipo .dll y controladores (.sys).

Las cabeceras diferenciales de este tipo de archivo son:

  • DOS Header: Cualquier binario .exe que abras en un hex editor va a tener los siguientes dos bytes al principio 4D 5A → en ASCII significa MZ que son las iniciales de Mark Zbikowski uno de los ingenieros de Microsoft que diseño el sistema DOS (Disk Operating System) en los años 80s.

La mayoría de las DOS Headers son cabeceras de herencia que estan ahi solo por el mero hecho de hacer que un binario nuevo tenga compatibilidad con un sistema antiguo.

El campo que vemos como e_lfanew sería el parámetro de inicio del PE Header que importa para analizar, este es un puntero indica donde comienzan esas cabeceras importantes para analizar.

Entre los DOS Header y los PE Header normalmente hay un DOS Stub: es una parte del binario que imprime el mensaje This program cannot be run in DOS modesi se intenta ejecutar un .exe en un OS MS-DOS.

  • PE Header: Una vez que sigues la dirección marcada por la cabecera e_lfanew nos encontramos dentro de las PE Headers, la primera cabecera siempre comienza con los bytes 50 45 00 00 PE\0\0 .

El PE Header se divide en tres partes:

  • File Header (IMAGE_FILE_HEADER)

  • Optional Header (IMAGE_OPTIONAL_HEADER)

Hay que tener en cuenta que a pesar de que su nombre lo diga no es opcional, ya que es esencial para el correcto funcionamiento de binarios

.exe y .dll, esto ya que contiene cabeceras como las siguientes:

  • Entry Point es la dirección RVA (Relative Virtual Address) donde Windows empieza a ejecutar código. No siempre es el main() muchas veces pasa primero por el CRT Runtime. Es el primer campo a revisar cuando se analiza Malware.
  • Secciones: En la última parte tenemos las secciones, se podria considerar que es un mapa del binario, es un array IMAGE_SECTION_HEADER que describe sección por sección y lo que almacena dentro de ellas.

A continuación, una lista de las secciones que podrían encontrarse si analizaran un binario PE

Secciones del compilador:

Sección Contenido Permisos
.text Código ejecutable R + X
.data Variables globales inicializadas R + W
.rdata Datos de solo lectura, strings, imports R
.bss Variables no inicializadas R + W
.reloc Tabla de relocalizaciones para ASLR R
.pdata Tabla de excepciones (solo x64) R
.xdata Datos de unwinding para excepciones (x64) R
.edata Export Table (funciones exportadas) R
.idata Import Table (DLLs y funciones importadas) R
.tls Thread Local Storage R + W
.rsrc Recursos: iconos, strings, manifests, versión R
.crt Datos de inicialización del CRT de C++ R

Secciones de debugging*:

Sección Contenido Permisos
.debug Información de debugging genérica R
.debug$S Símbolos de debugging (MSVC) R
.debug$T Tipos de debugging (MSVC) R
.symtab Tabla de símbolos R
.strtab Tabla de strings de símbolos R

Secciones de seguridad y protección:

Sección Contenido Permisos
.sxdata Datos de Safe Exception Handlers (SEH) R
.gfids Guard CF — tabla de funciones válidas para CFG R
.gehcont Guard EH Continuation (mitigación de excepciones) R
.voltbl Volatile metadata table R

Secciones específicas de .NET / CLR**:

Sección Contenido Permisos
.text (CLR) IL bytecode + metadata R + X
.rsrc (CLR) Recursos del ensamblado R
.reloc (CLR) Relocalizaciones del CLR R

*Estas secciones generalmente se eliminan en builds de Release. Si las ves en un binario sospechoso, puede ser que el autor compiló en modo Debug por descuido, lo cual filtra rutas del sistema, nombres de variables y estructura del proyecto.

**Los binarios .NET tienen una estructura interna completamente diferente dentro de estas secciones. El entry point real lleva al CLR runtime, no directamente al código del programador.

Las letras en permisos significan lo siguiente: R (lectura), W (escritura) y X (ejecución). Es importante aclarar que estos no son los permisos que tienes como usuario sobre el archivo en disco, sino los atributos que el loader de Windows asigna a cada región de memoria cuando carga el binario. Es decir, determinan qué operaciones puede realizar el procesador sobre esa región una vez el ejecutable está en memoria.

Linux: Executable and Linkable Format (ELF)

 

Los binarios de tipo Executable and Linkable Format son los que le envían instrucciones a maquinas con Kernel de Linux para poder ejecutar instrucciones comprensibles para la máquina. A diferencia del PE que tiene el legado MZ, el ELF fue diseñado desde cero en 1999 como parte del estándar System V ABI, por lo que es más limpio conceptualmente.

Se compone de las siguientes cabeceras (Headers):

  • ELF Header: Los primeros bytes de cualquier ELF siempre son 7F 45 4C 46

\x7FELF

Este Header tiene la siguiente estructura:

Valores de e_type :

Valor Constante Significado
0x0000 ET_NONE Tipo desconocido
0x0001 ET_REL Archivo relocatable (.o)
0x0002 ET_EXEC Ejecutable estático
0x0003 ET_DYN Shared object / PIE executable
0x0004 ET_CORE Core dump

Nota: Los ejecutables modernos compilados con PIE (Position Independent Executable) aparecen como ET_DYN, no como ET_EXEC.

Valores de e_machine

Valor Arquitectura
0x03 x86 32-bit
0x3E x86-64
0x28 ARM 32-bit
0xB7 AArch64 (ARM 64-bit)
0xF3 RISC-V
  • Program Header Table:

Cada entrada describe un segmento (segment): muestra una sección del binario que el kernel mapea en la memoria.

Tipos de segmentos más importantes:

Tipo Valor Significado
PT_LOAD 0x1 Segmento que se carga en memoria
PT_DYNAMIC 0x2 Info del dynamic linker (símbolos, libs)
PT_INTERP 0x3 Path al dynamic linker (/lib64/ldlinuxx86-64.so.2)
PT_NOTE 0x4 Metadata del compilador / SO
PT_PHDR 0x6 Ubicación del propio Program Header Table
PT_TLS 0x7 Thread Local Storage
PT_GNU_STACK 0x6474e551 Permisos del stack
PT_GNU_RELRO 0x6474e552 Región de solo lectura post-init
  • Section Header Table:

Cada entrada describe una sección con su contenido especifico:

Secciones más comunes:

Sección Tipo Contenido
.text SHT_PROGBITS Código ejecutable
.data SHT_PROGBITS Variables globales inicializadas
.bss SHT_NOBITS Variables no inicializadas (no ocupa espacio en disco)
.rodata SHT_PROGBITS Datos de solo lectura, strings
.symtab SHT_SYMTAB Tabla de símbolos (static)
.dynsym SHT_DYNSYM Tabla de símbolos dinámicos
.strtab SHT_STRTAB Strings de .symtab
.dynstr SHT_STRTAB Strings de .dynsym
.shstrtab SHT_STRTAB Nombres de todas las secciones
.plt SHT_PROGBITS Procedure Linkage Table
.got SHT_PROGBITS Global Offset Table
.got.plt SHT_PROGBITS GOT entries para PLT
.dynamic SHT_DYNAMIC Info del dynamic linker
Sección Tipo Contenido
.rel.text SHT_REL Relocalizaciones para .text
.rela.text SHT_RELA Relocalizaciones con addend
.init SHT_PROGBITS Código de inicialización pre-main
.fini SHT_PROGBITS Código de finalización post-main
.init_array SHT_INIT_ARRAY Punteros a constructores
.fini_array SHT_FINI_ARRAY Punteros a destructores
.note.gnu.buildid SHT_NOTE Hash único del build
.eh_frame SHT_PROGBITS Info de unwinding para excepciones

MacOS: MachO

Es el formato de binario de Apple. Fue creado originalmente para el microkernel Mach en la Universidad Carnegie Mellon y Apple lo adoptó cuando compró NeXT en 1997. A diferencia del PE y el ELF, Mach-O tiene una característica única: los Fat Binaries / Universal Binaries.

Fat Binaries / Universal Binaries

Antes de ver la estructura dejo una lista de los distintos tipos de paquetes que entran dentro de Fat Binaries:

Un Fat Binary es un paquete que ejecuta varios Mach-O el sistema se encarga de elegir el slice correcto cuando ejecuta el paquete, su estructura es algo así:

Además de esto podemos ver sus distintos Headers para saber que encontraríamos a la hora de analizar uno

  • Mach-O Header:

Valores de cputype :

Valor Arquitectura
0x07 x86 32-bit
Valor Arquitectura
0x01000007 x86_64
0x0C ARM 32-bit
0x0100000C ARM64 (Apple Silicon)

Valores de filetype :

Valor Constante Significado
0x1 MH_OBJECT Archivo objeto (.o)
0x2 MH_EXECUTE Ejecutable estándar
0x6 MH_DYLIB Librería dinámica (.dylib)
0x7 MH_DYLINKER El dynamic linker (dyld)
0x8 MH_BUNDLE Bundle / plugin
0xA MH_DYSYM Stub de librería dinámica
0xB MH_DSYM Archivo de símbolos de debug

Flags importantes:

Flag Significado
MH_PIE PIE habilitado → ASLR activo
MH_TWOLEVEL Namespaces de dos niveles (lib + símbolo)
MH_NO_HEAP_EXECUTION Heap no ejecutable
MH_HAS_TLV_DESCRIPTORS Tiene Thread Local Variables
MH_ALLOW_STACK_EXECUTION Stack ejecutable
  • Load Commands

Todos los Loads Commands comparten esta cabecera base:

El loader de macOS (dyld) lee cada Load Command en orden y ejecuta las instrucciones que contiene.

Los Load Commands más importantes

Comando Significado
LC_SEGMENT_64 Define un segmento a cargar en memoria
LC_DYLD_INFO_ONLY Info de binding y rebase para dyld
LC_SYMTAB Tabla de símbolos estáticos
LC_DYSYMTAB Tabla de símbolos dinámicos
LC_LOAD_DYLIB Librería dinámica a importar
LC_LOAD_WEAK_DYLIB Librería opcional (no falla si no existe)
LC_RPATH Rutas de búsqueda de librerías
LC_MAIN Entry point del binario
LC_UNIXTHREAD Entry point estilo Unix (binarios viejos)
LC_CODE_SIGNATURE Firma de código (obligatoria en iOS)
LC_ENCRYPTION_INFO_64 Info de cifrado (apps de App Store)
LC_VERSION_MIN_MACOSX Versión mínima de macOS requerida
LC_UUID Identificador único del binario
LC_FUNCTION_STARTS Tabla de offsets a funciones
LC_DATA_IN_CODE Datos embebidos en secciones de código
LC_DYLD_CHAINED_FIXUPS Sistema moderno de fixups de dyld

LC_LOAD_DYLIB en detalle:

  • Segments y Sections

En Mach-O hay una jerarquía de dos niveles Segments que contienen

Sections

Segmentos estándar:

Segmento Permisos Contenido
__PAGEZERO Ninguno Página nula, atrapa null pointer dereferences
__TEXT R + X Código ejecutable y datos de solo lectura
__DATA R + W Datos mutables
Segmento Permisos Contenido
__DATA_CONST R (post-init) Datos constantes después de inicialización
__LINKEDIT R Símbolos, strings, firmas, info de dyld
__OBJC R + X Metadata de Objective-C (binarios legacy)

Secciones dentro de __TEXT:

Sección Contenido
__TEXT.__text Código compilado
__TEXT.__stubs Stubs para llamadas a librerías (equiv. PLT)
__TEXT.__stub_helper Helper para lazy binding
__TEXT.__objc_methnames Nombres de métodos Objective-C
__TEXT.__cstring Strings literales de C
__TEXT.__const Constantes
__TEXT.__unwind_info Info de unwinding de excepciones

Secciones dentro de __DATA:

Sección Contenido
__DATA.__got Global Offset Table (non-lazy)
__DATA.__la_symbol_ptr Lazy symbol pointers (equiv. GOT.PLT)
__DATA.__nl_symbol_ptr Non-lazy symbol pointers
__DATA.__cfstring Strings de CoreFoundation
__DATA.__objc_classlist Lista de clases Objective-C
__DATA.__objc_selrefs Referencias a selectores ObjC
__DATA.__bss Variables no inicializadas

Dynamic Linker de Apple (dyld)

El equivalente a ldlinux.so en Linux, pero con diferencias importantes:

Kernel carga un binario → lee LC_LOAD_DYLINKER (/usr/lib/dyld) → dyld procesa LC_DYLD_INFO_ONLY → LC_MAIN (entry point del binario)

dyld procesa LC_DYLD_INFO_ONLY (estructura):

  • Rebase: ajusta punteros internos por ASLR
  • Binding: resuelve símbolos de librerías externas
  • Lazy binding: resuelve símbolos al primer uso

Code Signing – Obligatorio en iOS, importante en macOS

Componente Función
CodeDirectory Hash de cada página del binario
Entitlements Permisos declarados (XML)
CMS Signature Firma criptográfica de Apple
Requirements Restricciones de ejecución

Comparativa de los tres formatos

 

Concepto PE (Windows) ELF (Linux) Mach-O (macOS/iOS)
Magic bytes MZ + PE\0\0 \x7FELF 0xFEEDFACF
Multi-arch NO NO Fat Binary
Entry point AddressOfEntryPoint e_entry LC_MAIN
Imports Import Table / IAT PLT + GOT LC_LOAD_DYLIB + stubs
Loader ntdll.dll ldlinux.so dyld
 Segmentos  Sections Segments +Sections  Segments → Sections
Firma decódigo  Opcional  Opcional  Obligatoria en iOS
 Herramientas PE-bear, CFFExplorer readelf,objdump  otool, nm, jtool2

Créditos: Ezequiel Salinas Bydlowski

Donaciones
STREAMER

Segui Nuestras Redes
  • LinkedIn17.3k+
  • Whatsapp1.7k+
  • TelegramNuevo

Advertisement

Cargando Siguiente Publicación...
Encontranos
Buscar Tendencia
Loading

Signing-in 3 seconds...

Signing-up 3 seconds...

Todos los campos son obligatorios.