5 de enero de 2018

Meltdown y Spectre: Graves vulnerabilidades en los procesadores de los principales fabricantes

Tres vulnerabilidades graves han sido descubiertas en las versiones modernas de los procesadores Intel, AMD y ARM, que permiten en el peor de los casos leer memoria reservada privada del kernel desde código no privilegiado




Solution: Replace CPU hardware (Solución: Reemplace el procesador). Así rezaba la nota en CERT/CC sobre estas tres vulnerabilidades. Afortunadamente, actualmente anuncia como solución actualizar los diversos sistemas operativos que suelen correr sobre los procesadores afectados. Y esto es básicamente lo que deben hacer los usuarios de a pie, mantener sus sistemas operativos actualizados, así como un antivirus. No nos cansamos de repetir esto, como podéis observar.

En resumen, todo procesador moderno está afectado en mayor o menor medida. Ya hemos visto que para estas vulnerabilidades la solución recomendada es actualizar el sistema operativo. Lo cierto es que los usuarios de a pie tampoco pueden hacer mucho más. Está claro que no vas a tirar el procesador, o cambiarlo por otro modelo moderno probablemente igual de vulnerable. (¿o quizás sí y cambiar tu Intel por un AMD menos vulnerable como veremos ahora?).

En cualquier caso, los parches que pueden ofrecer los sistemas operativos estarán básicamente restringidos a evitar la explotación de una de las tres vulnerabilidades, la conocida como Meltdown (CVE-2017-5754). Las otras dos, bajo el nombre Spectre (CVE-2017-5753 y CVE-2017-5715) no se pueden mitigar completamente a pesar de los parches que salgan, aunque se puede dificultar la facilidad con que se explotan, o detectar programas conocidos que intenten explotarlas. Estas dos vulnerabilidades bajo el nombre Spectre tienen las mismas consecuencias, por lo que se puede explicar lo básico de forma conjunta.

La incidencia de estas vulnerabilidades afecta principalmente a los servidores que corren múltiples procesos de múltiples usuarios, ya que es posible acceder a la información de otros usuarios a través de estas vulnerabilidades. Para el usuario de a pie, con mantener actualizado el sistema operativo y un antivirus, es suficiente. También se ha hablado de disminución en el rendimiento provocada por los parches para estas vulnerabilidades, pero lo cierto es que para el usuario de a pie es despreciable.

Vamos a hacer una comparativa básica de Meltdown y Spectre:

  • Productos afectados: Meltdown afecta a casi todo procesador Intel producido después de 1995 (y a algunos ARM's). Spectre afecta prácticamente a todos los procesadores modernos de Intel, AMD y ARM, en mayor o menor medida.
  • Impacto: Meltdown permite leer memoria privada del kernel desde código no privilegiado. Spectre también permite leer memoria, pero del mismo proceso o de otro distinto.
  • Dificultad de explotación: Meltdown es relativamente fácil de explotar, y la parte principal del exploit es básicamente universal. Spectre requiere conocimiento profundo del código de los procesos a explotar, y por tanto los exploits son personalizados por objetivo.
  • Solución: Para Meltdown vale con que los sistemas operativos cambien su manera de trabajar con la memoria. Para Spectre no hay parches definitivos (¿tirar el procesador a la basura?), pero lo básico es actualizar el sistema operativo y un antivirus, como dijimos previamente.

Ya para los más avanzados, sigue una explicación técnica de las vulnerabilidades. Sin embargo, hasta para la mayoría de éstos es necesario hacer un repaso básico de algunos conceptos de arquitectura de computadores. Los conceptos se simplifican lo máximo posible:


  • Los procesadores modernos no siempre leen directamente de la memoria, sino que primero consultan una memoria caché, que se encarga de almacenar las entradas de memoria que son accedidas con más frecuencia. Es una memoria bastante limitada en tamaño, cuyas entradas más antiguas y menos accedidas se van eliminando en favor de entradas nuevas. La ventaja que tiene esto es que si una entrada está en la memoria caché, ahorras la lectura de la memoria principal, que es más lenta.

  • La ejecución fuera de orden permite cambiar el orden de las instrucciones a ejecutar en un programa, o incluso ejecutarlas simultáneamente (siempre conservando la lógica original). Esto es algo que hace el procesador por su cuenta para mejorar la velocidad de ejecución. El procesador lleva un registro de la ejecución, y revierte los efectos de instrucciones que se adelantaron si resulta que una instrucción anterior provocó algún error o se saltó a otro sitio y esas instrucciones adelantadas no debieron ejecutarse.

  • A veces, la ejecución fuera de orden llega a una instrucción de salto cuya dirección de destino depende de instrucciones previas a la instruccion de salto que no han sido ejecutadas todavía (precisamente por lo que hace la ejecución fuera de orden). El procesador entonces realiza la llamada ejecución especulativa, que consiste en predecir la dirección de salto, saltar y seguir la ejecución. De forma similar a lo que ocurre en la ejecución fuera de orden, esto lo hace llevando un registro de los cambios realizados tras la predicción, para poder volver al estado original si la predicción fue fallida. Si no, se ha ahorrado tiempo.

  • Para la ejecución especulativa, es necesario predecir de alguna forma la dirección del salto. Para eso existen diversas implementaciones de predicción de saltos en los procesadores, generalmente basados en el mismo principio de direcciones de salto más frecuentes desde cierta dirección almacenada.

Espero que no haya sido muy duro :)

A continuación la descripción técnica de las dos primeras vulnerabilidades (la tercera es una variación de la segunda fácil de comprender si se entiende ésta):


Meltdown (CVE-2017-5754)

Una de las cosas principales que permite la existencia de estas vulnerabilidades es lo mal que están implementadas la ejecución fuera de orden, la especulativa, la predicción de saltos... Desde el punto de vista de la seguridad. Por ejemplo, en la ejecución especulativa, cuando la predicción de salto falló y en realidad se ejecutó código que no debía ejecutarse, recordamos que se deshacen todos los cambios pertinentes... Bueno, no todos. La memoria caché no vuelve a su estado original.

Es decir, que la ejecución de ese código realmente sí deja cierta huella. Dejando de lado la seguridad, que cambie un poco la caché puede afectar ligeramente a la velocidad de acceso a las entradas, que se hayan perdido entradas de la caché que valían... Pero poco más... El problema viene cuando estos cambios en la caché que no deberían haber ocurrido permiten revelar información de una forma algo compleja, como veremos ahora...

Recordamos que Meltdown se usa para leer memoria privada del kernel desde código no privilegiado. Un proceso no privilegiado en los sistemas operativos modernos tiene en su espacio de direcciones un trozo reservado para el kernel, aunque no vaya a usarse directamente (por razones que no vienen al caso). Por tanto, intentar acceder a esta zona de la memoria genera una excepción y se aborta la ejecución del programa.

El problema principal que permite la existencia de Meltdown es que los procesadores afectados no manejan correctamente estos accesos ilegales cuando se ejecutan instrucciones fuera de orden. Esto permite que se ejecute la instrucción que accede ilegalmente al trozo del kernel, pero para cuando el procesador se prepara para manejar la excepción generada por el acceso ilegal, ya se habrán ejecutado unas cuantas instrucciones por delante de la instrucción que accedió ilegalmente.

En principio esto no debería ocasionar problema alguno, ya que como explicamos, en la ejecución fuera de orden se revierte todo... Excepto el estado de la memoria caché. El siguiente trozo de código nos explica cómo se puede aprovechar esto:


Extraído de http://bit.ly/2qwoB6d

'rcx' es la dirección del kernel que queremos leer, y 'rbx' un array que usaremos para recuperar indirectamente el valor de 'al', el byte leído del kernel (ahora veremos cómo). Ignorando las líneas 3, 5 y 6 para la explicación básica, en la línea 4 leemos el byte del kernel. Y en la línea 7 accedemos a una dirección de memoria que dependerá del valor del byte leído del kernel (en la línea 5 se transforma un poco, y recordemos que el registro 'al' es un subconjunto del registro 'rax').

Básicamente, acabamos de provocar que cambie la caché almacenando la dirección a la que se ha accedido en la línea 7 dependiendo del byte leído del kernel. Esto lo permite la ejecución fuera de orden, ya que deja ejecutar varias instrucciones por delante antes de lanzar la excepción por acceso ilegal, y que la caché no se restaura cuando la excepción ocurre.

A partir de aquí, es posible darse cuenta de qué dirección cambió en la caché de forma indirecta. Si previamente se preparó la caché para que no contuviese ninguna entrada asociada al array 'rbx', tras ejecutar el código de la imagen la caché contendrá una única entrada de ese array. Si intentamos acceder una a una a todos las posibles direcciones a las que se pudo acceder en la línea 7, notaremos que una de las direcciones se accede muy rápidamente... Porque ya estaba en la caché, gracias a la línea 7.

Sabiendo la dirección que está en la caché, se puede recuperar el byte leído del kernel (ya que esa direccion dependía de ese byte, y de una forma fácil de deducir). Si se hace esto para cada uno de los bytes del trozo del kernel, podremos leer efectivamente todo la memoria reservada al kernel. Una última cosa a tener en cuenta es que tendremos que manejar la excepción de alguna forma, para que no aborte el programa. Esto se hace de forma legal sin problema alguno.



Spectre (CVE-2017-5753)

Esta vulnerabilidad se basa en engañar al sistema de predicción de saltos, para que en una estructura condicional tipo 'if' salte a la rama equivocada y la ejecución especulativa deje huellas en la caché, como pasa en Meltdown. El código explotable por esta vulnerabilidad tiene que seguir un patrón específico. Por tanto, habrá que buscar en el proceso objetivo un trozo de código con ciertas características. A continuación, un ejemplo simplificado del tipo de código que buscamos:


Extraído de http://bit.ly/2qwoDuR


En este trozo de código, 'x' debe ser una variable controlada por el atacante (una entrada externa a un programa, como un argumento o un valor de un paquete de red). Básicamente la idea es buscar un valor de 'x' que haya que la carga de 'array[x]' apunte a la dirección de memoria que queremos extraer (¿una contraseña en memoria?). La comprobación del 'if' es una comprobación clásica de seguridad, para evitar que se acceda a memoria fuera de rango en la instrucción de dentro. Y seguramente 'x' sea demasiado grande para pasar la comprobación. La gracia está en que podemos engañar al sistema de predicción de saltos para que ejecute especulativamente lo de dentro, dejando huella en la caché dependiendo del valor de 'x', al estilo de Meltdown.

Tiene que darse una serie de condiciones para que se pueda explotar, y todavía no se ha explicado cómo es posible engañar al sistema de predicción de saltos o extraer información de la caché, especialmente estando en procesos totalmente distintos. Lo básico será conseguir que se ejecute ese código en el proceso víctima, está claro. Para esto, tenemos que realizar alguna acción que lleve a ello. Por ejemplo, si ese código se encarga de manejar peticiones HTTP, podemos mandarle una.

Respecto al tema de engañar al sistema de predicción de saltos... Resulta que el BTB (Branch Target Buffer), la tabla que se usa para llevar un registro de los saltos más comunes para permitir la ejecución especulativa, es compartida entre procesos. No sólo eso, ni siquiera almacena toda la dirección de salto, sino simplemente los 20 bits inferiores. Esto permite que desde un proceso puedas afectar a la BTB desde el proceso atacante, ejecutando múltiples veces un salto desde la misma dirección (o que al menos coincidan los 20 bits inferiores), y que salte al mismo sitio al que nosotros queremos que la víctima salte.

Con esto ya se tiene la primera parte del ataque. Se controla 'x' para que apunte a una zona de memoria que queramos leer, se engaña al sistema de predicción de salto para que ejecute lo que hay dentro del 'if' y deje huella en la caché por la ejecución especulativa. La caché es compartida entre procesos,y si bien no se pueden recuperar entradas de otro proceso, se puede aprovechar el hecho de que las entradas de la caché de diferentes procesos compiten entre ellas por el mismo hueco, y se echan unas a otras. Esto es, el proceso atacante llena la caché con sus entradas, y después de ejecutar el exploit, podemos empezar a leer de memoria, a ver cuál es más lenta y por tanto ha sido echada de la caché por el proceso víctima.


Advertencia

Hemos hecho un esfuerzo para ofrecer una introducción técnica a estas vulnerabilidades, una introducción corta y básica. Para ello hemos esquivado detalles y probablemente cometido alguna que otra herejía explicando conceptos. Esta explicación no pretende ser exhaustiva ni usarse como referencia, para ello están las referencias oficiales en las siguientes páginas:

  • http://bit.ly/2EVsy7u
  • http://bit.ly/2qpmroE
  • http://bit.ly/2m0SoyW



Carlos Ledesma
cledesma@hispasec.com


Más información:

Vulnerability Note VU#584653 - CPU hardware vulnerable to side-channel attacks
http://bit.ly/2qyqpvB

Meltdown and Spectre - Bugs in modern computers leak passwords and sensitive data
http://bit.ly/2EVsy7u
http://bit.ly/2qpmroE

Reading privileged memory with a side-channel
http://bit.ly/2m0SoyW

Meltdown y Spectre: así es la pesadilla en la seguridad de las CPUs de Intel, AMD y ARM
http://bit.ly/2EVszs4

Cómo actualizar todos tus sistemas operativos y navegadores para frenar a Meltdown y Spectre
http://bit.ly/2EW1h4M

A Simple Explanation of the Differences Between Meltdown and Spectre
http://bit.ly/2m1OVAy

Negative Result: Reading Kernel Memory From User Mode
http://bit.ly/2qwoJmd




☛ El artículo completo original de noreply@blogger.com (Carlos Ledesma) lo puedes ver aquí

No hay comentarios.:

Publicar un comentario