Pero, ¿cómo se puede infectar mi ordenador si sólo he visitado una web?

Pero, ¿cómo se puede infectar mi ordenador si sólo he visitado una web?
16 comentarios Facebook Twitter Flipboard E-mail

¿Nunca os habéis preguntado cómo es posible que con sólo abrir una página web o un fichero creado para la ocasión, los _hackers_ sean capaces de aprovecharse de una vulnerabilidad y tomar el control de un ordenador? Tenemos un ejemplo muy reciente de esto en el fallo de Internet Explorer descubierto hace unos días. Un objeto Flash especialmente preparado que permite a un atacante tomar control de tu equipo como por arte de magia.

¿Qué está pasando en realidad en estos ataques? En este artículo vamos a tratar de explicar sus fundamentos básicos: las técnicas que se usan en la práctica son bastante más complejas y enrevesadas, pero siempre responden a la misma idea, que es que se sobreescriban ciertos datos en memoria.

¿Cómo ejecuta un ordenador un programa?

Antes de entrar en materia tenemos que repasar un poquito, de manera simplificada, cómo funcionan los ordenadores y cómo ejecutan los programas.

Un programa simple.

Un programa simple. En cursiva, las "instrucciones" para el procesador.

1: Las instrucciones son en realidad muchísimo más básicas que las que he puesto, del estilo "suma este registro a este otro registro, guarda el resultado en X posición de memoria", pero para la explicación no es demasiado relevante.

Los programas son, a grandes rasgos, un montón de instrucciones puestas una detrás de otra para que el procesador las ejecute. Supongamos que tenemos nuestro programa "Hola Mundo", el de la imagen de arriba, que simplemente pide al usuario que introduzca una cadena e imprima otra por pantalla. Ahí veis las instrucciones1 que ejecutaría el procesador junto con los datos necesarios, que están guardados en los tres primeros huecos.

Cuando ejecutemos el programa, el sistema operativo lo pondrá en una posición de memoria cualquiera, y le _dirá_ al procesador que empiece a ejecutar las instrucciones a partir de la dirección 3, que es donde empieza el código que queremos ejecutar. Cuando acabe, nuestro programa devolverá el control al sistema operativo con la última instrucción.

Básicamente así son todas las funciones en un programa. Una sección con datos, un código a ejecutar y una última instrucción para volver a la función anterior, cuya dirección está guardada en una posición de memoria.

¿Y si me paso al escribir?

Algún lector avispado habrá detectado ya el posible fallo en el programa anterior. Al procesador no le hemos dicho cuánto espacio tiene en la dirección 0 para guardar una cadena. ¿Qué pasa si yo escribo una cadena muy larga y guarda más de lo que puede? Pues lo que veis en la siguiente imagen.

Overflow

El procesador, que es muy obediente (o estúpido, según se mire) copia la cadena de texto, y si no le dices donde parar, el procesador no para y sigue escribiendo, aunque sobreescriba otras cosas. En este caso, podéis imaginar que ahora, en lugar de escribir "Hola mundo" escribirá lo que haya quedado en esa segunda posición, en este caso _"larga y me he pasado"_.

Como curiosidad esto está bien pero no tiene mucho de fallo de seguridad. De momento. ¿Qué pasa si ponemos una cadena más larga aún? Sobreescribiría la dirección 2, que dice dónde tiene que volver. Es decir, que cuando el procesador llegue a la última instrucción volverá a una dirección aleatoria, la que indique el valor que haya quedado sobreescrito en esa posición.

Ese es el primer paso para aprovecharse de los fallos: poder controlar qué es lo siguiente que va a ejecutar el procesador. El segundo es introducir el código que quiera ejecutar el atacante. Para ello, en lugar de una cadena normal introducimos una cadena que, en binario, son instrucciones para el procesador. Por continuar con nuestro programa de ejemplo, si introdujésemos _"descarga malware.com, ejecuta el descargable, 0"_ nos quedaría algo como esto en memoria:

Buffer overflow

El procesador ejecutará la función normalmente. Cuando llegue al final, en lugar de volver a donde tenía que volver, empezará a ejecutar las instrucciones que hay a partir de la posición 0, es decir, las que el atacante ha querido introducir.

Así de simple es este tipo de vulnerabilidad, también llamada de _buffer overflow_. Nos aprovechamos de un fallo en un programa que no comprueba bien hasta dónde escribe para sobreescribir ciertos datos que luego nos den el control sobre la ejecución (lo que se suele llamar un _shellcode_).

Por supuesto, en la realidad esto no es _tan_ fácil, pero la idea básica es la misma. El problema de verdad no es las simplificaciones que hagamos aquí, sino las protecciones del sistema operativo.

DEP: Eso no se toca

Data Execution Prevention

Problema resuelto. Bueno, casi.

Desde hace unos años, los principales sistemas operativos cuentan con mecanismos de protección frente a este tipo de fallos, siendo el principal Data Execution Prevention (DEP, prevención de ejecución de datos). Con DEP, el sistema operativo impide que se ejecute nada en la parte de datos, de tal forma que aunque consigamos desbordar un búfer y sobreescribir la dirección de retorno, no seremos capaces de ejecutar nuestro código.

DEP dificulta mucho pero no termina de impedir la explotación de fallos de este tipo. Quizás no podamos ejecutar código que metamos nosotros, pero eso no quita que un programa tenga otro montón de funciones interesantes que podemos ejecutar. Por ejemplo, system, que permite ejecutar un comando cualquiera del sistema. Si modificamos la dirección de retorno (en el código de antes, el número en la instrucción 2) podemos hacer que se ejecuten funciones del sistema con los parámetros que queramos, y así tomar control de la ejecución.

Este tipo de ataques se denominan _return-to-libc_, y aunque así explicados parecen sencillos, no lo son tanto. El principal problema es que tenemos que saber en qué dirección está la función que queremos llamar, algo difícil teniendo en cuenta que sistemas operativos modernos tratan de distribuir aleatoriamente (el nombre técnico es Address Space Layout Randomization) la posición de estas funciones para evitar precisamente ese tipo de ataques.

¿Cómo funcionaba el _exploit_ de Internet Explorer?

Hasta ahora hemos visto lo básico de este tipo de fallos y cómo un atacante puede ejecutar código en nuestro sistema sólo con hacernos abrir un archivo o ver una página web. Por supuesto, hay técnicas muchísimo más avanzadas como Return-Oriented Programming, que permite ejecutar cualquier cosa sólo manipulando las direcciones de retorno, usando _pedazos_ de códigos de librerías conocidas.

Pero ya que hemos empezado hablando de Internet Explorer, vamos a acabar el artículo hablando también de él y explicando el fallo que ha sufrido. FireEye ha publicado un análisis de cómo funciona el enrevesado _exploit_, nosotros vamos a verlo simplificado.

El primer paso es poner a ejecutar un archivo SWF (Flash), que crea varios objetos en memoria. Después, llama a una función de Javascript de Internet Explorer. Esta función tiene un fallo, y es que permite sobreescribir el campo que indica la longitud de un objeto en Flash.

2: La realidad no está muy lejos de esto: según FireEye, el objeto vulnerable es un vector o lista de elementos.

Ese es el punto más importante del _exploit_, así que vamos a repasarlo bien. Supongamos que el objeto en cuestión es una cadena de caracteres2, y el campo de longitud le dice a Flash cuantos caracteres tiene de capacidad. Gracias al fallo de IE el atacante _engaña_ a Flash y le hará pensar que la cadena ocupa más espacio del que abarca en realidad. Esto quiere decir que si guardamos ahí una cadena de mayor longitud que la capacidad real, se escribirá en partes de la memoria que de otra forma no podría modificar el _exploit_.

Layout Memoria

Los atacantes no dejan nada al azar, por supuesto. Los objetos están creados de tal forma que tengan una distribución especial en memoria. Casualmente, el objeto que está detrás de nuestra cadena es un archivo de sonido que, entre otras cosas, tiene campos con direcciones de funciones a las que llamar para realizar ciertas tareas (técnicamente, esos campos pertenecen a la tabla de métodos virtuales).

Aquí aplica lo que habíamos visto en el apartado anterior: sobreescribe uno de esos campos con una llamada a la función de Windows ZwProtectVirtualMemory, que entre otras cosas puede desactivar la protección DEP de segmentos de memoria (siempre y cuando la política de seguridad lo permita, claro).

A partir de aquí todo va cuesta abajo y sin frenos. Sin protección de ejecución de datos y con acceso a la memoria, el _exploit_ añade varias instrucciones que arreglan el estropicio y limpian las huellas, y que después descargan el _malware_ de verdad como una imagen. FireEye no da muchos más detalles de qué hace ese archivo adicional, pero ya podéis imaginar que no es nada bueno.

Hasta aquí este artículo. Esperamos que haya servido como una introducción interesante a este tipo de ataques. Como siempre, si tenéis dudas o sugerencias podéis plantearlas en los comentarios.

Comentarios cerrados
Inicio