Protecciones contra buffer overflows en la práctica
Hace tiempo que deseo aprender a realizar ataques buffer overflow, pero por falta de tiempo (y a veces por no sentarme decidido a hacerlo) fue quedando relegado en la lista de cosas a aprender. Con aprender me refiero a realizar los ataques en la práctica, la teoría ya la conozco desde hace años! Pueden encontrar un par de artículos que escribí el año pasado sobre esto: Buffer overflow, un asesino en serie y Evitando que un programa explote, técnicas para detectar buffer overflows.
En fin, la cosa es que probando ejemplos del excelente libro "Gray Hat Hacking" y del tutorial Smashing The Stack For Fun And Profit me encontré con varias trabas. No solo tenía que entender lo que estaba tratando de hacer, sino entender el por qué ni siquiera copiando y pegando los ejemplos andaban!
Fue así que investigando encontré varias protecciones que el compilador GCC y el kernel colocan en los programas y me pareció interesante comentarlas.


Arquitectura 64 bits

El primer obstáculo que encuentra un atacante que intenta realizar un bof es la arquitectura. La arquitectura de 64bits agregó varias funcionalidades que le permiten al sistema operativo protegerse mejor. Principalmente las mejoras en cuanto a seguridad son:

- Bit No eXecution (NX), conocido como eXecute Disabled (XD) en Intel, o Enhanced Virus Protection en AMD. Este bit permite marcar las páginas como no ejecutables, con lo cual el sistema operativo ahora puede utilizar la arquitectura para setear páginas de memoria que contienen datos como no ejecutables, evitando así la ejecución de código inyectado en el stack o en el heap debido a un overflow.
La idea de evitar la ejecución en áreas de datos viene desde hace tiempo, pero al no tener soporte en la arquitectura x86 (que sólo permitía marcar una página como readonly/execute con el mismo bit) debía emularse por software. Un ejemplo de ello es el proyecto PaX.

El mapeo de qué secciones son ejecutables se pueden setear en el formato binario ELF. Allí el compilador coloca los headers del programa e indica a través de permisos RWE qué secciones son ejecutables.
Pueden ver los headers de cualquier programa con los comandos objdump o readelf. Por ejemplo, pueden utilizar readelf de la siguiente forma:
$ readelf -l programa
Para marcar que el stack es no ejecutable, se incorporó el header GNU_STACK, el cual es revisado por el loader para setear los permisos correctos. Si este header tiene los flags RW, la pila no tiene ejecución.
Este header lo incorpora GCC desde la versión 4.3 y por default cuando compilamos un programa veremos que el stack no tiene ejecución.
Como existen programas antiguos o que requieren especialmente ejecución en el stack, es posible marcar el stack como ejecutable. Si cuando compilamos un programa agregamos el argumento "-z execstack", el header GNU_STACK indicará los permisos RWE, incorporando así la ejecución.
También es posible habilitar la ejecución del stack utilizando el programa execstack. Con el parámetro "-s" indicamos que el programa tiene stack ejecutable y con "-c" eliminamos la ejecución.
Algo a tener en cuenta es que si no existe el header GNU_STACK, el loader asume que el programa necesita ejecución en el stack, esto se debe al deseo de proveer compatibilidad con programas viejos.

- Mayor espacio de randomización de direcciones (ASLR). En la arquitectura de 32 bits sólo se podían utilizar 8 bits para para la randomización de la dirección virtuales (teóricamente eran 20, pero por limitaciones en el SO, sólo quedaban 8), mientras que en la nueva arquitectura de 64 es posible utilizar 28 (teóricamente 36).
Esto complica aún más el tipo de ataques "return to libc", los cuales ya eran complejos al utilizar ASLR. Con arquitecturas de 32 bits era factible averiguar por fuerza bruta en qué direcciones se cargaban las librerías debido a la utilización de sólo 8 bits.


ASLR

En el artículo que escribí el año pasado les hablé sobre ASLR y de qué trata. En la sección anterior les comenté que en la arquitectura de 64bits se mejoró considerablemente esta randomización. Como dije, en la arquitectura de 32bits también existe esta protección y el kernel de linux la incorporó a partir de la versión 2.6.12 por default. Si quieren deshabilitar esta funcionalidad, simplemente ejecuten:
echo "0" > /proc/sys/kernel/randomize_va_space

Stack Smashting Protection

Esta técnica también la expliqué en el artículo de protección contra bof. En la práctica GCC utiliza el llamado canario para detectar cuándo ocurrió un overflow que pudo sobreescribir la dirección de retorno. Esto se hace incorporando un valor conocido (canario) entre la dirección de retorno y las variables locales de la función. De esta forma, si ocurre un overflow en alguna de las variables locales, deberá sobreescribir el canario antes de poder sobreescribir la dir de retorno. Este cambio en el canario será detectado por la función de protección y terminará el programa.
Por default en muchas distribuciones (por ejemplo debian) GCC viene configurado para incorporar este canario en los programas que compile, pero en caso de no hacerlo, el programador puede utilizar el parámetro "-fstack-protector" para agregarlo. Si no se desea utilizar y sabemos que el compilador lo agregaría por default, se puede utilizar el parámetro "-fno-stack-protector".


Buffer & Format String Vulnerability

Cuando se describe los bof hay algo que salta a la vista. El problema surge por no checkear cuántos datos se van a copiar en una dada variable de tamaño fijo, lo cual permite copiar por ejemplo 500 bytes en un arreglo de 300. Si las funciones que hacen las asignaciones comprobaran los tamaños, estaríamos a salvo. Existen alternativas seguras para funciones de la librería libc que sí comprueban los tamaños de buffers, así que por qué no usarlas?
En GCC existe una funcionalidad para fortificar el código, la cual se activa con el flag "-D_FORTIFY_SOURCE=2". Este flag le indica a GCC que checkee en tiempo de compilación los tamaños de los buffers y cambie llamadas a funciones que no limitan el tamaño de los buffers con funciones que si lo hagan. También hace que GCC agregue checkeos en tiempo de ejecución para los tamaños de los buffers y las regiones de memoria.
Algo a tener en cuenta con este flag es que sólo se puede utilizar con el parámetro "-O2", es decir, utilizando optimización de código.

Un ataque que no describí en los artículos sobre buffer overflows es el de Format String Attack. Este ataque se aprovecha de las funciones que utilizan formato, como printf, fprintf, sprintf y snprintf. La vulnerabilidad aparece cuando no se proveen tantos parámetros como se especifica en el formato (ej: printf("%s=%s", "mapeo")), o cuando se le permite al usuario ingresar el string de formato (ej: printf(argv[1]), y desde consola ejecutar ./programa "ataque %s %s"). Las funciones de formato intentan leer de la pila tantos parámetros como los especificados en el string de formato, así que si no se asigna la cantidad de parámetros correcta, podríamos leer datos de memoria arbitraria, o escribir en ella (utilizando %n), y, en ocasiones, lograr root!
En fin, si bien es fácil de evitar, el tema es bastante interesante, así que si quieren, pueden leer el paper Format String Attacks de Tim Newsham.
A lo que quería llegar con todo esto, es que la opción D_FORTIFY_SOURCE también evita ataques que escribiendo en la memoria, gracias al formateador %n, permitirían ejecutar código. Lo que hace simplemente el compilador es bloquear todos los format strings que contengan "%n".

Si la distribución de linux que utilizan tiene configurado GCC para usar D_FORTIFY_SOURCE por default, pueden desactivarlo usando el parámetro "-D_FORTIFY_SOURCE=0".


Referencias

- Buffer overflows on linux-x86-64
- x86-64 buffer overflow exploits and the borrowed code chunks exploitation technique
- ::: GCC PROTECTIONS :::
- Ubuntu Wiki CompilerFlags
- debian Wiki Hardening
- The GNU Stack Quickstart
- Gray Hat Hacking - The Ethical Hacker's Handbook
- Format String Attacks

1 comentarios:

Anónimo dijo...

Excelente, hace unos días leí este artículo https://zarza.com/gray-hat-hacking-los-de-la-etica-ambigua/ espero les sea de utilidad a sus lectores.

Publicar un comentario