Introducción al Buffer Overflow
TAG -> Artículos
Metodología básica para analizar binarios en Linux y determinar si contienen vulnerabilidades, como desbordamientos de buffer y saber como explotarlos.
Paso 1
Saber que tipo de binario es:
Resultado
Es un binario compilado de 32 bit.
Paso 2
Ejecutar el binario para saber que hace (Recordar darle permisos de ejecución).
Resultado
En caso de que no muestre nada, podríamos verificar en que puerto escucha o que hace usando el siguiente comando.
Resultado
strace
nos permite ver más a bajo nivel que esta pasando por detrás, por lo cual aunque el binario no nos muestre que hace o en que puerto escucha, podemos buscar lineas como sin_port=htons(20201),
y sin_addr=inet_addr("0.0.0.0")}, 16) = 0
de tal forma que veremos claramente en que puerto conectarnos para interactuar.
Adicional
Podemos ver que librerías usa con el siguiente comando:
Resultado
Y así ver si alguna librería falta para instalarla.
Paso 3
Para empezar a interactuar con el binario podemos hacer lo siguiente, usando nc
:
Resultado
Podemos ingresar información
Y vemos que no pasa mucho, podríamos ingresar la clásica AAA
.
Y podremos ver el siguiente comportamiento en el binario:
Lo cual no indica que es propenso a Buffer Overflow,
Un buffer overflow ocurre cuando un programa escribe más datos en un buffer (una región de memoria) de lo que este puede manejar, sobrescribiendo áreas de memoria adyacentes. Esto puede llevar a fallos en el programa, corrupción de datos o incluso ejecución de código malicioso.
En este caso, ejecuté el binario secure_software
, el cual está escuchando en el puerto 20201, Luego, me conecté al programa con nc
y envié una cadena muy larga de "A", Al enviar más datos de los que el buffer puede manejar, el programa intentó escribir más allá de su espacio reservado en memoria, lo que causó un segmentation fault. Esto significa que el programa intentó acceder a una dirección de memoria no permitida, provocando que se cierre inesperadamente.
Este tipo de vulnerabilidad es crítica, ya que puede permitir la ejecución de código arbitrario si logramos sobrescribir la dirección de retorno en la pila. Con técnicas como ret2libc o ROP (Return Oriented Programming), podríamos redirigir la ejecución a un shell o código malicioso, lo que haría posible tomar el control del sistema.
En resumen: envié más datos de los que el programa esperaba y rompí su funcionamiento, causando un error de segmentación. Si exploramos más a fondo, podríamos explotar esto para obtener acceso no autorizado al sistema. 🔥
Paso 4
Esta imagen muestra una representación visual de cómo ocurre un buffer overflow en la memoria de la pila (stack).
Explicación de la imagen
Izquierda (Estado normal de la pila)
La pila crece hacia abajo en la memoria.
Se almacenan variables locales (AAAA representa datos de usuario).
Se guarda el EBP (Base Pointer), que ayuda a gestionar el marco de la función.
Se almacena la dirección de retorno (RET), que indica a dónde debe regresar el programa después de ejecutar la función.
Luego, se guardan parámetros de la función.
Derecha (Buffer Overflow)
Se ingresaron demasiados datos (representados por AAAA en rojo).
Los datos sobrescribieron valores importantes en la pila, incluyendo EBP y la dirección de retorno (RET).
Si el atacante controla RET, puede redirigir la ejecución del programa a código malicioso.
Usando gdb
gdb
Para correr el binario:
Volvemos a interactuar con el binario, e ingresando todas las AAA
que habíamos utilizado anteriormente, para ver mas cómodo a nivel de registro que es lo que estamos sobrescribiendo, por lo tanto al hacer esto veremos en gdb
lo siguiente:
Y efectivamente podemos ver como:
El $ebp
esta siendo sobrescrito por AAAA que en hexadecimal equivale a 0x41414141
, también vemos como sobrescribimos el $eip
que es RED; esta variable es la que apunta a la siguiente dirección a la cual el flujo de programa tiene que ir para interpretar las nuevas instrucciones.
Paso 5
Analizar protecciones de seguridad
El comando checksec
se usa para analizar las protecciones de seguridad implementadas en un binario ELF en sistemas Linux. Permite verificar si un ejecutable tiene mitigaciones contra exploits, como DEP, ASLR, RELRO, Canary, PIE, entre otras.
Explicación de las protecciones
RELRO (Relocation Read-Only)
FULL RELRO
: Protege la GOT (Global Offset Table) contra sobrescritura.Partial RELRO
: Protege parcialmente la GOT.No RELRO
: No tiene esta protección.
STACK CANARY
Usa valores aleatorios para detectar sobrescrituras de buffer antes de afectar la ejecución.
No canary found
indica que el binario no tiene esta protección.
NX (No eXecute)
NX enabled
: Previene la ejecución de código en segmentos de memoria marcados como datos (como la pila).NX disabled
: Permite ejecución en la pila y otros segmentos, facilitando exploits.
PIE (Position Independent Executable)
No PIE
: El binario tiene direcciones de memoria fijas, facilitando ataques.PIE enabled
: Hace que las direcciones sean aleatorias en cada ejecución, dificultando exploits.
FORTIFY_SOURCE
Yes
: Protege contra ciertas vulnerabilidades de desbordamiento de buffer.No
: No está habilitada esta protección.
Tenemos 2 manera de usar la herramienta, una desde mismo gdb
y otra desde la shell usando pwn
:
Usando gdb -> gef
Usando pwn
🔴 El binario es altamente vulnerable
Permite ejecución en la pila → Posible shellcode injection.
No tiene Stack Canary → Facilita ataques de buffer overflow.
No tiene NX ni PIE → Hace que la explotación sea más sencilla y predecible.
Tiene segmentos RWX → Permite modificar código y ejecutarlo.
Información de seguridad más detallada:
Protección
Estado
Explicación
Arquitectura
i386-32-little
Binario de 32 bits, usa little-endian. Más predecible para la explotación.
RELRO
Partial RELRO
Protege parcialmente la GOT. Un atacante puede modificar ciertas direcciones en tiempo de ejecución.
Canary
No canary found
/ ✘
No hay Stack Canary, lo que significa que un buffer overflow puede sobrescribir la dirección de retorno sin detección.
NX (No eXecute)
NX unknown - GNU_STACK missing
/ ✘
La pila es ejecutable, permitiendo la inyección de shellcode.
PIE (Position Independent Executable)
No PIE (0x8048000)
/ ✘
El binario no es relocatable, lo que hace que las direcciones sean predecibles, facilitando ataques de memoria.
Stack
Executable
La pila es ejecutable, lo que permite inyectar y ejecutar código malicioso.
RWX Segments
Has RWX segments
Existen segmentos de memoria con permisos de lectura, escritura y ejecución, lo que permite modificar código en ejecución.
Stripped
No
El binario conserva símbolos de depuración, lo que facilita el análisis y explotación.
Paso 6
En este paso lo que aremos es utilizar el comando pattern create
que en GEF genera una cadena de caracteres única de 1024 bytes, esto nos ayudara a identificar con precisión la dirección en la que ocurre la sobreescritura de la dirección de retorno u otros registros críticos.
Generamos este patrón porque, cuando una función vulnerable sobrescribe un registro (como EIP
en 32 bits o RIP
en 64 bits), queremos saber exactamente en qué posición dentro del buffer ocurrió la sobreescritura. Luego, al provocar el crash y examinar el valor del registro afectado, podemos calcular la posición exacta del offset.
Resultado
Ahora lo que aremos es volver a correr el programa escribiendo de nuevo r
en gdb
y como datos mandamos el payload que nos dio pattern create
, lógicamente el programa falla, pero si vemos a ver los registros, podremos notar que el $eip
ahora vale:
De manera más clara podemos usar el comando grep
, para ver que es lo que pasa:
Resultado
La parte roja, es el punto exacto donde sobrescribimos el $eip
, por lo cual todos los caracteres antes de eso, son los necesarios para llegar a ese punto especifico, esto lo podemos contar manualmente o usar el comando pattern
de nuevo:
Resultado
Nos dice que en total son 300 caracteres, que son necesarios introducir en el binario para luego sobrescribir el $eip
, ahora sabiendo esto podemos verificarlo de la siguiente manera:
Resultado
Utilizando Python generamos 300 A luego le sumamos 4 B y por ultimo le agregamos 100 C, veamos que pasa ahora si corremos de nuevo el binario con gdb
e ingresamos estos datos.
Y efectivamente ahora el $eip
vale:
Por lo tanto, ahora estamos seguro de que podemos sobrescribir el $eip
y que son necesarios 300 caracteres para llegar a ese punto, ademas la pila o $esp
vale:
Podemos inspeccionar el $esp
de la siguiente manera:
Resultado
Los 0x43434343
son las C, pero si vemos 4 antes:
Vemos que al principio esta 0x42424242
que son nuestras 4 B, por lo tanto ahora lo que podemos decir es que, el $esp
apunta al comienzo de nuestras C que ingresamos después, entonces lo que podemos hacer ya que el NX esta deshabilitado, es apuntar a las C pero en ves de C ingresar un shellcode (instrucciones a bajo nivel) que se van a interpretar por lo tanto podremos ejecutar esa instrucción en la pila.
Paso 7
En este punto podemos empezar a crear nuestro exploit, generalmente se suele usar la librería pwntools, pero en este caso usaremos socket.
Shellcode
Un shellcode es un código en ensamblador diseñado para ejecutarse en un sistema víctima. Su nombre viene de "shell" porque típicamente abre una shell inversa o bind shell.
Resultado
Lo que usaremos es:
En este caso, el shellcode que generé crea una reverse shell, es decir, se conecta de vuelta a mi máquina atacante en el puerto 1234
.
Opciones explicadas:
-p linux/x86/shell_reverse_tcp
: Usa un payload para Linux de arquitectura x86 que hace una shell reversa.LHOST=172.17.0.1
: La IP de mi máquina atacante.LPORT=1234
: El puerto en el que recibiré la conexión.-b "\x00"
: Evita el byte nulo (\x00
), porque corta cadenas en C y puede romper el exploit.-f py -v shellcode
: Genera el payload en formato Python con la variable llamadashellcode
.
El resultado fue un shellcode de 95 bytes, codificado con x86/shikata_ga_nai
(un encoder polimórfico que lo hace más difícil de detectar).
Otra cosa que debemos tener en cuenta es /proc/sys/kernel/randomize_va_space
lo que contiene debe estar en 0
, si hay otro número tipo 2, cambiar a 0, esto es para que el ASLR (Address Space Layout Randomization), no nos cause errores:
ASLR (Address Space Layout Randomization) es una medida de seguridad que aleatoriza las direcciones de memoria donde se cargan los ejecutables, bibliotecas, pila y heap. Su objetivo es evitar exploits de buffer overflow al hacer impredecibles las direcciones de memoria, complicando ataques como ret2libc o la ejecución de shellcode.
Los valores posibles son:
0
Desactivado (Sin ASLR)
1
Parcial (Randomización de la pila y heap, pero no de las bibliotecas compartidas)
2
Completo (Aleatorización total: heap, stack, mmap, VDSO y librerías)
Cómo desactivar el ASLR:
Temporalmente:
Tenemos este caso, podemos ejecutar el siguiente comando:
Resultado
Para hacer el cambio permanente, debemos agregar esta línea a /etc/sysctl.conf
:
Y aplica los cambios con:
En caso de no existir el archivo lo podemos crear, en mi caso hago esta configuración temporal para tener mejor seguridad y solo lo desactivo cuando hago un laboratorio de Buffer Overflow.
jmp ESP
jmp ESP
es una instrucción en ensamblador x86 que significa: Saltar a la dirección almacenada en el registro ESP.
En términos simples: Ejecuta el código que está en la pila (
stack
) en ese momento.
Cuando explotamos un buffer overflow, muchas veces inyectamos código malicioso (shellcode) en la pila. Pero para ejecutarlo, necesitamos redirigir el flujo de ejecución hacia la dirección de memoria donde se encuentra.
🔥 jmp ESP
es útil porque nos permite redirigir la ejecución justo a la pila, donde en este caso colocamos nuestro shellcode.
Pasos a seguir
Primero -> nasm_shell.rb
Herramienta de Metasploit para convertir instrucciones de ensamblador en opcodes.
Escribimos
jmp ESP
y nos devuelveFFE4
, que es el código en hexadecimal de la instrucción en x86.Esto nos ayuda a identificar
jmp ESP
en un binario vulnerable.
Segundo -> objdump -D secure_software | grep "ff e4"
objdump -D
: Desensambla un binario para ver su código máquina.grep "ff e4"
: Busca la instrucciónjmp ESP
en el binariosecure_software
.Nos muestra una dirección (
0x8049213
), dondejmp ESP
está presente en la memoria.
Algo que debemos tener en cuenta es que en sistemas de 32 bits, las direcciones de memoria se almacenan en orden "little-endian", lo que significa que los bytes menos significativos van primero.
Ejemplo: La dirección
0x8049213
en memoria debe escribirse al revés en nuestro exploit:Dirección original:
0x08049213
Orden little-endian:
"\x13\x92\x04\x08"
🔥 ¿Para qué se usa?
Se sobrescribe EIP (Extended Instruction Pointer) con la dirección de
jmp ESP
para redirigir la ejecución a nuestra shellcode. 🚀
Teniendo todo esto en cuenta nuestro exploit final quedaría de la siguiente manera:
Explotación
Explicación paso a paso:
Offset (
buffer
):Se envían 300 bytes de "A" (
b"A" * 300
) para llenar el buffer y alcanzar la dirección de retorno (EIP
).
Sobrescritura de
EIP
:Se coloca la dirección
0x08049213
(b"\x13\x92\x04\x08"
, en little-endian) enEIP
.Esta dirección corresponde a una instrucción
JMP ESP
, redirigiendo la ejecución al stack.
NOP Sled (
nops
):Se colocan 32 bytes de
\x90
antes del shellcode para evitar problemas de precisión al ejecutar la carga útil.
Shellcode:
Es un código generado con
msfvenom
para ejecutar una acción específica (ej. una shell reversa o ejecutar un comando).
Payload final:
Se construye concatenando:
Envío del exploit:
Se abre una conexión TCP al servicio en el puerto
20201
y se envía el payload.Se usa
shutdown(socket.SHUT_WR)
para asegurar que los datos sean procesados antes de cerrar la conexión.
Ahora nos ponemos en escucha en el puerto 1234 y ejecutamos el exploit con el binario ejecutado.
Y hemos logrado explotar el Buffer Overflow de manera correcta, logramos inyectar una Reverse Shell y ejecutar comando de forma remota, aunque en este caso esta todo local, por si quieren usar un exploit con la librería pwntools les dejo el siguiente ejemplo, lo único que cambia es la forma de conectarse a la victima:
Adicionales -> Para más información
strings
: Extraer cadenas legibles de texto
strings
: Extraer cadenas legibles de textoTe permite ver cualquier cadena legible que esté en el binario. Es útil para encontrar rutas de archivos, nombres de funciones, o posibles mensajes de error.
nm
: Muestra los símbolos en el binario
nm
: Muestra los símbolos en el binarioEsto te ayuda a ver las funciones y variables presentes en el binario, tanto las definidas como las importadas. Te permite identificar funciones importantes como main
o strcpy
.
readelf
: Información detallada del formato ELF
readelf
: Información detallada del formato ELFPuedes usar este comando para ver secciones, encabezados y otros detalles del binario. Te da una idea clara del diseño interno del archivo.
radare2
: Desensamblado, análisis y depuración
radare2
: Desensamblado, análisis y depuraciónRadare2 es una de las herramientas más poderosas y versátiles para el análisis de binarios. Te permite desensamblar, analizar la estructura de funciones, visualizar gráficos de control de flujo y depurar el programa.
Ghidra
: Descompilador y análisis profundo
Ghidra
: Descompilador y análisis profundoGhidra es una suite de ingeniería inversa de código abierto desarrollada por la NSA. Permite descompilar binarios a un pseudocódigo de alto nivel, similar a C, lo que facilita la comprensión del código compilado.
Descompilación a pseudocódigo C: Puedes usar Ghidra para convertir el ensamblador en un código más legible, lo que es muy útil para entender funciones complejas sin tener que leer ensamblador.
Análisis de estructuras de datos: Ghidra puede ayudar a identificar variables, funciones y estructuras que facilitan la comprensión del código binario.
Para empezar con Ghidra:
Abre Ghidra y carga el binario.
Usa la herramienta de descompilación para convertir el código ensamblador a pseudocódigo.
Examina las funciones y busca comportamientos sospechosos (como uso de funciones peligrosas como
strcpy
).
IDA Pro
(o IDA Free): Desensamblador y depurador
IDA Pro
(o IDA Free): Desensamblador y depuradorIDA Pro es otra herramienta muy utilizada en el análisis de binarios. Tiene capacidades similares a Ghidra, con un enfoque en desensamblado y depuración. Es excelente para identificar posibles puntos de vulnerabilidad en el código.
Si quieres observar cómo se comporta el binario en tiempo de ejecución o analizar cómo interactúa con la memoria, puedes usar depuradores.
gdb
: Depurador GNU
gdb
: Depurador GNUGDB es el depurador estándar en Linux y es muy útil para el análisis de binarios a nivel de ensamblador. Puedes ejecutar el programa paso a paso, establecer puntos de interrupción y observar el estado de la memoria, registros y variables.
En GDB, puedes observar cómo los valores se mueven a través de la pila y los registros, lo que te da una pista de si el binario es vulnerable a un desbordamiento de búfer u otros tipos de explotación.
pwndbg
: Extensión de GDB para explotación
pwndbg
: Extensión de GDB para explotaciónpwndbg
es una extensión para GDB orientada a la explotación de binarios. Te proporciona una vista clara de la memoria, registros y otras estructuras importantes para facilitar la explotación de vulnerabilidades como desbordamientos de búfer.
Para instalarlo y usarlo:
Luego, abre tu binario en GDB como siempre, y ahora tendrás todas las herramientas de pwndbg
disponibles para facilitar tu análisis.
gef
(GDB Enhanced Features): Depurador mejorado
gef
(GDB Enhanced Features): Depurador mejoradoEl que usamos en este Laboratorio.
gef
es otra extensión para GDB que mejora la experiencia de depuración al agregar funciones avanzadas, como un mejor análisis de la pila, visualización de memoria y más.
Para instalarlo y usarlo:
Usa gef
para analizar el estado de la memoria y ver exactamente dónde podría estar ocurriendo el desbordamiento.
Last updated