Fernando Cárdenas Fernández
22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
Tabla de contenido
1 Objetivo. ................................................................................................................................ 3
2 El programa Vulnerable......................................................................................................... 3
2.1 Ejecución del Programa................................................................................................. 3
2.2 ¡Crash! ........................................................................................................................... 5
3 Explotando la vulnerabilidad del programa. ......................................................................... 5
3.1 Introducción de datos binarios. .................................................................................... 5
3.2 Usando gdb ................................................................................................................... 6
4 Preguntas .............................................................................................................................. 8
Fernando Cárdenas Fernández 2 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
1 Objetivo.
El objetivo de esta práctica es conseguir un mayor conocimiento sobre cómo funciona un
buffer overflow, ver en detalle el proceso para explotar dicha vulnerabilidad, y proceder a la
corrección del código para evitar este problema. El proyecto se puede ejecutar sobre cualquier
distribución Linux, pero para obtener un resultado repetible en cada ejecución se recomienda
utilizar la máquina virtual que se proporciona junto a esta práctica (Usuario: debian, Password:
debian). Durante la ejecución del taller se irán realizando una serie de cuestiones que deben
contestarse por parte del alumno. El programa Vulnerable.
Para la ejecución de esta práctica se suministran tres ficheros:
• deseos-alt.c: código fuente de la aplicación vulnerable.
• runbin.sh: shell script usada para llamar a la aplicación vulnerable y que garantiza
que el Shell desde donde se llama a la aplicación no interpreta los caracteres
especiales que se pasen como argumentos.
• Makefile: fichero para compilar la aplicación.
Estos ficheros pueden descargárselos de la plataforma de teleformación de la universidad de
Sevilla.
Cree un directorio de trabajo con su código de usuario, y copie en su interior estos ficheros.
Para compilarlo ejecute la siguiente sentencia:
make
Verifique que se ha ejecutado correctamente la compilación y se ha generado el ejecutable
deseos-alt en el directorio de trabajo.
1.1 Ejecución del Programa.
El programa lee datos de la entrada estándar (stdin), esto es, del teclado y escribe en la
salida estándar (stdout), la pantalla. El programa se ejecuta escribiendo ./deseos-alt en
una ventana de terminal. Cuando lo hacemos, obtenemos el siguiente mensaje de bienvenida:
En este punto, el programa está esperando que el usuario elija una de las dos opciones que
propone. Si escribimos 1 nos permite “ver los deseos” que se han escrito hasta ese momento,
y si escribimos 2 nos permite “añadir un deseo”. Supongamos que escribimos 1 y pulsamos
Return:
Fernando Cárdenas Fernández 3 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
Observar que nos responde “No deseos” indicando que aún no hay ningún deseo almacenado
en el sistema, y nos vuelve a mostrar el mensaje de bienvenida. Ahora vamos a escoger la
opción 2 y vamos a intentar escribir un deseo. Esto es lo que pasa:
Ahora el programa está esperando que el usuario escriba algo. Supongamos que escribimos
“Dormir es importante” y pulsamos Return. A continuación nos vuelve a salir el mensaje de
bienvenida. Si ahora escribimos 1 de nuevo, obtenemos lo siguiente:
Podemos continuar añadiendo más deseos, escribiendo 2. Por ejemplo, si repetimos la
secuencia anterior, y escribimos “El ejercicio es util”. Obtenemos:
Podemos continuar haciendo esto tanto tiempo como queramos. Podemos finalizar el
programa escribiendo Control-D.
Fernando Cárdenas Fernández 4 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
1.2 ¡Crash!
Este programa es vulnerable a un buffer overflow. Es fácil ver donde hay un problema, si
escribimos otro valor diferente de 1 ó 2. Por ejemplo, si escribimos 343.
El programa tiene (al menos) dos vulnerabilidades. Una es la demostrada anteriormente, pero
hay otra. Esta práctica le guiará por los pasos que hay que dar para provocarla, y a
continuación deberá responder algunas cuestiones para demostrar que ha hecho todos los
pasos.
2 Explotando la vulnerabilidad del programa.
A continuación vamos a mostrar algunas herramientas que son necesarias para explotar las
vulnerabilidades del programa.
2.1 Introducción de datos binarios.
Para poder introducir los datos binarios que nos permitan explotar las vulnerabilidades del
programa usaremos el script runbin.sh también proporcionado con el código de la práctica.
Con este script podemos escribir cadenas hexadecimales. Por ejemplo.
Los caracteres \x41\x41 representan dos bytes, definidos en formato hexadecimal. 41 en
hexadecimal es 65 en decimal, que corresponde al carácter de la tabla ASCII ‘A’. Como
resultado, cuando elegimos la opción de “Ver Deseos”, el programa imprime AA. Si escribimos
algo como \x07 sería el código ASCII 7, que se corresponde con el sonido de una campana. Por
lo que cuando lo imprimimos se debería oír el sonido.
Para explotar la vulnerabilidad del programa tenemos que introducir secuencias de datos
binarios que contienen direcciones, que son palabras de 32 bits. La arquitectura x86 es “little-
endian”, que significa que los bytes de la palabra de la dirección se almacenan primero el
menos significativo y el último el más significativo. Esto significa que la dirección hexadecimal
0xabcdef00, se introduciría byte a byte en orden inverso: \x00\xef\xcd\xab
Fernando Cárdenas Fernández 5 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
EJERCICIO1: Compruebe el resultado que se obtiene cuando se llama directamente a
./deseos-alt y se le pasa los mismos caracteres de entrada.
EJERCICIO2: el script runbin.sh tiene el siguiente código:
while read -r line; do echo -e $line; done | ./deseos-alt
explique el funcionamiento de esta línea.
2.2 Usando gdb
Para conseguir la información necesaria para explotar las vulnerabilidades del programa
tenemos que averiguar cómo se almacena el mismo en memoria. Podemos averiguar esa
información usando la herramienta de debug gdb. Podemos conectar gdb con la instancia del
programa deseos-alt que se esté ejecutando en ese momento, y entonces utilizarlo para
imprimir información sobre el estado del programa, y ejecutar paso a paso el programa.
Para conectar gdb con deseos-alt, primero debemos invocar ./runbin.sh y entonces, en una
ventana de terminal separada, en el mismo directorio de trabajo donde se encuentre el
ejecutable, y como el usuario root de la máquina, ejecutar la siguiente línea:
La opción –p de gdb que se conecte con el proceso que tiene como identificador el que se
pasa como argumento. El comando pgrep deseos-alt busca en la tabla de procesos el
identificador que tiene el proceso que se está ejecutando en ese momento con el nombre
deseos-alt (antes de ejecutar este comando tenemos que asegurarnos que no hay más de
un proceso denominado deseos-alt en el equipo en el que estamos trabajando, ya que
puede que nos conectemos a otro programa diferente de con el que estamos trabajando. Si es
el caso debe finalizar todos los procesos utilizando el comando pkill deseos-alt).
Una vez conectados al proceso, podemos comenzar usando gdb examinando su estado y
controlando el programa.
Fernando Cárdenas Fernández 6 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
Esto salida nos indica que gdb está conectado con la ejecución de deseos-alt. En este punto
el programa se encuentra en pausa, podemos empezar a introducir comandos de gdb. Por
ejemplo:
Lo que hemos hecho es fijar un punto de ruptura en la línea 100 de la aplicación deseos-alt.
A continuación continuamos con la ejecución de la aplicación con el comando cont. Si en la
ventana donde se está ejecutando el aplicativo (que ahora si responderá a nuestras peticiones)
escogemos la opción 2 y pulsamos Return, esto causa que se ejecute el código de la línea 100 y
entonces el programa se volverá a detener. En la ventana donde estamos ejecutando gdb
veremos que el programa se ha detenido de nuevo y que podemos volver a ejecutar comandos
del depurador:
Como vemos, vamos ejecutando el programa línea a línea usando la orden next. A
continuación pintamos el valor de la variable s con el comando print, y nos muestra el valor
que elegimos en la aplicación (la opción 2). Imprimimos también la dirección de memoria
Fernando Cárdenas Fernández 7 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
donde se almacena la variable r. Por último continuamos con la ejecución del programa
escribiendo de nuevo la orden cont.
Cuando hayamos hecho todo el trabajo que necesitemos con gdb, salimos de la herramienta
escribiendo la orden quit.
Estos son los comandos básicos de gdb que vamos a necesitar usar: fijar puntos de ruptura con
break, ejecutar paso a paso la aplicación con next, e imprimir valores con print. Si desea
una guía de referencia sobre esta herramienta los manuales están disponibles en Internet. Una
guía de referencia rápida la puede obtener en el siguiente enlace:
http://www.cs.umd.edu/class/spring2014/cmsc414-0201/downloads/gdb-refcard.pdf
3 Preguntas
Nos encontramos ahora preparados para desarrollar un exploit para esta aplicación.
El primer paso consiste en identificar las vulnerabilidades que tiene el código. Para ello abra el
código fuente de la aplicación disponible en el fichero deseos-alt.c. Utilice alguno de los
editores de texto disponible: gedit, emacs o vim. Analice el código disponible y responda a
los siguientes ejercicios:
EJERCICIO3: Realice un resumen de cómo funciona la aplicación: qué se hace en la función
principal del programa, que hace cada una de las funciones que están definidas en el
programa, cuando son llamadas cada una de ellas. Describa también los diferentes punteros
que se definen en el programa y qué utilidad tienen dentro de la aplicación.
EJERCICIO4: En el programa hay una overflow de la pila. ¿Cuál es el nombre de la variable
que referencia el buffer afectado?
EJERCICIO5: Considere el buffer que ha identificado en el ejercicio anterior. ¿Cuál es la línea
de código que puede provocar el desbordamiento del buffer?.
EJERCICIO6: Hay otra vulnerabilidad, no relacionada con la anterior, que afecta a una tabla,
que no está en la pila, y que se puede indexar fuera de sus límites (lo cual, entendiéndolo en
sentido amplio, es una clase de buffer overflow). ¿Qué variable contiene esta tabla o
buffer?.
EJERCICIO7: Considere el buffer que acaba de identificar. ¿qué línea de código puede
provocar ese desbordamiento del buffer?.
Ahora use gdb para examinar el programa cuando se está ejecutando y conteste a los
siguientes ejercicios. Estos ejercicios consisten básicamente en ir ejecutando el programa e ir
obteniendo la información que necesitamos para construir el exploit que nos permite ejecutar
la segunda vulnerabilidad que hemos identificado de overflow de una tabla.
EJERCICIO8: Determine la dirección de los siguientes elementos (la dirección que ocupan en
memoria, no el valor que contienen) que se definen en el programa (muestra las direcciones
en hexadecimal):
Fernando Cárdenas Fernández 8 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
1. buf
2. ptrs
3. write_secret
4. p (la variable local definida dentro del main)
EJERCICIO9: ¿Qué entrada debemos proporcionar al programa de forma que prtr[s] lea (y
entonces intente ejecutar) el contenido de la variable local p en vez del puntero a función
almacenado en el buffer apuntado por ptrs? Puede determinar la respuesta realizando una
pequeña operación matemática con las direcciones que ha obtenido en el ejercicio anterior
(tenga cuidado que tiene en cuenta el tamaño del puntero cuando haga la aritmética de
punteros). Si lo calcula correctamente, terminará ejecutando la función pat_on_back. Dé la
respuesta como un entero sin signo.
El siguiente paso es aprovecharse de como en C se tratan las cadenas terminadas en \0. La
variable buf que se utilizar para leer la entrada del usuario cuando selecciona una de las dos
opciones del menú puede tener mucho más que sólo una representación numérica. La función
atoi convierte la cadena de caracteres guardada en buf en un valor entero. ¿Cómo sabe C
donde acaba la cadena? Cuando encuentra el carácter nulo \0 (\x00 en hexadecimal).
¿Qué ocurre si rellenamos buf con la cadena de la siguiente forma:
buf = “234\x00Otros Datos adicionales\x00”
(En el caso de nuestro programa el último \x00 es el que se incluye en la línea 99). En este
caso. La función atoi va a considerar solo la primera parte hasta el primer \x00, 234,
mientras que el resto, “Otros Datos adicionales” se quedará almacenado en memoria. Y esta es
la parte interesante, ya que estos datos adicionales pueden ser instrucciones ejecutables de
alguna otra función que queremos llamar. Como en el caso anterior, podemos proporcionar un
valor numérico que nos permita referenciar la dirección de memoria de otra función, y esa
posición puede referenciar a la dirección de memoria de “Otros Datos adicionales” que puede
ser un código inyectado.
Vamos a intentar inyectar datos en la posición 65 de buf.
EJERCICIO10: ¿Qué valor debemos escribir de forma que prts[s] lea (e intente ejecutar)
comenzando desde el byte número 65 en buf, esto es buf[64]? Responda a la cuestión
suministrando un valor entero.
EJERCICIO11: La cadena que nos vas a permitir explotar la vulnerabilidad de inyección de
código comentada anteriormente tendrá la siguiente forma:
NUMEJER10\x00CARACTERESDERELLENO\xEE\xEE\xEE\xEE
Donde:
- NUMEJER10: el valor entero determinado en el Ejercicio10.
- CARACTERESDERELLENO: cualquier cadena de caracteres que va a actuar de
relleno para llegar hasta la posición 65 de buf.
Fernando Cárdenas Fernández 9 22/04/2023
Master en Seguridad TIC (10ª Edición): Curso 2022/2023
- \xEE\xEE\xEE\xEE: la dirección de la función a la que queremos saltar (en el caso
de este ejercicio la función write_secret). Tener en cuenta que la codificación de
la dirección debe ser Little endian.
Consideremos ahora la otra vulnerabilidad que habíamos identificado de desbordamiento
de buffer en la pila. Vamos a provocar un overflow en la variable wis en la pila. Podemos
hacer esto eligiendo la opción 2 para llamar a la función put_wisdom, y entonces escribir
suficientes byte para sobreescribir la dirección de retorno de la función, reemplazándola
con la dirección de la función write_secret.
Para obtener respuesta a lo que se va a solicitar, es conveniente que usemos los siguientes
dos comandos de gdb:
- backtrace: imprime la pila de llamadas de funciones en el punto de ejecución
donde se encuentra el programa en el momento de la ejecución.
- x: comando el cual realiza un volcado hexadecimal de los bytes que se le indiquen
a partir de una determinada dirección. Por ejemplo si escribimos:
x/48xw $esp
se imprimirían 48 palabras (por la w), en formato hexadecimal (por la x),
comenzando en la dirección de memoria almacenada en el registro $esp.
EJERCICIO12: ¿Cuántos bytes necesitas introducir antes de introducir la dirección de la
función write_secret?
EJERCICIO13: Modifique el programa de forma que las vulnerabilidades que hemos
explotado se solucionen. Para ello utilice las funciones seguros que se han visto en la
introducción teórica. A continuación verifique que todas las operaciones que hemos
realizado con anterioridad ya no conllevan ningún problema de seguridad.
Fernando Cárdenas Fernández 10 22/04/2023