He estado leyendo sobre un exploit anterior contra GDI + en Windows XP y Windows Server 2003 llamado JPEG de la muerte para un proyecto en el que estoy trabajando.
El exploit está bien explicado en el siguiente enlace: http://www.infosecwriters.com/text_resources/pdf/JPEG.pdf
Básicamente, un archivo JPEG contiene una sección llamada COM que contiene un campo de comentario (posiblemente vacío) y un valor de dos bytes que contiene el tamaño de COM. Si no hay comentarios, el tamaño es 2. El lector (GDI +) lee el tamaño, resta dos y asigna un búfer del tamaño adecuado para copiar los comentarios en el montón. El ataque implica poner un valor de 0
en el campo. GDI + resta 2
, lo que lleva a un valor del -2 (0xFFFe)
cual se convierte al entero sin signo 0XFFFFFFFE
pormemcpy
.
Código de muestra:
unsigned int size;
size = len - 2;
char *comment = (char *)malloc(size + 1);
memcpy(comment, src, size);
Observe que malloc(0)
en la tercera línea debe devolver un puntero a la memoria no asignada en el montón. ¿Cómo se puede escribir0XFFFFFFFE
bytes ( 4GB
!!!!) no bloquee el programa? ¿Esto escribe más allá del área del montón y en el espacio de otros programas y el sistema operativo? ¿Qué pasa entonces?
Según tengo entendido memcpy
, simplemente copia los n
caracteres del destino al origen. En este caso, el origen debe estar en la pila, el destino en el montón y n
es 4GB
.
malloc
tamaño ed es de solo 2 bytes en lugar de0xFFFFFFFE
. Este enorme tamaño solo se utiliza para el tamaño de la copia, no para el tamaño de asignación.Respuestas:
Esta vulnerabilidad fue definitivamente un desbordamiento de montón .
Probablemente lo hará, pero en algunas ocasiones tuvo tiempo para explotar antes de que ocurra el bloqueo (a veces, puede hacer que el programa vuelva a su ejecución normal y evitar el bloqueo).
Cuando se inicia memcpy (), la copia sobrescribirá algunos otros bloques del montón o algunas partes de la estructura de gestión del montón (por ejemplo, lista libre, lista de ocupados, etc.).
En algún momento, la copia encontrará una página no asignada y activará un AV (infracción de acceso) al escribir. Luego, GDI + intentará asignar un nuevo bloque en el montón (ver ntdll! RtlAllocateHeap ) ... pero las estructuras del montón ahora están desordenadas.
En ese momento, al crear cuidadosamente su imagen JPEG, puede sobrescribir las estructuras de administración de la pila con datos controlados. Cuando el sistema intente asignar el nuevo bloque, probablemente desvinculará un bloque (libre) de la lista libre.
Los bloques se gestionan con (notablemente) un flink (enlace de avance; el siguiente bloque de la lista) y un parpadeo (enlace de retroceso; el bloque anterior de la lista) punteros. Si controlas el parpadeo y el parpadeo, es posible que tengas una posible ESCRITURA4 (condición de escritura Qué / Dónde) donde controlas lo que puedes escribir y dónde puedes escribir.
En ese momento, puede sobrescribir un puntero de función ( SEH [Controladores de excepciones estructurados] eran un objetivo elegido en ese momento en 2004) y obtener la ejecución del código.
Ver la publicación de blog Corrupción en pilas: un estudio de caso .
Nota: aunque escribí sobre la explotación usando la lista libre, un atacante podría elegir otra ruta usando otros metadatos del montón ("metadatos del montón" son estructuras que utiliza el sistema para administrar el montón; flink y blink son parte de los metadatos del montón), pero la explotación de desvinculación es probablemente la más "fácil". Una búsqueda en Google de "explotación de montón" arrojará numerosos estudios sobre esto.
Nunca. Los sistemas operativos modernos se basan en el concepto de espacio de direcciones virtuales, por lo que cada proceso tiene su propio espacio de direcciones virtuales que permite direccionar hasta 4 gigabytes de memoria en un sistema de 32 bits (en la práctica, solo tiene la mitad en la tierra del usuario, el resto es para el kernel).
En resumen, un proceso no puede acceder a la memoria de otro proceso (excepto si lo solicita al kernel a través de algún servicio / API, pero el kernel verificará si la persona que llama tiene derecho a hacerlo).
Decidí probar esta vulnerabilidad este fin de semana, para que pudiéramos tener una buena idea de lo que estaba pasando en lugar de pura especulación. La vulnerabilidad tiene ahora 10 años, así que pensé que estaba bien escribir sobre ella, aunque no he explicado la parte de explotación en esta respuesta.
Planificación
La tarea más difícil fue encontrar un Windows XP con solo SP1, como lo fue en 2004 :)
Luego, descargué una imagen JPEG compuesta solo por un solo píxel, como se muestra a continuación (corte por brevedad):
Una imagen JPEG se compone de marcadores binarios (que introducen segmentos). En la imagen de arriba,
FF D8
es el marcador SOI (Start Of Image), mientras queFF E0
, por ejemplo, es un marcador de aplicación.El primer parámetro en un segmento de marcador (excepto algunos marcadores como SOI) es un parámetro de longitud de dos bytes que codifica el número de bytes en el segmento de marcador, incluido el parámetro de longitud y excluyendo el marcador de dos bytes.
Simplemente agregué un marcador COM (0x
FFFE
) justo después del SOI, ya que los marcadores no tienen un orden estricto.La longitud del segmento COM está configurada
00 00
para activar la vulnerabilidad. También inyecté 0xFFFC bytes justo después del marcador COM con un patrón recurrente, un número de 4 bytes en hexadecimal, que será útil cuando "explote" la vulnerabilidad.Depuración
Hacer doble clic en la imagen activará inmediatamente el error en el shell de Windows (también conocido como "explorer.exe"), en algún lugar
gdiplus.dll
, en una función llamadaGpJpegDecoder::read_jpeg_marker()
.Esta función se llama para cada marcador en la imagen, simplemente: lee el tamaño del segmento del marcador, asigna un búfer cuya longitud es el tamaño del segmento y copia el contenido del segmento en este búfer recién asignado.
Aquí el inicio de la función:
eax
puntos de registro al tamaño del segmento yedi
es el número de bytes que quedan en la imagen.Luego, el código procede a leer el tamaño del segmento, comenzando por el byte más significativo (la longitud es un valor de 16 bits):
Y el byte menos significativo:
Una vez hecho esto, el tamaño del segmento se utiliza para asignar un búfer, siguiendo este cálculo:
alloc_size = tamaño_segmento + 2
Esto se hace mediante el siguiente código:
En nuestro caso, como el tamaño del segmento es 0, el tamaño asignado para el búfer es 2 bytes .
La vulnerabilidad está justo después de la asignación:
El código simplemente resta el tamaño del tamaño del segmento (la longitud del segmento es un valor de 2 bytes) del tamaño del segmento completo (0 en nuestro caso) y termina con un subdesbordamiento entero: 0 - 2 = 0xFFFFFFFE
Luego, el código verifica si quedan bytes para analizar en la imagen (lo cual es cierto), y luego salta a la copia:
El fragmento anterior muestra que el tamaño de la copia es 0xFFFFFFFE fragmentos de 32 bits. El búfer de origen está controlado (contenido de la imagen) y el destino es un búfer en el montón.
Condición de escritura
La copia activará una excepción de infracción de acceso (AV) cuando llegue al final de la página de memoria (esto podría ser desde el puntero de origen o el puntero de destino). Cuando se activa el AV, el montón ya está en un estado vulnerable porque la copia ya ha sobrescrito todos los siguientes bloques de montón hasta que se encuentra una página no mapeada.
Lo que hace que este error sea explotable es que 3 SEH (manejador de excepciones estructurado; esto es try / excepto en el nivel bajo) están detectando excepciones en esta parte del código. Más precisamente, el 1st SEH desenrollará la pila para que vuelva a analizar otro marcador JPEG, omitiendo así por completo el marcador que desencadenó la excepción.
Sin un SEH, el código habría bloqueado todo el programa. Entonces, el código omite el segmento COM y analiza otro segmento. Entonces volvemos
GpJpegDecoder::read_jpeg_marker()
con un nuevo segmento y cuando el código asigna un nuevo búfer:El sistema desvinculará un bloque de la lista libre. Sucede que las estructuras de metadatos fueron sobrescritas por el contenido de la imagen; por lo que controlamos la desvinculación con metadatos controlados. El siguiente código en algún lugar del sistema (ntdll) en el administrador de almacenamiento dinámico:
Ahora podemos escribir lo que queramos, donde queramos ...
fuente
Como no conozco el código de GDI, lo que se muestra a continuación es solo una especulación.
Bueno, una cosa que me viene a la mente es un comportamiento que he notado en algunos sistemas operativos (no sé si Windows XP tenía esto) fue al asignar con nuevo /
malloc
, en realidad puede asignar más que su RAM, siempre y cuando no escribes en esa memoria.Este es en realidad un comportamiento del kernel de Linux.
De www.kernel.org:
Para entrar en la memoria residente, se debe activar un error de página.
Básicamente, necesita ensuciar la memoria antes de que realmente se asigne en el sistema:
A veces, en realidad, no hará una asignación real en RAM (su programa aún no usará 4 GB). Sé que he visto este comportamiento en Linux, pero no puedo replicarlo ahora en mi instalación de Windows 7.
A partir de este comportamiento es posible el siguiente escenario.
Para que esa memoria exista en la RAM, debe ensuciarla (básicamente memset o alguna otra escritura en ella):
Sin embargo, la vulnerabilidad explota un desbordamiento del búfer, no un error de asignación.
En otras palabras, si tuviera esto:
Esto dará lugar a una escritura después del búfer, porque no existe un segmento de memoria continua de 4 GB.
No pusiste nada en p para ensuciar los 4 GB de memoria, y no sé si
memcpy
ensucia la memoria de una vez, o solo página por página (creo que es página por página).Eventualmente terminará sobrescribiendo el marco de la pila (Stack Buffer Overflow).
Otra vulnerabilidad más posible era si la imagen se guardaba en la memoria como una matriz de bytes (leer el archivo completo en el búfer) y el tamaño de los comentarios se usaba solo para saltar información no vital.
Por ejemplo
Como mencionaste, si el GDI no asigna ese tamaño, el programa nunca fallará.
fuente
malloc(-1U)
que seguramente fallará, regresaráNULL
ymemcpy()
se estrellará.