DirtyCow: ¿En qué consiste este fallo de Linux?

Hoy vamos a explicar algunas de las características importantes de DirtyCOW, esta vulnerabilidad de larga data del núcleo Linux.

Apodado DirtyCOW, esta vulnerabilidad de escalada de privilegios potencialmente permite a cualquier aplicación instalada, o código malicioso, ganar acceso a nivel de root y secuestrar un dispositivo. Esta vulnerabilidad, designada como CVE-2016-5195, fue descubierta por el investigador Phil Oester.

El bug de programación obtiene su nombre del mecanismo del kernel Linux llamado Copy-On-Write, o copia en escritura, que permite a un proceso copiar el área de memoria al momento de clonar (fork) o, por ejemplo, al mapear archivos en memoria (mmap), y solo duplica o crea un nuevo segmento de memoria al momento de modificar el original.dirtycow

La implementación del bug permite a los programas tomar el control de lo que debería ser un segmento de memoria de solo lectura del root mapeado en la m emoria del proceso.

Aunque la vulnerabilidad no es en si misma grave ni rara (tengamos en cuenta que Micro$oft corrige vulnerabilidades de escala de privilegios todos los meses), este fallo puede ser particularmente peligroso, ya que está presente en el núcleo Linux desde la versión 2.6.22 (2007) y es muy fácil de explotar. Además, muchos otros sistemas operativos derivados que utilizan el núcleo Linux, como Android, también podrían ser vulnerables al DirtyCOW.

Además, el código del exploit está disponible en Internet para profesionales de la seguridad a modo de prueba de concepto, pero está siendo utilizado para vulnerar los sistemas expuestos en la red global.

Incluso Linus Torvalds, creador del núcleo Linux, admitió la semana pasada que intentó corregir esta vulnerabilidad 11 años atrás, y no lo corrigió porque en su momento era difícil, y no era grave. Hoy el mismo error tiene una relevancia mayor debido a cambios en la arquitectura del núcleo.

Varias distribuciones ya han lanzado parches de seguridad para corregir esta vulnerabilidad, incluidos Debian, RedHat y Ubuntu, por lo que en estas distros, simplemente con actualizar el sistema (y reiniciar) se soluciona el problema.

Desafortunadamente, varios builds del kernel vulnerable está instalado en millones de routers, gadgets de Internet de las cosas (IoT), y otros sistemas embebidos, y será muy difícil parcharlos.

Detalles técnicos 🙂

COW (copy-on-write) es utilizado para simplificar la administración de memoria del sistema operativo. Entre otras cosas, permite ejecutar programas que compartan datos comunes en memoria hasta que uno de ellos quiera privadamente modificar dichos datos. En este punto, el núcleo copia los datos a otra página de memoria de modo que solo el proceso en cuestión se vea afectado.

Primero, tienes que abrir un ejecutable de root con mmap() en modo solo lectura como un mapeo privado de archivos en el segmento de memoria del proceso. De esta forma, el ejecutable podrá ser leído por el proceso de usuario.

Mientras, repetidamente deberías llamar a la función madvise()  en el mapeo de memoria con el flag MADV_DONTNEED, que le dirá al núcleo que no necesitas acceder ni utilizar realmente y en el corto plazo, el segmento de memoria mapeado. Esto puede hacerse con un thread en el proceso.

Así, en otro thread del mismo proceso abres archivo /proc/self/mem con privilegios de lectura-escritura. Este es un archivo especial que permite al proceso acceder a su propio segmento de memoria virtual como si fuera un archivo.

Utilizando funciones normales para hacer seek y escritura en archivos, puedes repetidamente sobre escribir parte de tu propia memoria que está mapeada al archivo ejecutable del root. Estas escrituras no deberían tener efecto sobre el ejecutable del disco.

Ahora, tu proceso puede leer el binario mapeado en modo solo lectura en un objeto read-only privado, un thread está ejecutando madvise() en el objeto de solo lectura, y otro thread está escribiendo en ese objeto de solo lectura.

La escritura en ese objeto en memoria dispara el CoW: la página modificada del ejecutable será alterada solo en la memoria local del proceso, no en el archivo de root que se encuentra en el disco y que está siendo mapeado.

No obstante, y debido a este bug, el kernel ejecuta la operación CoW pero permite al proceso escribir el ejecutable mapeado en solo lectura igualmente. Estos cambios son enviados al disco por el kernel.

Esto ocurre porque, debido a que estamos ejecutando constantemente la función madvise(), el núcleo no des-asocia completamente el ejecutable mapeado desde la memoria privada del proceso que lo intenta modificar. El proceso escribe sobre el objeto de solo lectura, dispara un fallo de página, y el kernel maneja este fallo de página copiando las páginas modificadas en el área privada de memoria del proceso, hasta ahí sin problemas.

Ahora bien, madvise() le “dice” al kernel que descarte las páginas asociadas al archivo mapeado. Llamar a esta función mientras estamos escribiendo el /proc/self/mem eventualmente generará un estado inconsistente donde las páginas alojadas en la región mapeada para el ejecutable serán eliminadas, y se podrá escribir en el área de memoria. Una escritura que debería ir a las páginas privadas del proceso y no alterar las páginas del objeto mapeado. Estos cambios son entonces enviados por el kernel al disco, modificando el archivo mapeado.

En otras palabras, ejecutar madvise() permite que el kernel descarte las páginas modificadas en el área local del proceso. Cuando el thread modifica el mapa original del binario, se copian (CoW) las páginas al área local privada del proceso, y luego se descartan por la ejecución de madvise().

Si esto se ejecuta una y otra vez, repetidamente, eventualmente el thread que modifica las páginas modificará las páginas reales del mapeo de binario, lo que ocasionará que se escriban los cambios en el disco, sobre el archivo original.

Esto puede ser explotado para modificar un binario de root con SUID, de modo que, por ejemplo, podamos levantar una shell de root en el sistema.

El parche, que solo cambia 2 líneas e introduce una función inline, setea el flag que envía una señal a CoW indicando que ocurrió una operación, y previniendo que una pagina cargada en el mapa de memoria privado de un proceso sea desbloqueada y sobre escrita.

Algunos FAQs oficiales

En el sitio oficial de la vulnerabilidad, dirtycow.ninja, podemos ver algunas preguntas y respuestas interesantes.

¿Qué es CVE-2016-5195?

Es la referencia oficial al bug, CVE es Common Vulnerabilities an Exposures, y es el estándar para nombres de vulnerabilidades de seguridad mantenido por MITRE.

¿Por que se llama DirtyCOW?

Por  lo que comentamos antes, COW viene de Copy-on-Write, la funcionalidad del kernel que da origen a esta vulnerabilidad.

¿Mi sistema está afectado por este bug?

Seguramente sí, al momento de escribir este articulo 🙂

¿Puede un antivirus bloquear el ataque?

Pueden desarrollarse firmas de antivirus que detecten DirtyCow, por ejemplo, comparando el tamaño de los binarios del sistema antes y después de estar afectados, pero no hay forma de bloquear el ataque.

¿Es un error de OpenSSL?

Nop.

¿Cómo puedo corregir la vulnerabilidad en mi sistema?

El fallo ya fue corregido en el núcleo, y muchas distros ya lo han corregido, solo basta actualizar la distribución y reiniciar el sistema para solucionarlo en la mayor parte de los casos.

¿Cómo puedo desinstalar Linux?

Siga estos pasos xD

¿Hay forma de saber si me han explotado la vulnerabilidad en el sistema?

El exploit de la vulnerabilidad no deja huellas, por lo que no puede saberse.

Un screencast de prueba

En el siguiente screencast les muestro cómo podemos ejecutar un exploit de DirtyCow en Ubuntu, y elevar privilegios a root en el sistema.

En el código del exploit se puede ver perfectamente, sin entrar demasiado en detalle, las funciones C que hemos estado comentando más arriba.

Espero les resulte interesante y ameno!

No duden en compartir si creen que puede serle útil a alguien! Gracias!!