CREANDO UN LABORATORIO SIN MITIGACIONES
En este artículo crearemos un laboratorio sin mitigaciones frente a este tipo de ataques.
Última actualización
En este artículo crearemos un laboratorio sin mitigaciones frente a este tipo de ataques.
Última actualización
Artículo copiado de la Fundación Sadosky escrito por Teresa Alberto.
A la hora de introducirse en el tema, es recomendable deshabilitar las técnicas de mitigación presentes por defecto en los sistemas modernos. A medida que se avanza con ataques más sofisticados es posible habilitar una a una estas mitigaciones para poner en práctica cómo subvertirlas y trabajar en el marco de escenarios más “reales”. Es por ello que se recomienda compilar los programas vulnerables con ciertos flags específicos que harán la tarea más fácil y se recomiendan ciertas configuraciones extras en el sistema.
Como el desbordamiento de búfer es una vulnerabilidad de larga data, los sistemas operativos actuales desarrollaron prevenciones para este tipo de ataques, entre otras la randomización del espacio de direcciones y la implementación de canaries de protección de la pila. Por eso para introducir estos ataques es necesario deshabilitar una serie de configuraciones para, en una segunda instancia, explicar cómo superarlas. A continuación se hace un repaso de las mitigaciones y se usa el script checksec
para detectar cuando se encuentran habilitadas o no.
El ASLR (“Address Space Layout Randomization” o mapa de espacio de direcciones aleatorio) es una tecnología diseñada para impedir la explotación de ciertas vulnerabilidades. El ASLR vuelve aleatorias las direcciones de memoria de un proceso y por ende, dficulta la tarea de adivinar direcciones de la pila. Esta información es necesaria para la lectura y reescritura de sectores claves de la pila, para la inyección y ejecución de código malicioso y en el caso de técnicas de explotación más avanzadas, para la creación de ROP gadgets.
Es posible temporalmente deshabilitar esta protección desde la terminal configurando el espacio de memoria como “no random”.
Es una política en relación a la memoria que indica que nunca se debe tener una página de memoria escribible y ejecutable al mismo tiempo (representado como write XOR execute o W^X). Es por ello que se marcan los sectores escribibles dentro de las direcciones de un proceso como no ejecutables. La pila entonces deviene en un área de memoria no ejecutable. Los ataques iniciales consistiran en parte en inyectar un código arbitrario en la pila (escribir en la pila) y ejecutarlo (ejecutar en la pila). Para ser capaces de escribir y ejecutar en la pila, es necesario inicialmente deshabilitar esta mitigación.
Por defecto gcc
compila con esta mitigación habilitada. Para deshabilitar protección de ejecución en la pila, al compilar con gcc
agregamos el flag -z execstack
.
Para prevenir técnicas de explotación que sobreescriben la Global Offset Table, se obliga al linker a resolver todas las funciones linkeadas dinámicamente al iniciarse el programa y, una vez actualizada la tabla GOT, volverla de sólo lectura de modo que no pueda ser sobreescrita de manera arbitraria. Es por ello que en exploits que involucran la GOT es necesario que la mitigación RELRO (en inglés RELocation Read-Only) se encuentre deshabilitada o habilitada parcialmente con la variante “RELRO parcial”. Esta última opción es usada por defecto en la mayoría de distribuciones de Linux modernas, por lo que no es necesaria ninguna configuración extra.
No obstante en casos donde un ataque utilice la sección DTORS será necesario deshabilitarlo completamente con el flag -Wl,-z,norelro
.
En esta técnica de mitigación el compilador inserta una marca o canario en la pila cuando detecta una función que accede a variables locales por referencia. En estos casos inmediatamente después de almacenar la dirección de retorno, se almacena a su vez un valor (el canario o la cookie) entre la variable local (datos) y la dirección de retorno (información de control). Frente a un ataque de reescritura de la dirección de retorno, el valor del canario se verá modificado levantando alertas de que se produjo un desbordamiento de búfer. Y se detiene la ejecución del programa antes de que la función retorne a la dirección vulnerada por ejemplo con una excepción de violación de segmento. En una primera instancia, de esta manera se evitaría una reescritura de la dirección de retorno de una función y la consecuente ejecución de código malicioso.
Dado que inicialmente se trabajará con técnicas de corrupción de la dirección de retorno en memoria es necesario deshabilitar esta protección de la pila compilando con gcc
con el flag -fno-stack-protector
.
-m32
para compilar ejecutables de 32 bits.
-mpreferred-stack-boundary=2
para el alineamiento de la pila.
-ggdb
habilita información de debugging
-no-pie
para evitar compilar el binario como Position Independent Executable, sino cambiaría en cada ejecución la ubicación del código en el espacio de memoria virtual.
Inicialmente se compilan con los flags mencionados previamente:
No se utiliza el flag -Wl,-z,norelro
para deshabilitar RELRO
, dado que con una configuración de RELRO parcial
es posible avanzar sin problemas.
En muchos exploits es necesario hacer cálculos sobre direcciones de la pila (la dirección de variables, punteros, etc). Como gdb
agrega variables de entorno que se almacenan en la pila y la modifican, los cálculos sobre las direcciones obtenidas en gdb
no resultan útiles para explotar el binario por fuera de un entorno de debugging.
Por ejemplo, para ver las diferencias en variables de entorno:
Existen varias maneras de alinear la memoria dentro y fuera de gdb
.
Para limpiar las variables de entorno es posible ejecutar el programa con el wrapper env -i
. Como ejemplo podemos ver como se limpian las variables de entorno al ejecutar env -i /usr/bin/printenv
: Printenv
No obstante, vemos que gdb
igualmente agrega variables de entorno:
env -i
Una posible solución es usar el wrapper env -i
para limpiar las variables de entorno tanto usando gdb
como a la hora de ejecutar el programa. Dentro de gdb
con show env
sabemos qué variables de entorno se mantienen y optar por sumarlas en la ejecución o eliminarlas de gdb
con unset env
.
Por ejemplo, ejecutamos printenv
con las variables de entorno que incluye gdb
que observamos recién (usando siempre el path
completo para ejecutar el programa como hace el debugger):
O debugeamos printenv
con env -i
y eliminamos dentro de gbd
las variables de entorno que se mantienen con unset env
:
Y si lo ejecutamos:
Stack 1 Probamos la primer solución con el programa Stack 1 y observamos cómo las direcciones de buf
y cookie
son idénticas al ejecutar y debuggear el programa:
Y sino, de otra manera, probamos sin variables de entorno en gdb
y vemos como logramos las mismas direcciones para las variables:
Si es necesario enviarle un input por stdin al programa vulnerable (que cuenta por ejemplo con una función como gets()
) esta solución también funciona:
Y también cuendo el programa vulnerable espera que se le pasen argumentos por ejemplo con una función como strcpy(buf,argv[1])
):
Para facilitar esta configuración es posible editar el archivo .gdbinit
:
Otra solución es utilizar un script como Fixenv de Hellman que fija las direcciones del stack. Es posible ver el funcionamiento del script de Hellman (usando ./r.sh
como wrapper) con el programa Stack 1.
A diferencia de la solución anterior con env -i
cuando el programa vulnerable toma un input por stdin, este script no resuelve las diferencias entre las direcciones con y sin gdb.
En cambio funciona correctamente cuando es necesario pasarle argumentos al programa vulnerable:
Si bien en este caso nos ocupamos de compilar nosotrxs mismxs los binarios de los programas vulnerables, cuando se trabaja en estos temas es una buena práctica aislar en una máquina virtual la ejecución de binarios no conocidos.
[1]. Reece, Alex. (02 de mayo de 2013). Introduction to format string exploits [Post]. Code Arcana. Recuperado de http://codearcana.com/posts/2013/05/02/introduction-to-format-string-exploits.html