
empty_list - exploit para p0 issue 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w
empty_list - exploit para el problema p0 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w @i41nbeer
FALLO: getvolattrlist toma un argumento bufferSize controlado por el usuario a través de la llamada al sistema fgetattrlist.
Al asignar un búfer del kernel para serializar la lista de atributos, se encuentra el siguiente comentario:
/*
El problema es que el código no maneja correctamente el caso en que el tamaño del búfer proporcionado por el usuario es menor que el tamaño de encabezado solicitado. Si pasamos ATTR_CMN_RETURNED_ATTRS, llegaremos al siguiente código:
/* Return attribute set output if requested. / if (return_valid) { ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS; if (pack_invalid) { / Only report the attributes that are valid */ ab.actual.commonattr &= ab.valid.commonattr; ab.actual.volattr &= ab.valid.volattr; } bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual)); }
No hay ninguna comprobación de que el búfer asignado sea lo suficientemente grande para contener al menos eso.
Explotación: Espero publicar un análisis más extenso de esto; estas son algunas notas aproximadas sobre cómo funciona el exploit:
El error te da la capacidad de escribir 8 bytes cero al final de una asignación kalloc.16. Aunque parece que podrías controlar unos pocos bits en esos bytes, no estoy seguro de que realmente puedas, así que me centré en explotar como si estuviera escribiendo un puntero NULL al final.
Esta es una primitiva bastante limitada, así que el primer paso es intentar enumerar las posibles cosas que se podrían hacer:
Al final elegí la primera opción. Luego hay dos requisitos adicionales:
Elegí apuntar a la estructura ipc_port, que tiene un campo de contador de referencia como su segunda palabra, cumpliendo así el primer requisito. Sin embargo, no se asigna en kalloc.16; en su lugar vive en su propia zona (ipc_ports).
Esto significa que tenemos que alinear un bloque de zona kalloc.16 justo antes de uno de ipc_ports, luego desbordar desde la última asignación kalloc.16 en el bloque kalloc.16 hacia la primera en ipc_ports.
Hay dos trucos que podemos usar para facilitar esto:
Inversión de lista libre: Las asignaciones de zona vendrán primero de páginas intermedias (parcialmente llenas). Esto significa que si comenzamos a liberar y asignar objetos k.16 en algún lugar en medio del preparativo, no se reutilizarán hasta que la página intermedia actual esté llena o vacía.
esto presenta un desafío porque las listas libres de páginas nuevas se llenan semi-aleatoriamente de modo que sus asignaciones irán del interior al exterior:
| 9 8 6 5 2 1 3 4 7 10 | <-- example "randomized" allocation order from a fresh all-free page
esto significa que nuestras páginas intermedias finales de k.16 y puertos se verán algo así:
| - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | kalloc.16 ipc_ports
si usamos el desbordamiento para corromper una entrada de lista libre, entraremos en pánico si se asigna, por lo que debemos evitar eso
el truco es que al controlar el orden de asignación y liberación podemos invertir las listas libres de modo que las páginas intermedias finales se vean más así: | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | kalloc.16 ipc_ports
en este punto es mucho más probable que podamos liberar un kalloc.16 y reasignarlo para el desbordamiento de modo que podamos alcanzar la primera qword de un ipc_port.
Asignaciones seguras de desbordamiento: ya que es probable que haya muchas asignaciones candidatas de las que tendremos que desbordar antes de llegar a la objetivo (que está justo al final, justo antes del ipc_port), debemos asegurarnos de que los objetos asignados en la página kalloc.16 sean seguros de corromper con un puntero NULL.
Utilizo descriptores ool_port de mensajes de mach para esto, ya que NULL es un valor válido.
Flujo del exploit: Realizamos el preparativo para invertir las listas libres de kalloc.16 y comenzamos a intentar desbordar hacia un ipc_port.
Conocemos el rango aproximado de nombres de puertos mach que contienen el puerto a corromper; después de cada intento de desbordamiento, verificamos cada uno de estos puertos para ver si el puerto fue corrompido. Un efecto secundario de una corrupción exitosa es que la bandera io_active del puerto se establecerá en cero. Podemos detectar esto sin causar efectos secundarios usando el método MIG mach_port_kobject.
Una vez que encontramos el puerto corrompido, necesitamos hacer que se tome y se suelte una referencia sobre él; y más importante aún, necesitamos que la ruta de código que hace esto no verifique la bandera io_active. mach_port_set_attributes hará esto por nosotros.
Ahora hemos convertido nuestra escritura de puntero NULL al final de un kalloc.16 en un puerto mach colgante :)
Provocamos un gc de zona, con el objetivo de que la memoria del puerto se reutilice como una página kalloc.4096. Primero logramos que se reutilice como un descriptor ool_ports donde el campo ip_context se superpone con un derecho de envío que nos enviamos a nosotros mismos a un puerto canario. Esto nos permite aprender la dirección aproximada de nuestros objetos en el kernel. Luego reemplazamos el ool_desc con un búfer de pipe, y con un poco de manipulación podemos determinar dónde está el puerto mach colgante en la memoria.
Creamos un puerto de tarea de kernel falso allí y luego limpiamos.
Fiabilidad: El exploit funciona, que era mi objetivo :) La fiabilidad es algo así como un 30% tal vez, todo depende de qué tan rápido puedas hacer el desbordamiento inicial y el bucle de prueba. Si algo más entra y asigna o libera en kalloc.16, aumentas la probabilidad de que corrompas una entrada de lista libre u otra cosa y entres en pánico.
Estoy seguro de que el exploit puede hacerse más fiable; solo lo he llevado al punto en que he demostrado que este error es explotable. Si quieres tomar esto como punto de partida y demostrar cómo mejorar la fiabilidad, ¡me encantaría leer una publicación de blog! Imagino que esto implicaría monitorear realmente las asignaciones de kalloc.16 y comprender cuáles son los casos de fallo y cómo se pueden prevenir.
Las tasas de éxito parecen ser más altas cuando el dispositivo se ha reiniciado y se ha dejado inactivo por un tiempo.
Limpieza: Si el exploit funciona, debería limpiar por sí mismo y no provocar pánico en el dispositivo. El puerto de tarea de kernel falso permanecerá activo.
Usa las funciones en kmem.h para leer y escribir memoria del kernel. Persiste un derecho de envío a tfp0 allí si quieres mantener el acceso a la memoria del kernel después de que este proceso termine.
He probado en: iPod Touch 6G, iPhone 6S, iPhone SE, iPhone 7, iPhone 8 Debería funcionar en iOS 11 hasta iOS 11.3.1