jueves, 18 de enero de 2018

Posibles consecuencias de Meltdown y Spectre

Tras el revuelo que se montó con las dos vulnerabilidades que se detectaron estas navidades (bueno, unos meses antes) que ya analicé en las dos entradas anteriores, quisiera hacer una reflexión acerca de lo que en realidad son los sistemas modernos de vulnerables o no.

Lo primero que hay que decir es que sí, que todos los sistemas de los últimos 10 años son vulnerables y lo van a ser hasta que se solucione el problema y se cambien las CPUs por otras nuevas. Esto incluye a Intel, AMD, ARM, Power y todas las CPUs con ejecución especulativa (o como leñe se traduzca al español y que no suene tan mal) Si quieres uno seguro, los SPARC con encriptación de la memoria por HW.

Ahora la cuestión ¿cómo de complicado es explotar estos fallos? Pues ahí tengo una buena noticia: es muy complicado, lo que no quiere decir que te quedes tranquilo del todo, pero por lo menos, no vamos a morir todos esta semana (y la que viene, seguramente tampoco) pero tampoco que extrañaría que en un tiempo, alguien sea capaz de sacarle jugo.

Hay gente que saca provecho de todo .. hasta de los cuadro de Kandinsky

Meltdown, aparte de afecta únicamente a Intel (los AMD lovers están de enhorabuena) se puede parchear con el parche KPTI o KAISER que lo que es separar los datos de kernel y de usuario en dos espacios de direccionamiento diferente con lo que un intento de acceder a la parte protegida es infructuoso. Lo malo es que obliga a cambios de direccionamiento y el impacto en el rendimiento es negativo, no tanto como se decía inicialmente y mucho mayor en CPUs y sistemas operativos antiguos como Windows Server. No hay mal que por bien no venga ... se puede forzar la renovación tecnológica (cada vez que veo, en 2018 peticiones de nuevas máquinas con Windows 2008 se me revuelven las tripas) Respecto a SPECTRE, no hay paliación, pero le pasa lo mismo, si no le ofreces los datos en el mismo espacio de direcciones no puede acceder a ellos.



Por otro lado, ninguno de estos agujeros puede escribir en ninguna parte, sólo pueden leer, así que no debemos temer que nos inyecten código malicioso, pero si podrían pillar claves privadas, usuarios y otros datos delicados y confidenciales que deberían estar protegidos. Aquí tenemos que tener en cuenta dos cosas:
  • Aleatorización del espacio de direcciones. En el pasado los sistemas operativos tenían un mapa de memoria más o menos fijo y yendo directamente a posiciones de memoria se podían obtener resultados en concreto (la memoria de vídeo, el stack, variables del sistema, etc) De hecho, muchas veces se programaba directamente pensando en estas direcciones con el fin de mejorar las prestaciones. Eso ya no es así, pero una parte la memoria está "virtualizada" (los programas tienen mapas de memoria propia que se copia en memoria real) y se ha incorporado un elemento "aleatorio" a la hora de distribuir la memoria del kernel, de manera que no está siempre en las mismas posiciones, lo que dificulta el acceso a determinados áreas del sistema. No es lo mismo crear un virus que saque datos de unas posiciones determinadas que no uno que tenga que buscar primero.
  • Mapa de memoria de MS-DOS
  • La velocidad de acceso a los datos de estas vulnerabilidades es relativamente escasa. Según he leido, 500 KB/s en el caso de Meltdown y 10 KB/s en el caso de SPECTRE. Eso significa que si tenemos que extraer y analizar 1 MB de datos tardaremos 100 segundos en el segundo caso en el mejor de los casos y haciendo un impacto en el sistema que nos estaría gritando "¡eh! ¡que estoy aquí putenandote el ordenador!" y la cosa no es así ¿verdad? queremos ser discretos. Si para un 1 Mb necesitamos 100 segundos, para 1 GB que son 1024 veces más como que la cosa no parece eficaz (el PC desde el que escribo tiene 16 GB, son 455 horas de análisis si no me he equivocado)

Como ya hemos dicho que no se permite escribir, hay que utilizar otras maneras de entrar en los sistemas, bien vía troyanos, vulnerabilidades de otros componentes ... o desplegando una VM con código malicioso que afecta a las nubes públicas (por eso se han dado tanta prisa en desplegar los parches) 

Pero por no estar en una cloud pública o tener KPTI instalado no vas a estar seguro. Quizás no te puedan acceder a claves privadas en el kernel pero por ejemplo, si te pueden meter un keylogger y capturarte passwords como muestra el vídeo.




Para resumir: El riesgo existe aunque la manera de protegerse también y es relativamente sencilla:
  • Mantén el sistema operativo actualiza. Evita los sistemas operativos antiguos que no tienen parche de seguridad.
  • No te instales software de orígenes dudosos.
  • Ojo al spam, phising, troyanos, etc
  • Mantén el antivirus actualizado.
  • No sería mala idea encriptar el HD (perjudica a las prestaciones)
  • Tomate el ordenador en serio, sobre todo si lo tienes conectado a Internet (cosa que haces ya que si no, no estarías leyendo esto)
  • Ojo con los android que las apps las carga el diablo (IOS creo que es un poco más seguro, pero hay que tener cuidado también)

lunes, 8 de enero de 2018

SPECTRE. Recuperación del dato. Entre lo sutil y lo diabólico.

En mi entrada anterior hablé de cómo MELTDOWN y SPECTRE burlaban los mecanismos de protección de las CPU para llegar áreas protegidas pero la cuestión era ¿cómo podía recuperar el dato? -- como si a alguien le importara -- (le voy a dar a la voz en off) 

Si analizamos el comportamiento predictivo de la  CPU resulta que por un lado, puede ejecutar comandos de forma desordenada ... vale, pero si por un casual atacamos una posición de memoria protegida no la podemos dejar en ningún sitio  -- claro zoquete, por eso está protegida -- ni en un registro ni en una posición de memoria pero resulta que sí podemos averiguar su valor por medio ... sutil, como diría el maestro ... con indirectas.



Pues antes de contar cuál es ese medio, voy a explicar un poco en detalle cómo funciona una caché -- si ya sabía yo que éste si no soltaba el rollo no se quedaba a gusto --- Las CPUs son rápidas, muy rápidas pero las memorias, aunque lo parezcan, no lo son tanto y por ello metemos en medio unas memorias mas rápidas llamadas cachés que aunque no son tan rápidas como los registros son mucho más rápidas que la memoria. Con el fin de optimizar el acceso a esa memoria, cada vez que hay que ir a buscar un dato a la memoria no se copia ese dato solo a la memoria, sino que se copia una cosa llamada línea que dependiendo de la arquitectura, es un número de de bytes determinados. En el caso de las CPUs de Intel que uso ahora son 64 bytes como se puede apreciar. Es decir, yo pido un byte y si hay un fallo de caché, va a la memoria y trae 64. Como la probabilidad de que pida bytes adyacentes es muy alta, seguramente la próxima vez que pida otro dato distinto éste se encuentre en la caché.


¿Qué diferencia tenemos de que un dato esté o no en caché? Pues que tarde 400-500 ciclos de CPU en tenerlo o que tarde 200, un ahorro considerable.

Pues la idea de estas vulnerabilidades es aprovecharse de esto. Como ya he dicho previamente, la  CPU accede a datos protegidos pero no los puede dejar en ninguna parte entonces ¿cómo poder saber el valor de este dato? Pues de manera indirecta con dos cosas:
- Profundo conocimiento del funcionamiento de la CPU y cómo se maneja el manejo de páginas de memoria.
- Un array "gordo" que no quepa en una línea y que ocupe 256 páginas de memoria ¿por qué 256 páginas y no 128 bloques o cualquier otra cosas ....? Ahora lo verás. El ejemplo está sacados del whitepaper del meltdown attack.

Lo que voy a describir leerá un sólo byte de la memoria. si quieres leer por ejemplo, una clave de 2048 bits (256 bytes) pues primero tienes que averiguar dónde está y repetir el proceso de 256 veces. No es rápido pero acaba funcionando y si eres sútil, no te pillaran. Si entras a saco vas a joder todo el rendimiento del servidor y te van a pillar a la primera.

El código es el siguiente (como dije, está en el white paper de meltdown)

Previamente a hacer nada vaciaremos toda la cache mediante la instrucción CLFLUSH y cargamos en el registro RCX la dirección que queremos atacar (la protegida) y en RBX la dirección de memoria donde esté nuestro array de prueba. Como ya hemos dicho antes, tenemos que conocer el tamaño de página que se intercambia, en este ejemplo, 4096 bytes (la típica página de 4K de toda la vida)

¿Lo tenemos todo listo? -- pero ¿te crees que alguien ha llegado aquí? -- pues vamos a hacer magia. Cargamos en el byte más bajo del acumulador (recordemos que nuestros registros son de 64 bits) el byte que apunta RCX (línea 4) y multiplicamos ese valor que hemos cargado por 4096 (línea 5) -- ¿lo cualo que dices que has hecho? -- multiplicar por 4096 o lo que es lo mismo, desplazo hacia la izquierda 12 posiciones (el número hexadecimal 0x0C es el decimal 12) lo que equivale a multiplicar por 2 un total de 12 veces y como 2 elevado a 12 es 4096, ya tenemos ese valor. Y con ese valor que hemos obtenido, sumado al puntero del array que queremos acceder cargamos el dato (línea 7) Un avezado espectador diría, pero si, estás machacando el puntero original -- Oye, que estás machacando el puntero original -- (eso no vale, lo he dicho yo primero) Pues en efecto, pero el valor que tenga RBX ya no me importa porque ya he hecho la magia ¿ha quedado claro?

-- Pues no, no ha quedado nada claro -- pues te lo explico, voz en off. En este caso, estoy forzando una excepción (violación de acceso a la memoria protegida) pero me aprovecho de que esto se ejecuta de manera desordenada ... es decir, que antes de que salte la excepción, la CPU ha accedido al dato, ha ejecut a la línea 7 ... y ha deshecho todo lo que hizo porque no es válido ... no hay forma de acceder al valor RBX, ni al de RCX, ni a la posición de memoria ... o quizás sí -- ¿me estás vacilando -- Pues sí, te estoy vacilando un poco porque en realidad si ha quedado un rastro. Antes hemos dicho que tenemos un array del tamaño de 256 páginas (en este caso 4K cada una, un array en memoria de un Mbyte) y que previamente, hemos vaciado la caché. Utilizando como índice el valor protegido hemos accedido a una posición de memoria de nuestro array cuyo valor es irrelevante, la cuestión es que ha cargado en memoria una página de memoria y sólo una ¿cómo sé cual? Pues de una manera muy sencilla, voy consultando página por página (desde 0 hasta 256 leo un byte en la posición multiplicada por el tamaño de la página) y miro cuanto tiempo tarda la CPU en dármela. Dado que todas están en la memoria principal, tendrá que ir a por ellas y tardará un tiempo muy similar para todas la páginas menos para una ... aquella página cuya posición coincida con el valor leído previamente en la memoria protegida -- no me queda claro-- Pues seguro que si ves este gráfico te queda más claro.


Todos los accesos pasan de los 400 ciclos de CPU menos uno que se queda por los 200, ese el que hemos pillado antes y ¡voilá! hemos hecho magia, averiguando un valor sin poder acceder a él. si quiero más datos, pues nada, repito el proceso las veces que haga falta, que con paciencia y saliva el elefante ...

El mecanismo que uses para llegar a forzar este proceso puede ser como MELTDOWN (fuerza una excepción pero antes ya ha accedido) o jugando con la predicción (SPECTRE) o algún invento nuevo, pero lo interesante del caso es que no le veo una solución sencilla al menos sin perjudicar el rendimiento del ordenador. No sirve el separar en cachés los datos e instrucciones del anillo 0 y del 3 porque estás accediendo a los datos del anillo 3 y vas a dejar el rastro en la caché. El forzar flushes de la caché cuando se produzcan violaciones puede perjudicar a otros procesos (no olvidemos que la LLC está compartida por todos los cores y procesos) Seguramente habrá que reforzar de alguna manera el acceso a la parte protegida por HW, primando el bit de protección que al parecer ahora  no es lo suficientemente estricto.

-- ¿De qué anillos hablamos? -- De estos, que te lo hay que contar todo.




Pues ... eso es esto, amigos.

-- Anonadado me hallo ¿latita Whiskas pa celebrarlo? ---

viernes, 5 de enero de 2018

Meltdown, Spectre y la caché de la muerte.

Estos días se ha hecho pública una vulnerabilidad que se aprovecha de la arquitectura de caché de todos los ordenadores. Posiblemente lo que hayas oído es que todos los ordenadores son vulnerables y que no hay protección posible que eso canta mucho en las noticias y es sensacionalista a tope. Lo malo es que por una vez tienen bastante razón.

Vamos a ver lo que pasa y para ello toca un poco de - ¿rollo patatero? - pues en efecto (macaguenlavozenoff).

Vamos a ir por partes, que la cosa tiene tela (avisados estáis ... ¡huid insensatos!) Por un lado, hay que tener en cuenta una cosa llamada "anillos de ejecución" que se definen en los sistemas operativos, el nivel 0 es el del sistema operativo y es el más privilegiado. Luego van los drives y por último, las aplicaciones, con menos privilegios.


¿Por qué se hace esto? Pues para evitar que se monte el pifostio padre y que una aplicación pueda modificar cosas del sistema operativo o de otras aplicaciones como ocurría en sistemas operativos más antiguos o simplones como el MS-DOS donde todo era accesible por todo y un programa mal hecho (no hay programa infalible) te podía colgar el ordenador o hacer algo peor, meterte un virus. Éstos inicialmente eran para tocar las narices pero ahora, con todos los ordenadores conectados te pueden robar tus datos o utilizar tu ordenador para cosas raras desde minar criptomonedas o cosas peores.

La cuestión es que los UNIX/Linux, el IOS de Apple que está basado en UNIX, Android (basado en Linux) y por fin Windows no permiten el acceso de las aplicaciones a estos niveles, haciéndolos más seguros (Claro que si sigues usando 1234 como password importa tres narices) Últimamente los virus intentan entrar por dos vías:
- Engañando al usuario para que les "eleve" la prioridad y hacer de las suyas,.
- Aprovechando bugs del sistema operativo o de las aplicaciones.

Al final todo esto, con un poco de cabeza, un antivirus y no andando haciendo el indio, más o menos se palía.

Ahora viene la segunda parte (de este rollo, que siempre se te olvida) que va un poco de arquitectura. Resulta que los vendedores de CPUs hablan mucho de cores, GHz pero suelen omitir un pequeño detalle: el acceso a la memoria. Echemos un ojo a esta captura:


La velocidad de la CPU es molona, casi 4 GHZ ... parece un pepinaco de cuidado y lo es. Lo que llama la atención es cuando aparece un campo llamado multiplicador .... ¿qué es eso? A lo mejor debemos retarnos a la era de los 486, cuando había uno con los número 33/66 (yo tuve un 33/100) y eso significaba que mientras que el procesador funcionaba internamente a 66 MHz (si, tres órdenes de magnitud por debajo) el acceso a la memoria funcionaba a la mitad (33 Mhz) Pues con el tiempo, la distancia en velocidad entre las CPUs y la memoria se ha ido incrementando. En este caso te encuentras que la memoria funciona a unos pobres 103 MHz y el multiplicador es nada menos que hasta un x44. No obstante, la memoria también ha evolucionado. Por ejemplo, utiliza ambos flancos en la señal de reloj, lo que convierte la frecuencia en el doble, transfiere varios bits en cada flanco, tiene bastantes bits de ancho de banda, .... En concreto en este caso, la velocidad teórica es de 3605 Mhz (DDR4) pero esa velocidad es sólo cuando transmite ... el problema es cuando hay que ir a buscar el dato que nos encontramos con una cosa llamada latencia que significa que el procesador está parado a la espera de un dato ... y eso pasa mucho más de lo que te piensas.


¿Cómo podemos paliar esto? -- pero ¿acaso te piensas que nos importa? -- Pues de varias maneras (incluso todas a la vez):
- Aumentando el número de cores, lo que permite hacer cosas en paralelo.La potencia del procesador equivale al número de GHz por el número de cores.
- Aumentando el número de threads por core, lo que hace que si un thread está en espera, el otro pueda que haga algo. En este caso, mi CPU no tiene Hyperthreading, pero en otras el aumento aparente es aproximadamente un 30-40% del rendimiento de la CPU. En procesadores con más threads como los SPARC, es mayor.
- Metiendo entre la memoria principal y los registros de la CPU diversas capas de memorias, cada vez más lentas pero cada vez mayores. En este caso, cada core dispone de dos cachés muy rápidas y pequeñas, de 32 KBytes cada una, una para datos y otra para código (una especie de arquitectura tipo Harvard que contrasta con el resto de la máquina, que en Von Newman) luego una segunda caché (por core) de 256 KB que mezcla datos y código y por último, una caché de L3 compartida por todos los cores de 6 MBytes que es la mayor y más lenta, pero aún así, más rápida que la memoria principal. Al último nivel de caché (en este caso la L3) se la conoce como Last Level Caché (LLC) y es el tamaño de caché que se publicita.


¿Hasta aquí todo claro? (pues seguro que lo complica) Con el fin de optimizar el rendimiento de la CPU las cachés no sólo van cargando datos a medida que se piden exclusivamente, se carga un bloque adyacente de manera que si el dato que se pide a continuación está en ese bloque (suele pasar) ya lo tienes en la caché. Es decir, la CPU va como a tirones: procesa lo que hay en la caché hasta que se queda sin datos y los vuelve a buscar a la memoria, cuando los tiene, los procesa de nuevo.

Pues resulta que las instrucciones no se ejecutan sin más, sino que primero se decodifican, se cargan los datos y luego se operan. Al final, una sola instrucción implica varios ciclos de CPU pero resulta que en realidad, las CPUs son capaces de de ejecutar una o más instrucciones por ciclo ¿cómo? Pues por medio de pipelines que ejecutan estas fases en paralelo: una decodifica instrucciones, otra carga parámetros, otras operan, ..... Pues para rizar el rizo y con la ayuda del compilador, no solo hacen eso sino que son capaces de predecir el comportamiento del programa en hilos ... es decir, pueden ejecutar el IF y el ELSE a la vez y en función del resultado, aprovechar la rama de ejecución ya calculada y descartar la mala ¡la leche! ¿no?  De hecho es capaz de ejecutar las instrucciones de manera desordenada (o lo que es lo mismo, de una manera distinta a la que fueron escritas) para optimizar los recursos y luego ordenar los resultados. Por supuesto, para ello cada procesador precisa un compilador especialmente diseñado para él

Pues toda esta parafernalia que ha costado más de una década desarrollar al nivel actual es la causante de los problemas que han aparecido recientemente. Al parecer incluso el CEO de Intel ha vendido todas las acciones que ha podido de su compañía, quedándose tan solo con las mínimas exigidas para el puesto cosa que le puede costar las cárcel por uso de información privilegiada. En España le meterían de CEO en una empresa del IBEX 35.

Lo que ha aparecido son básicamente dos vulnerabilidades:


Intenta hacer un acceso a un área de memoria restringida. El nivel de protección del Sistema Operativo y del HW se lo impide, pero consigue aprovechar una debilidad de las CPUs Intel y acceder a cualquier posición de memoria protegida. Este problema se mitiga con un parche que si no ha salido saldrá ya conocido como KPTI (Kernel Table Page Isolation) o KAISER que mueve las tablas de memoria del Kernel. Lo malo es que al parecer puede ralentizar bastante los ordenadores, en especial los que se hacen uso intensivo de los cambios de contexto (cambios de nivel en los anillos) que son las nubes, equipos virtualizados, etc. Pero mejor que permitir que desde una VM se acceda a otra ¿no? Yo no descartaría que en futuro aparezcan variantes de Meltdown que busquen otros datos aparte de las tablas de memoria. De momento, sólo afecta a CPUs Intel.


Esta es una vulnerabilidad peor porque afecta as todos los procesadores y arquitecturas (Apple, Android, Windows, Linux, ...) y es debida a que las cachés son compartidas por todos los procesos que corren en un ordenador y en los ordenadores modernos, procesos, lo que se dicen procesos, corren un montón (en la imagen de abajo puedes ver una captura de lo que hay en mi PC ahora mismo y no tengo precisamente muchas cosas abiertas pero procesos, dice que hay 205 corriendo) El tema es que Spectre juega con el sistema de predicción, forzando la ejecución de código privilegiado en entornos no privilegiados. Es la más complicada porque no tiene prevención (al menos ahora) y con un simple javascript pueden acceder a datos que se encuentren en el mismo servidor (aunque pensándolo bien, en entornos multiprocesador sólo se podrían acceder a VMs que corrieran en el mismo procesador e incluso, en los mismo cores, eso va a depender si está en L3 o en L2/L1) La cuestión es que la cosa tiene mala solución. Lo bueno de Spectre es que es muy complicado el sacarle partido de manera maliciosa ahora mismo ... pero todo llegará.


Y para terminar ¿qué afectación vamos a tener? Pues lo grandes servidores de nubes, públicas o privadas van a tener un problema serio de vulnerabilidad y de hecho, ya se están intentando proteger, aunque sea a costa de perder un porcentaje importante de sus prestaciones (se habla de hasta un 30%) El cambio de arquitectura y CPUs a corto plazo es irrealizable, a medio ... pues ya veremos. Se irán paliando poco a poco estos problemas, se incorporarán mecanismos de seguridad en la caché y poco a poco se irán sustituyendo los ordenadores vulnerables (hoy, 5 de enero de 2018 son todos) pero dudo que antes de una década haya desaparecido esta amenaza completamente. el tema no es tanto producir chips como el desarrollar, probar y reemplazar una arquitectura que ha estado evolucionando por más de una década.

Teóricamente los ordenadores domésticos son menos "interesantes" a la hora de infectarlos porque ya está el malware habitual, pero yo no me confiaría demasiado.

A saber de quién será la voz en off.
Felices fiestas que me voy de celebración.


Armaduras.

He de reconocer que últimamente no me estiro demasiado en el tema bloguero este. Tampoco voy a molestarme en hacer propósito de enmienda so...