flagfunction overwrite PicoCTF (Hard)

Contexto de la maquina

Trayectoria function overwrite

Descripción

Este reto consiste en analizar un binario vulnerable escrito en C que utiliza punteros a funciones. El objetivo es identificar una vulnerabilidad de memoria que permita modificar el flujo de ejecución del programa y provocar que se ejecute una función distinta a la prevista originalmente.

El programa permite introducir datos desde la entrada estándar y posteriormente realiza operaciones sobre un array de enteros. Debido a una validación incorrecta de índices, es posible acceder a posiciones de memoria fuera de los límites del array, lo que permite modificar un puntero a función global.

Objetivo del reto

Explotar la vulnerabilidad del programa para redirigir la ejecución hacia una función alternativa que permita revelar la flag.

Tipo de reto

  • Binario

  • Linux

  • Explotación de memoria

  • Punteros a funciones

Habilidades y técnicas evaluadas

  • Análisis de código fuente en C

  • Comprensión de punteros a funciones

  • Explotación de out-of-bounds array access

  • Manipulación de memoria

  • Uso de herramientas de reversing (objdump, gdb)

  • Comprensión del layout de memoria de variables globales

Análisis de vulnerabilidades

Despliegue del CTF

En la propia pagina buscaremos el CTF, dentro veremos dos archivos los cuales nos podremos descargar llamados vuln y vuln.c.

El objetivo de estos CTFs es encontrar la flag final.

Analisis del binario C#

Si leemos la descripcion del reto...

La descripción básicamente nos indica que debemos analizar un programa que utiliza function pointers en C, lo cual ya nos da una pista importante, ya que los punteros a funciones suelen ser un objetivo común en vulnerabilidades de memoria.

También nos indican que podemos descargar:

  • El binario ya compilado

  • El código fuente del programa

Antes de comenzar con el análisis vamos a crear una flag de prueba local para poder probar el exploit en nuestro entorno.

De esta forma, si conseguimos explotar el programa correctamente, podremos comprobar que se lee el contenido del archivo flag.txt.

Análisis del código

Ahora vamos a analizar el código proporcionado.

vuln.c

Vemos en el codigo que tiene una pequeña vulnerabilidad en esta parte de aqui:

Es un desbordamiento de array que permite modificar el puntero de función check. La validación es débil porque solo verifica que num1 sea menor que 10, pero no verifica que sea mayor o igual a 0.

Si ponemos num1 negativo, podemos acceder a posiciones de memoria antes del array fun. Esto nos permite modificar el puntero de función check que está antes en memoria.

Diseño de memoria

En memoria, asumiendo que fun y check están cerca en la sección de datos:

Si hacemos num1 = -1, estaríamos modificando fun[-1], que sería justo donde está almacenado el puntero check. Sin embargo, necesitamos verificar el offset exacto.

Obteniendo direcciones

Primero obtenemos las direcciones de las funciones con objdump:

Respuesta:

Calculamos la diferencia:

Queremos que check (que apunta a hard_checker) pase a apuntar a easy_checker. Necesitamos restarle 314 a la dirección actual.

Encontrando el offset correcto con GDB

Usamos GDB para encontrar la posición exacta de check relativa a fun:

La diferencia es de 64 bytes. Como es un array de enteros de 32 bits (4 bytes cada uno), el offset en índices es:

Por lo tanto, check está en fun[-16], no en fun[-1].

Análisis del assembly

Desensamblamos vuln para confirmar:

Respuesta:

Observamos líneas clave:

  • 0x080495f8 <+136>: mov 0x80(%ebx,%eax,4),%ecx → acceso a fun en ebx+0x80

  • 0x08049614 <+164>: mov 0x40(%ebx),%esi → carga de check en ebx+0x40

Confirmamos la diferencia de 64 bytes (0x80 - 0x40 = 0x40 = 64).

Calculando el payload

Necesitamos:

  • num1 = -16 (offset correcto para llegar a check)

  • num2 = diferencia entre direcciones = -314

Cuando se ejecute fun[-16] += -314, estaremos restando 314 a la dirección de hard_checker, obteniendo así la dirección de easy_checker.

Verificando la modificación

Ponemos un breakpoint justo después de la modificación y antes de la llamada:

Respuesta:

Ahora check apunta a easy_checker (0x080492fc)

Obteniendo la flag (local)

Ahora necesitamos que easy_checker nos dé la flag. Esta función requiere que la suma de los caracteres del story sea exactamente 1337.

Calculamos un string que sume 1337:

  • Carácter 'z' = ASCII 122

  • 10 'z' = 1220

  • Necesitamos 117 más → carácter 'u' (ASCII 117)

  • String final: "zzzzzzzzzzu" (10 z's + 1 u)

Ejecutamos el exploit:

Respuesta:

Aquí ocurren dos cosas importantes:

  1. -16 permite escribir fuera de los límites del array fun, accediendo a memoria anterior al array.

  2. Esa escritura modifica el puntero a función check, haciendo que ahora apunte a easy_checker.

El segundo valor (-314) es el valor que se suma en la operación:

lo que termina ajustando el contenido de memoria hasta que el puntero a función coincide con la dirección de easy_checker.

De esta forma conseguimos redirigir el flujo de ejecución del programa hacia la función que nos interesa.

Explotación en el reto remoto

Una vez comprobado que el exploit funciona correctamente en local, podemos utilizar exactamente los mismos valores contra el servicio remoto que proporciona el reto.

Nos conectamos con nc:

Respuesta:

Con esto veremos que hemos obtenido correctamente la flag del servidor remoto, explotando la vulnerabilidad del programa.

En resumen, el exploit consiste en:

  1. Aprovechar la falta de validación de índices negativos en el array fun.

  2. Utilizar ese acceso fuera de límites para sobrescribir el puntero a función check.

  3. Redirigir la ejecución hacia easy_checker.

  4. Enviar un story cuya suma ASCII sea 1337 para que la función imprima la flag.

flag.txt

Last updated