Difundiendo ImageMagick y profundizando en CVE-2020-27829


Introducción:

ImageMagick es un software de código abierto muy popular que se utiliza en muchos sistemas de todo el mundo. Está disponible para las plataformas Windows, Linux, MacOS, así como para Android e iOS. Se utiliza para editar, crear o convertir varios formatos de imagen digital y admite varios formatos como PNG, JPEG, WEBP, TIFF, HEIC y PDF, entre otros.

Google OSS Fuzz y otros investigadores de amenazas han hecho de ImageMagick el foco frecuente de fuzzing, una técnica extremadamente popular utilizada por los investigadores de seguridad para descubrir posibles vulnerabilidades de día cero en software de código abierto y cerrado. Esta investigación ha dado lugar a varios descubrimientos de vulnerabilidades que sus responsables deben abordar de forma regular. A pesar de los esfuerzos de muchos para exponer tales vulnerabilidades, una investigación reciente de McAfee ha expuesto nuevas vulnerabilidades que involucran el procesamiento de múltiples formatos de imagen, en varias bibliotecas y software de código abierto y de código cerrado, incluidos ImageMagick y Windows GDI +.

ImageMagick borroso:

Las bibliotecas de código abierto difusas se cubrieron en un blog detallado “Descubrimiento de vulnerabilidades en bibliotecas de código abierto Parte 1: Herramientas del oficio” el año pasado. Fuzzing ImageMagick está muy bien documentado, por lo que cubriremos rápidamente el proceso en esta publicación de blog y nos centraremos en el análisis de la causa raíz del problema que hemos encontrado.

Compilando ImageMagick con AFL:

ImageMagick tiene muchas opciones de configuración que podemos ver ejecutando el siguiente comando:

Podemos personalizar varios parámetros según nuestras necesidades. Para compilar e instalar ImageMagick con AFL para nuestro caso, podemos usar los siguientes comandos:

$ CC = afl-gcc CXX = afl = g ++ CFLAGS = ”- ggdb -O0 -fsanitize = dirección, indefinido -fno-omit-frame-pointer” LDFLAGS = ”- ggdb -fsanitize = dirección, indefinido -fno-omit-frame -puntero ”./configure

PS hacer -j $ (nproc)

$ sudo make install

Esto compilará e instalará ImageMagick con instrumentación AFL. El binario que estaremos fuzzing es "magia”, También conocida como“ herramienta mágica ”. Tiene varias opciones, pero usaremos su función de conversión de imágenes para convertir nuestra imagen de un formato a otro.

Un comando simple incluiría lo siguiente:

$ magia

Este comando convertirá un archivo de entrada a un formato de archivo de salida. Estaremos confundiendo esto con AFL.

Recolectando Corpus:

Antes de empezar a hacer fuzzing, necesitamos tener un buen corpus de entrada. Una forma de recopilar corpus es buscar en Google o GitHub. También podemos utilizar el corpus de prueba existente de varios software. Un buen corpus de prueba está disponible en el sitio de AFL aquí: https://lcamtuf.coredump.cx/afl/demo/

Minimizando Corpus:

La recopilación de corpus es una cosa, pero también debemos minimizar el corpus. La forma en que AFL funciona es que instrumentará cada bloque básico para que pueda rastrear la ruta de ejecución del programa. Mantiene una memoria compartida como mapa de bits y utiliza un algoritmo para comprobar los nuevos aciertos de bloque. Si se encuentra un nuevo hit de bloque, guardará esta información en un mapa de bits.

Ahora es posible que más de un archivo de entrada del corpus pueda activar la misma ruta, ya que hemos recopilado archivos de muestra de varias fuentes, no tenemos ninguna información sobre qué rutas activarán en el tiempo de ejecución. Si usamos este corpus sin eliminar dichos archivos, terminamos perdiendo tiempo y ciclos de CPU. Necesitamos evitar eso.

Curiosamente, AFL ofrece una utilidad llamada "afl-cmin”Que podemos utilizar para minimizar nuestro corpus de prueba. Esto es algo que se recomienda hacer antes de comenzar cualquier campaña de fuzzing. Podemos ejecutar esto de la siguiente manera:

$ afl-cmin -i -o – magia @@ / dev / null

Este comando minimizará el corpus de entrada y mantendrá solo aquellos archivos que desencadenan rutas únicas.

Ejecutando Fuzzers:

Una vez que hayamos minimizado el corpus, podemos empezar a hacer fuzzing. Para fuzz, necesitamos usar el siguiente comando:

$ afl-fuzz -i -o – magia @@ / dev / null

Esto solo ejecutará una única instancia de AFL utilizando un solo núcleo. En caso de que tengamos procesadores multinúcleo, podemos ejecutar múltiples instancias de AFL, con un maestro yn número de esclavos. Donde n son los núcleos de CPU disponibles.

Para verificar los núcleos de CPU disponibles, podemos usar este comando:

Esto nos dará la cantidad de núcleos de CPU (dependiendo del sistema) de la siguiente manera:

En este caso hay ocho núcleos. Entonces, podemos ejecutar un maestro y hasta siete esclavos.

Para ejecutar instancias maestras, podemos usar el siguiente comando:

$ afl-fuzz -M Maestro -i -o – magia @@ / dev / null

Podemos ejecutar instancias esclavas usando el siguiente comando:

$ afl-fuzz -S Slave1 -i -o – magia @@ / dev / null

$ afl-fuzz -S Slave2 -i -o – magia @@ / dev / null

Se puede hacer lo mismo para cada esclavo. Solo necesitamos usar un argumento -S y podemos usar cualquier nombre como esclavo1, esclavo2, etc.

Resultados:

A las pocas horas de comenzar esta campaña de Fuzzing, encontramos un bloqueo relacionado con una lectura fuera de límites dentro de una memoria de pila. Hemos informado de este problema a ImageMagick, y fueron muy rápidos en solucionarlo con un parche al día siguiente. ImageMagick ha lanzado una nueva compilación con la versión: 7.0.46 para solucionar este problema. Este problema fue asignado CVE-2020-27829.

Analizando CVE-2020-27829:

Al comprobar el archivo POC, encontramos que era un archivo TIFF.

Cuando abrimos este archivo con ImageMagick con el siguiente comando:

$ magick poc.tif / dev / null

Como resultado, vemos un bloqueo como el siguiente:

Como se desprende del registro anterior, el programa estaba intentando leer 1 byte pasado el búfer de almacenamiento asignado y, por lo tanto, ASAN provocó este bloqueo. Esto puede conducir al menos a una Error de ImageMagick en los sistemas que ejecutan una versión vulnerable de ImageMagick.

Comprensión del formato de archivo TIFF:

Antes de comenzar a depurar este problema para encontrar una causa raíz, es necesario comprender el formato de archivo TIFF. Su especificación está muy bien descrita aquí: http://paulbourke.net/dataformats/tiff/tiff_summary.pdf.

En resumen, un archivo TIFF tiene tres partes:

  1. Encabezado de archivo de imagen (IFH) – Contiene información como identificador de archivo, versión, compensación de IFD.
  2. Directorio de archivos de imagen (IFD) – Contiene información sobre la altura, el ancho y la profundidad de la imagen, el número de planos de color, etc. También contiene varias ETIQUETAS como mapa de colores, número de página, BitPerSample, FillOrder,
  3. Datos de mapa de bits – Contiene varios datos de imagen como tiras, mosaicos, etc.

Podemos tiffinfo utilidad de libtiff para recopilar información diversa sobre el archivo POC. Esto nos permite ver la siguiente información con tiffinfo como ancho, altura, muestra por píxel, fila por tira etc .:

Hay algunas cosas a tener en cuenta aquí:

El desplazamiento de TIFF Dir es: 0xa0

El ancho de la imagen es: 3 y la longitud es: 32

Los bits por muestra son: 9

La muestra por píxel es: 3

Filas por tira es: 1024

La configuración de la cepilladora es: plano de una sola imagen.

Usaremos estos datos en esta publicación.

Depurando el problema:

Como podemos ver en el registro de fallas, el programa fallaba en la función "PushQuantumPixel" en la siguiente ubicación en la línea 256 de quantum-import.c:

Al marcar la función “PushQuantumPixel” en “MagickCore / quantum-import.c” podemos ver el siguiente código en la línea # 256 donde el programa falla:

Podemos ver lo siguiente:

  • "Píxeles" parece ser una matriz de caracteres
  • dentro de un bucle for, su valor se lee y se asigna a quantum_info-> state.pixel
  • su dirección aumenta en uno en cada iteración del bucle

El programa se bloquea en esta ubicación mientras lee el valor de "píxeles", lo que significa que el valor está fuera del límite de la memoria dinámica asignada.

Ahora tenemos que averiguar lo siguiente:

  1. ¿Qué son los "píxeles" y qué datos contiene?
  2. ¿Por qué se estrella?
  3. ¿Cómo se solucionó esto?

Encontrar la causa raíz:

Para empezar, podemos verificar la función "ReadTIFFImage" en el archivo coders / tiff.c y ver que asigna memoria usando una llamada a la función "AcquireQuantumMemory", que aparece según la documentación mencionada aquí:

https://imagemagick.org/api/memory.php:

“Devuelve un puntero a un bloque de memoria al menos contar * bytes cuánticos alineados adecuadamente para cualquier uso.

El formato de la "AcquireQuantumMemory ” el método es:

void * AcquireQuantumMemory (const size_t count, const size_t quantum)

A continuación se incluye una descripción de cada parámetro:

contar

el número de objetos para asignar de forma contigua.

cuántico

el tamaño (en bytes) de cada objeto. "

En este caso, dos parámetros pasados ​​a esta función son "grado" y "Tamaño de (* strip_pixels)"

Podemos ver eso "grado" se calcula de la siguiente manera en el código siguiente:

Hay una función TIFFStripSize (tiff) que devuelve el tamaño de una tira de datos como se menciona en la documentación de libtiff aquí:

http://www.libtiff.org/man/TIFFstrip.3t.html

En nuestro caso, vuelve 224 y también podemos ver que en el código mencionado anteriormente, "imagen-> columnas * tamaño de (uint64) ” también se agrega en extensión, lo que da como resultado 24 agregado hasta el punto, por lo que el valor 248.

Por lo tanto, este valor de extensión de 248 y el tamaño de (* strip_pixels) que es 1 se pasa a "AcquireQuantumMemory ” función y memoria total de 248 Se asignan bytes.

Así es como se asigna la memoria.

"Strip_pixel”Es un puntero a la memoria recién asignada.

Tenga en cuenta que esto es 248 bytes de memoria recién asignada. Dado que estamos usando ASAN, cada byte contendrá "0xbe", que es el valor predeterminado para la memoria recién asignada por ASAN:

https://github.com/llvm-mirror/compiler-rt/blob/master/lib/asan/asan_flags.inc

La ubicación de inicio de la memoria es 0x6110000002c0 y la ubicación final es 0x6110000003b7, que es un total de 248 bytes.

Esta memoria se pone a 0 mediante un "memset " llamar y esto se asigna a una variable "pag", como se menciona en la imagen de abajo. También tenga en cuenta que "pag" se utilizará como puntero para recorrer esta ubicación de memoria en el futuro en el programa:

Más adelante vemos que hay una llamada a "TIFFReadEncodedPixels ” que lee los datos de la tira del archivo TIFF y los almacena en el búfer recién asignado "strip_pixels" de 248 bytes (documentación aquí: http://www.libtiff.org/man/TIFFReadEncodedStrip.3t.html):

Para comprender qué son los datos de este archivo TIFF, debemos consultar nuevamente la estructura del archivo TIFF. Podemos ver que hay una etiqueta llamada "StripOffsets " y su valor es 8, que especifica el desplazamiento de los datos de la tira dentro del archivo TIFF:

Vemos lo siguiente cuando verificamos datos en desplazamiento 8 en el archivo TIFF:

Vemos lo siguiente cuando imprimimos los datos en "strip_pixels"(Tenga en cuenta que está en pequeño endian formato):

Entonces "Strip_pixels" son los datos reales del archivo TIFF de desplazamiento 8. Esto se atravesará a través del puntero "pag".

Adentro "ReadTIFFImage ” función hay dos bucles for anidados.

  • El primero "en bucle" es responsable de iterar para "samples_per_pixel ” tiempo que es 3.
  • El segundo "en bucle" es responsable de iterar los datos de píxeles para "imagen-> filas"Veces, que es 32. Este segundo bucle se ejecutará para 32 veces o número de filas en la imagen independientemente de tamaño de búfer asignado .
  • Dentro de este segundo bucle for, podemos ver algo como esto:

  • Podemos notar que "ImportQuantumPixel" la función usa el "pag" puntero para leer los datos "Strip_pixels" y después de cada llamada a "ImportQuantumPixel", valor de "pag" se incrementará en "paso".

Aquí "paso"Se calcula llamando a la función"TIFFVStripSize ()"Función que, según la documentación, devuelve el número de bytes en una tira con nrows filas de datos. En este caso lo es 14. Entonces, cada puntero de tiempo "pag" se incrementa en "14 ” o "0xE" dentro de segundo para bucle.

Si imprimimos el imagen estructura que se pasa a "ImportQuantumPixels”Función como parámetro, podemos ver lo siguiente:

Aquí podemos notar que el columnas el valor es 3, la filas el valor es 32 y profundidad es 9. Si verificamos el archivo POC TIFF, esto se ha tomado de Ancho de la imagen y ImageLength y BitsPerSample valor:

En última instancia, el control llega a "ImportaciónRGBQuantum"Y luego al"PushQuantumPixel”Y uno de los argumentos de esta función son los datos de píxeles que apuntan "pag". Recuerde que esto apunta a la dirección de memoria que se asignó previamente mediante el "AdquirirQuantumMemory”, Y que su longitud es de 248 bytes y cada vez que el valor de“ p ”se incrementa en 14.

La "PushQuantumPixel" La función se utiliza para leer datos de píxeles de "pag" en el almacenamiento interno de datos de píxeles de ImageMagick. Hay un bucle for que es responsable de leer datos de la matriz de píxeles proporcionada de 248 bytes en una estructura "Quantum_Info”. Este bucle lee datos de píxeles de forma incremental y los guarda en el "Quantum_info-> state.pixels" campo.

La causa principal aquí es que no hay comprobaciones de límites adecuadas y el programa intenta leer datos más allá del tamaño de búfer asignado en el montón, mientras lee los datos de la tira dentro de un en bucle.

Esto provoca un bloqueo en ImageMagick como podemos ver a continuación:

Causa principal

Por lo tanto, para resumir, el programa se bloquea porque:

  1. El programa asigna 248 bytes de memoria para procesar los datos de la tira para la imagen, un puntero "pag" apunta a este recuerdo.
  2. Dentro de un bucle for, este puntero aumenta en "14" o "0xE" para el número de filas de la imagen, que en este caso es 32.
  3. Según este cálculo, se requieren 32 * 14 = 448 bytes o más cantidad de memoria, pero solo se asignaron 248 en la memoria real.
  4. El programa intenta leer datos asumiendo que la memoria total es de más de 448 bytes, pero el hecho de que solo estén disponibles 248 bytes provoca un problema de lectura de memoria fuera de límites.

¿Cómo se arregló?

Si revisamos el parche de diferencias, podemos ver que se realizaron los siguientes cambios para solucionar este problema:

Aquí el 2Dakota del Norte argumento para "AdquirirQuantumMemory"Se multiplica por 2 aumentando así la cantidad total de memoria y evitando este problema de lectura fuera de límites de la memoria del montón. La memoria total asignada es 496 bytes, 248 * 2 = 496 bytes, como podemos ver a continuación:

Otro problema con la solución:

Se lanzó una nueva versión de ImageMagick 7.0.46 para solucionar este problema. Si bien el parche soluciona el problema de asignación de memoria, si verificamos el código a continuación, podemos ver que hubo una llamada a memset que no estableció el tamaño de memoria adecuado en cero.

Se asignó memoria extensión * 2 * tamaño de (* strip_pixels) pero en esto memset a 0 solo fue hecho por extensión * tamaño de (* strip_pixels). Esto significa que la mitad de la memoria se estableció en 0 y el resto se incluyó 0xbebebebe, que es por defecto para la nueva asignación de memoria ASAN.

Desde entonces, esto se ha solucionado en versiones posteriores de ImageMagick mediante el uso de extensión = 2 * TIFFStripSize (tiff); en el siguiente parche:

https://github.com/ImageMagick/ImageMagick/commit/a5b64ccc422615264287028fe6bea8a131043b59#diff-0a5eef63b187504ff513056aa8fd6a7f5c1f57b6d2577a75cff428c0c7530978

Conclusión:

El procesamiento de varios archivos de imagen requiere una comprensión profunda de varios formatos de archivo y, por lo tanto, es posible que algo no se haya implementado exactamente o se haya perdido. Esto puede dar lugar a varias vulnerabilidades en dicho software de procesamiento de imágenes. Algunas de estas vulnerabilidades pueden provocar DoS y otras pueden provocar la ejecución remota de código que afecte a todas las instalaciones de este software tan popular.

Fuzzing juega un papel importante en la búsqueda de vulnerabilidades que los desarrolladores a menudo pasan por alto y durante las pruebas. En McAfee buscamos constantemente varios software de código cerrado y de código abierto para ayudar a protegerlos. Trabajamos muy de cerca con varios proveedores y hacemos una divulgación responsable. Esto muestra el compromiso de McAfee de asegurar el software y proteger a nuestros clientes de diversas amenazas.

Continuaremos difuminando varios software y trabajando con los proveedores para ayudar a mitigar los riesgos derivados de tales amenazas.

Nos gustaría agradecer y agradecer al equipo de ImageMagick por resolver rápidamente este problema en 24 horas y lanzar una nueva versión para solucionarlo.





Enlace a la noticia original