1. Introducción
El análisis de binarios y la revisión de código son dos competencias muy útiles en ciberseguridad. Permiten estudiar cómo funciona un programa, identificar comportamientos sospechosos, detectar vulnerabilidades y comprender qué operaciones realiza un ejecutable cuando no se dispone de su código fuente.
En un ejercicio de Red Team, estas técnicas pueden ayudar a investigar una aplicación, analizar herramientas encontradas en un equipo comprometido durante una simulación autorizada, identificar mecanismos de protección o comprender cómo interactúa un programa con el sistema operativo.
En un pentest, el análisis de código permite revisar aplicaciones propias o autorizadas para localizar errores de validación, gestión de memoria, autenticación, tratamiento de datos y utilización de funciones sensibles.
Para comenzar, conviene dominar tres áreas:
- Sistemas binarios y hexadecimal: comprender cómo se representan los datos en memoria y en los archivos.
- Análisis estático y dinámico: estudiar un programa sin ejecutarlo y observar su comportamiento durante la ejecución.
- Análisis de código e ingeniería inversa: interpretar código fuente, instrucciones de ensamblador y estructuras de un ejecutable.
No es necesario empezar por técnicas avanzadas. Con unos conocimientos básicos de C, Python, Linux y arquitectura de computadores es posible construir un laboratorio y realizar los primeros análisis.
2. Fundamentos de los sistemas binarios
Los ordenadores representan la información mediante bits, cuyos valores son 0 y 1. Un conjunto de ocho bits forma un byte.
Por ejemplo:
| Decimal | Binario | Hexadecimal |
|---|---|---|
| 0 | 00000000 | 0x00 |
| 1 | 00000001 | 0x01 |
| 10 | 00001010 | 0x0A |
| 65 | 01000001 | 0x41 |
| 127 | 01111111 | 0x7F |
| 255 | 11111111 | 0xFF |
El sistema hexadecimal se utiliza constantemente en análisis de binarios porque permite representar cada cuatro bits mediante un único dígito hexadecimal.
Por ejemplo:
Binario: 01001000 01101111 01101100 01100001
Hexadecimal: 48 6f 6c 61
ASCII: H o l a
Esta secuencia representa la palabra Hola en ASCII.
¿Por qué es importante en ciberseguridad?
El análisis hexadecimal permite inspeccionar archivos, identificar cabeceras, reconocer cadenas de texto, interpretar datos de protocolos y estudiar estructuras internas de ejecutables.
También es útil para analizar volcados de memoria, registros de red y archivos potencialmente maliciosos.
Practicar con Python
Podemos convertir números y cadenas sin necesidad de instalar herramientas adicionales.
numero = 65
print("Decimal:", numero)
print("Binario:", bin(numero))
print("Hexadecimal:", hex(numero))
texto = "Hola"
datos = texto.encode("utf-8")
print("Bytes:", datos)
print("Hexadecimal:", datos.hex())
El resultado mostrará las representaciones del número 65 y de la cadena Hola.
3. Comprender la estructura de un archivo binario
Un archivo ejecutable no es simplemente una colección de instrucciones. Normalmente contiene cabeceras, secciones, información sobre bibliotecas, datos y, en algunos casos, símbolos que facilitan el análisis.
Dos formatos importantes son:
- ELF: utilizado habitualmente en Linux y otros sistemas Unix.
- PE: utilizado por los ejecutables y las bibliotecas de Windows, como los archivos
.exey.dll.
También conviene conocer los formatos Mach-O de macOS.
Un analista debe aprender a identificar el tipo de archivo antes de intentar interpretarlo.
Herramientas básicas en Linux
El comando file identifica el formato probable de un archivo:
file programa
Para consultar su tamaño:
ls -lh programa
Para calcular su hash SHA-256:
sha256sum programa
En macOS se puede utilizar:
shasum -a 256 programa
El hash sirve para identificar una versión concreta de un archivo y comprobar si dos muestras son idénticas. No demuestra, por sí solo, que el archivo sea seguro o malicioso.
Inspeccionar los bytes
Con xxd podemos visualizar el contenido hexadecimal:
xxd programa | head -n 20
La salida muestra los desplazamientos, los bytes y una representación textual aproximada.
Esta inspección resulta útil para reconocer cabeceras y estructuras conocidas. Por ejemplo, un ejecutable ELF suele comenzar con los bytes 7f 45 4c 46, mientras que un archivo PE suele comenzar con 4d 5a (MZ) y contiene posteriormente una cabecera PE.
4. Diferenciar análisis estático y dinámico
Antes de utilizar herramientas avanzadas, conviene distinguir dos formas de examinar un programa.
Análisis estático
Consiste en estudiar el archivo sin ejecutarlo.
Se pueden revisar:
- El formato y la arquitectura.
- Las cadenas de texto incluidas.
- Las funciones y los símbolos disponibles.
- Las bibliotecas utilizadas.
- Las instrucciones de ensamblador.
- El flujo de control y las llamadas a funciones.
- Los posibles errores de programación.
La principal ventaja es que no es necesario ejecutar el archivo investigado. Sin embargo, un análisis estático puede no revelar todo el comportamiento de un programa, especialmente si utiliza ofuscación o genera código durante la ejecución.
Análisis dinámico
Consiste en observar un programa mientras se ejecuta en un entorno controlado.
Se puede investigar:
- Qué procesos crea.
- Qué archivos abre o modifica.
- Qué conexiones de red intenta establecer.
- Qué bibliotecas carga.
- Qué funciones ejecuta.
- Qué errores aparecen.
- Cómo cambia su comportamiento ante diferentes entradas.
En Linux, herramientas como strace permiten observar determinadas llamadas al sistema. En Windows se utilizan, entre otras, Process Monitor y herramientas de depuración.
Importante: el análisis dinámico debe realizarse en un laboratorio aislado cuando exista la posibilidad de que el archivo sea malicioso. No conviene ejecutar muestras desconocidas en el equipo de trabajo habitual.
5. Análisis de cadenas de texto con strings
Una de las primeras operaciones que puede realizar un analista es buscar cadenas de texto legibles dentro de un ejecutable.
La herramienta strings permite localizar secuencias de caracteres imprimibles:
strings programa
Para mostrar únicamente cadenas de al menos ocho caracteres:
strings -n 8 programa
Podrían aparecer nombres de funciones, mensajes de error, rutas, direcciones URL, nombres de bibliotecas o mensajes de depuración.
Por ejemplo, un programa podría contener cadenas como:
Configuracion cargada
Error al abrir el archivo
https://example.com/api
Estas cadenas ofrecen pistas sobre el funcionamiento del programa, pero deben interpretarse con precaución.
Una dirección URL no demuestra que el ejecutable se conecte a ella. Una cadena aparentemente sospechosa tampoco demuestra que exista una vulnerabilidad.
Además, las cadenas pueden estar cifradas, comprimidas, construidas durante la ejecución o eliminadas del binario.
6. Consultar las funciones y las bibliotecas utilizadas
Los ejecutables pueden utilizar bibliotecas externas para realizar operaciones de entrada y salida, gestionar memoria, comunicarse con el sistema operativo o implementar funcionalidades criptográficas.
En Linux, readelf permite inspeccionar las estructuras de un archivo ELF:
readelf -h programa
El comando anterior muestra información de la cabecera, como la arquitectura y el tipo de archivo.
Para consultar los símbolos:
readelf -s programa
Para consultar las dependencias dinámicas:
readelf -d programa
También se puede utilizar:
objdump -d programa
Este último comando muestra instrucciones de ensamblador cuando el archivo es compatible con el formato que analiza.
Hay que tener en cuenta que un binario compilado sin símbolos de depuración puede ser más difícil de interpretar. Además, no todas las funciones utilizadas por un programa aparecen necesariamente con nombres reconocibles.
En Windows, herramientas como PE-bear y Detect It Easy pueden ayudar a estudiar las cabeceras PE, las secciones, la arquitectura y determinados indicadores del compilador o del empaquetado.
7. Introducción al ensamblador
Cuando se compila un programa, el código fuente se transforma en instrucciones que el procesador puede ejecutar.
Por ejemplo, una operación aritmética sencilla podría expresarse en C así:
int sumar(int a, int b) {
return a + b;
}
En una arquitectura x86-64, una versión posible de su implementación podría contener instrucciones similares a estas:
mov eax, edi
add eax, esi
ret
La secuencia es ilustrativa: las instrucciones concretas dependen de la arquitectura, el compilador, las opciones de optimización y la convención de llamadas.
En este ejemplo:
movcopia un valor.addrealiza una suma.retdevuelve el control al llamador.
No es necesario memorizar todo el ensamblador desde el primer día. Es más útil aprender a reconocer las instrucciones habituales, los registros, los saltos condicionales, las llamadas a funciones y el uso de la pila.
Para comenzar, conviene estudiar x86-64 si se trabaja principalmente con ordenadores convencionales, aunque ARM64 también es importante por su presencia en dispositivos móviles y equipos Apple.
8. Crear un programa de laboratorio y analizarlo
La mejor manera de aprender consiste en compilar un programa propio y comparar su código fuente con el ejecutable resultante.
Crea un archivo llamado laboratorio.c:
#include <stdio.h>
int sumar(int a, int b) {
return a + b;
}
int main(void) {
int resultado = sumar(10, 20);
printf("Resultado: %d\n", resultado);
return 0;
}
Este programa no realiza operaciones sensibles: únicamente suma dos números y muestra el resultado.
Compilar en Linux o macOS
Si tienes instalado GCC o Clang, puedes compilarlo con:
gcc -Wall -Wextra -O0 -g laboratorio.c -o laboratorio
En macOS, si no tienes GCC instalado, normalmente puedes utilizar el compilador Clang incluido en las herramientas de desarrollo de Apple:
clang -Wall -Wextra -O0 -g laboratorio.c -o laboratorio
La opción -O0 evita las optimizaciones habituales y -g añade información de depuración. Estas opciones facilitan los primeros ejercicios.
Ejecuta el programa:
./laboratorio
La salida será:
Resultado: 30
Inspeccionar el ejecutable
Identifica el archivo:
file laboratorio
Calcula su hash:
shasum -a 256 laboratorio
Busca cadenas de texto:
strings laboratorio
En Linux, si se trata de un ejecutable ELF, consulta sus cabeceras:
readelf -h laboratorio
Por último, inspecciona las instrucciones:
objdump -d laboratorio
En macOS, puedes usar otool -hv laboratorio para consultar información de cabecera y otool -tvV laboratorio para inspeccionar las instrucciones cuando sean compatibles con la arquitectura y el formato del archivo.
El objetivo no es encontrar una vulnerabilidad en este pequeño programa, sino aprender a relacionar el código fuente con las estructuras e instrucciones que aparecen en el ejecutable.
9. Introducción a Ghidra y la ingeniería inversa
Ghidra es una plataforma de análisis de software que permite estudiar ejecutables mediante desensamblado, descompilación y otras funciones de ingeniería inversa.
Su utilidad principal consiste en facilitar la comprensión de programas cuando no se dispone del código fuente original.
Entre sus capacidades se encuentran:
- Identificar funciones.
- Examinar referencias entre funciones.
- Analizar instrucciones de ensamblador.
- Generar una representación aproximada en pseudocódigo.
- Inspeccionar cadenas y referencias cruzadas.
- Estudiar el flujo de control.
- Renombrar funciones y variables para documentar el análisis.
Un procedimiento inicial sería:
- Crear un proyecto de laboratorio.
- Importar el ejecutable compilado anteriormente.
- Permitir que Ghidra analice el archivo.
- Localizar la función
main, si conserva ese nombre. - Buscar la función que realiza la suma.
- Comparar las instrucciones con el código C original.
- Revisar el pseudocódigo generado y anotar las diferencias.
La descompilación no reconstruye necesariamente el código fuente exacto. Los nombres originales de las variables pueden haberse perdido y las optimizaciones pueden transformar considerablemente la estructura del programa.
Por ello, el pseudocódigo debe considerarse una ayuda para el análisis, no una representación infalible del programa original.
10. Qué revisar durante un pentest
El análisis de binarios y de código puede ayudar a investigar diferentes clases de problemas de seguridad.
| Área | Qué revisar |
|---|---|
| Validación de entradas | Datos que llegan a funciones sin comprobarse correctamente. |
| Gestión de memoria | Accesos fuera de límites, uso de memoria liberada y errores relacionados. |
| Autenticación | Comprobaciones de identidad incompletas o realizadas únicamente en el cliente. |
| Criptografía | Uso incorrecto de algoritmos, claves o generadores aleatorios. |
| Secretos | Credenciales o claves incorporadas accidentalmente al ejecutable. |
| Dependencias | Bibliotecas desactualizadas o con vulnerabilidades conocidas. |
| Sistema de archivos | Rutas inseguras, permisos excesivos y operaciones sensibles. |
| Comunicaciones | Falta de validación de certificados o tratamiento inseguro de datos recibidos. |
| Flujo de control | Condiciones de seguridad que pueden omitirse por errores lógicos. |
Encontrar una cadena con aspecto de contraseña no significa que sea una credencial válida. Del mismo modo, observar una función potencialmente peligrosa no demuestra que pueda explotarse.
El analista debe relacionar cada hallazgo con las entradas, las condiciones de ejecución, los permisos necesarios y el impacto potencial.
11. Herramientas recomendadas para comenzar
Estas herramientas cubren las principales etapas de un análisis inicial.
file: identificación del formato del archivo.xxd: inspección hexadecimal.strings: búsqueda de cadenas legibles.sha256sumyshasum: cálculo de hashes.readelfyobjdump: análisis de ejecutables ELF.- Ghidra: desensamblado, descompilación e ingeniería inversa.
- Detect It Easy: identificación de formatos y posibles empaquetadores.
- PE-bear: inspección de ejecutables PE de Windows.
- Process Monitor: observación de actividad de procesos en Windows.
strace: observación de llamadas al sistema en Linux.
No es necesario instalar todas estas herramientas al mismo tiempo. Para empezar, basta con un compilador, una terminal y un programa de prueba. Después se puede incorporar Ghidra para profundizar en el análisis.