GhostRace y los Speculative Concurrent Use-After-Free Exploits basados en Spectre_v1
Hoy os voy a hablar de GhostRace, una nueva forma de explotar sistemas uniendo los ataques de Spectre y las vulnerabilidades de Use-After-Free que son tan comunes en muchos exploits como hemos visto en el pasado. Es un artículo académico muy interesante, y de muy bajo nivel, pero merece la pena que entiendas qué es lo que hace, y qué es lo que significa así que vamos a repasar un poco estas dos vulnerabilidades y cómo las utiliza GhostRace.
En el año 2018 se descubrieron los ataques de Spectre y Meltdown, donde se podría acceder a valores de memoria prohibidos, aprovechándose de la ejecución especulativa de la CPU en las arquitecturas avanzadas que tenemos hoy en día día en los microprocesadores. La idea es que la CPU, teniendo puertas lógicas en paralelo, y registros en la caché, el microprocesador puede ejecutar al mismo tiempo todas las ramas de un if.
Spectre & Spectre-PHT (Spectre v1)
Supongamos que hacemos una instrucción que sea if (X<A1+A2) then C=B1+B2 else C=B3+B4. En un entorno especulativo, para calcular C, el micro calcula en paralelo la condición y las dos ramas. Es decir, calcula X<A1+A2, C=B1+B2 y C=B3+B4, y luego, cuando tenga la respuesta a la condición, ya elige, pero eso hace que el tiempo de ejecución total del programa sea menor, porque se ha paralelizado una instrucción.
Esto, Spectre y Meltdown demostraron que podría ser un problema, si alguien ponía en una de las ramas una operación que podría ser un acceso ilegal dependiendo del valor de la condición. Por ejemplo, si tenemos una instrucción como:
if (x<100) then C=array1[X] else C=array2[X]
y la CPU hace la ejecución especulativa de las dos ramas, entonces puede ser que esté haciendo un acceso a array1[9000] cuando array1 podría tener solo 100 posiciones. Este dato, sin embargo, estará en uno de los registros de la caché, que podría ser filtrado directamente desde el registro de la caché.
Una explotación concreta de esta técnica se publicó en el año 2019, llamada Spectre-PHT o llamada como Spectre v1, que se hace con un ejemplo como este que podéis ver en la imagen siguiente. Debido a las protecciones contra Spectre y Meltdown, el ataque de Spectre-PHT, llamado también Spectre v1, hace una operación como la siguiente, en la que se hace una comparación del valor x con la longitud de un array - controlado por el atacante -, para luego ejecutar el acceso a una posición de memoria concreta.
Si durante varias peticiones x está dentro de los límites de array1 y el atacante se asegura que no se ha cacheado ningún valor de array2 lo que va a suceder es que la CPU va a predecir que el valor de esa comparación podría ser True, y por tanto comenzará la ejecución especulativa de la parte del "then", metiendo en la caché del microprocesador la información de una determinada zona de memoria, asumiendo que la comparación previa va a ser True.
Cuando el atacante introduce un valor X fuera de los límites, se supone que no debería ejecutarse la parte del "then" de la comparación porque está fuera de los límites, pero en el registro de la caché estará cargado el valor de la zona de la memoria que se había especulado - y que está fuera de la memoria accesible en array1[x] - asumiendo previamente que el IF podría ser True. Esto quiere decir que en la caché está guardado el valor de array2[array1[x]*0x100] siendo array1[x] un valor cargador de dirección de memoria prohibida que se ha cargado en la caché, junto con el valor de array2[array1[x]*0x100].
Ahora, recorriendo el rango de posibles registros de memoria de array[2], se podrá saber cuál es valor de un byte que se ha leakeado, porque estará cacheado en un registro del microprocesador, y por tanto el acceso a ese array2[y] que se obtendrá será mucho menor que el acceso a las demás posiciones porque está cacheado en una zona de muy baja latencia de acceso, y por tanto se podría saber el valor que había en array1[X] (el valor prohibido), haciendo una sencilla ecuación que es y/0x100 = valor que hay en la dirección de memoria prohibida más allá de los límites de array1 que está en array1[x].
Esta técnica permite, haciendo algo como lo visto en la Figura 4, tener un SRC "Speculative Race Condition" que nos da un Information Disclosure o "Speculative Information Disclosure" para er más exactos del valor de una posición de memoria prohibida. Esa posición de memoria puede ser un puntero, con información importante para generar un exploit, o una zona de memoria que nos ancle para poder saltar el KASLR (Kernel Address Space Layaout Randomization) que aleatoriza las direcciones de carga de programas en el Kernel. Si quieres conocer más de cómo funcionan las protecciones de memoria en los sistemas operativos, te recomiendo los libros de Linux Exploiting y de Máxima Seguridad en Windows.
Use-After-Free
Las vulnerabilidades de Use-After-Free son muy comunes en muchos programas cuando no gestionan de forma segura la memoria dinámica. La idea es tan sencilla como que un programa cargue en el Heap dun segmento de código de un programa apuntado por un puntero. Después, esa zona de memoria del Heap se elimina, pero el puntero no se elimina haciéndole apuntar a NULL.
Si esto es así, un atacante podría cargar en esa zona de memoria un programa controlado por él, y conseguir que el puntero estuviera apuntando a su programa, por lo que podría después se ejecutado haciendo que el código apuntado por ese puntero consiga la ejecución del contador de programa.
Figura 7: Linux Exploiting |
Cargar el programa a ejecutar en una zona de memoria donde se ha cargado código de la víctima previamente, ayuda a saltarse el DEP (Data Execution Prevention) al ser zonas de memoria marcadas para ejecución de código y no datos. Esta es una técnica clásica de construcción de explotis, y en el libro de Linux Exploiting se estudia en un capítulo, como podéis ver aquí en el índice.
Evitar esto, es tan sencillo como asegurase de eliminar todos los punteros al heap de ejecución y no dejarlos colgados apuntando a zonas de memoria que han sido liberadas en el heap, y para eso hay protecciones en los kernels de los sistemas operativos, con el objetivo de que no pase esto.
GhostRace
Llegados a este punto, ya podemos mezclar las dos técnicas de ataque, es decir, los ataques de Spectrev1 para saltarse mediante SRC (Speculative Race Conditions) y SID (Speculative Information Disclosures) las protecciones del kernel contra acceso concurrente sincronizados para regiones críticas como mutex o spinlocks que no aplican en ejecución especulativa, y ejecutar ataques Use-After-Free, en lo que llaman en el paper de GhostRace los Speculative Concurrent Use-After-Free (SCUAF).
Al final, para hacer un exploit hay que lograr que conseguir encontrar un punto del código que sea una vulnerabilidad de UAF, para conocer en qué posición de memoria hay que cargar el segmento de código con el exploit. Para evitar ello, el kernel tiene protecciones con los punteros que evitan que se pueda acceder al valor de esos punteros en el kernel como Kaslr, pero con una explotación especulativa con Spectre_v1 se puede conseguir una condición de carrera que consiga acceder a ese valor, tal y como se ha visto antes.
Para conseguir eso, en el artículo hacen ejemplos de construcciones de Speculative Race Conditions basadas en Spectre_v1 para lograr ataques de Speculative Information Disclosure y Speculative Hijacking Control Flow, que son piezas necesarias para hacer un exploit SCUAF funcional, que pueda ejecutar código arbitrario incluso con las protecciones contra UAF en el kernel del sistema operativo.
Conclusiones
El paper de GhostRace hace todas las primitivas en ramas especulativas, por lo que todas dependen de generar condiciones de carrera en ramas especulativas (SRC Speculative Race Conditions) con zonas de memoria prohibidas para acceder a la información almacenada en ellas. Resolver este problema implica poner protecciones extras en el microprocesador, o en el kernel para evitar las explotaciones de Spectrev1 que se pueden hacer hoy en día en las CPUs afectadas, pero esto implica una perdida masiva de rendimiento, por lo que no hay planes en Linux de hacer estos cambios. Un bonito panorama para los fabricantes de microprocesadores, programadores de hipervisores, cloud software de base y kernel de sistemas operativos.
¡Saludos Malignos!
Autor: Chema Alonso (Contactar con Chema Alonso)
No hay comentarios:
Publicar un comentario