
Escape de invitado a anfitrión de VirtualBox E1000
Me gusta VirtualBox y no tiene nada que ver con por qué publico una vulnerabilidad 0day. La razón es mi desacuerdo con el estado contemporáneo de la infosec, especialmente de la investigación de seguridad y el bug bounty:
Estoy agotado de los dos primeros, por lo que mi movimiento es la divulgación completa. Infosec, por favor avanza.
Software vulnerable: VirtualBox 5.2.20 y versiones anteriores.
SO anfitrión: cualquiera, el bug está en una base de código compartida.
SO invitado: cualquiera.
Configuración de la VM: predeterminada (el único requisito es que la tarjeta de red sea Intel PRO/1000 MT Desktop (82540EM) y el modo sea NAT).
Hasta que salga la compilación de VirtualBox parcheada, puedes cambiar la tarjeta de red de tus máquinas virtuales a PCnet (a cualquiera de las dos) o a Paravirtualized Network. Si no puedes, cambia el modo de NAT a otro. La primera opción es más segura.
Un dispositivo de red virtual predeterminado de VirtualBox es Intel PRO/1000 MT Desktop (82540EM) y el modo de red predeterminado es NAT. Nos referiremos a él como E1000.
El E1000 tiene una vulnerabilidad que permite a un atacante con privilegios de root/administrador en un invitado escapar al ring3 del anfitrión. Luego, el atacante puede usar técnicas existentes para escalar privilegios al ring 0 a través de /dev/vboxdrv.
Para enviar paquetes de red, un invitado hace lo que hace una PC común: configura una tarjeta de red y le suministra paquetes de red. Los paquetes son tramas de la capa de enlace de datos y otros encabezados de nivel superior. Los paquetes suministrados al adaptador se envuelven en descriptores Tx (Tx significa transmitir). El descriptor Tx es una estructura de datos descrita en la hoja de datos del 82540EM (317453006EN.PDF, Revision 4.0). Almacena metainformación como el tamaño del paquete, la etiqueta VLAN, los indicadores de segmentación TCP/IP habilitados, etc.
La hoja de datos del 82540EM contempla tres tipos de descriptores Tx: legacy, contexto y datos. Legacy está obsoleto, creo. Los otros dos se usan juntos. Lo único que nos importa es que los descriptores de contexto establecen el tamaño máximo de paquete y activan la segmentación TCP/IP, y que los descriptores de datos contienen las direcciones físicas de los paquetes de red y sus tamaños. El tamaño de paquete del descriptor de datos debe ser menor que el tamaño máximo de paquete del descriptor de contexto. Normalmente, los descriptores de contexto se suministran a la tarjeta de red antes que los descriptores de datos.
Para suministrar descriptores Tx a la tarjeta de red, un invitado los escribe en Tx Ring. Este es un búfer circular que reside en la memoria física en una dirección predefinida. Cuando todos los descriptores se han escrito en Tx Ring, el invitado actualiza el registro TDT MMIO del E1000 (Transmit Descriptor Tail) para indicar al anfitrión que hay nuevos descriptores que procesar.
Considera el siguiente arreglo de descriptores Tx:``` [context_1, data_2, data_3, context_4, data_5]
Asignemos sus campos de estructura de la siguiente manera (los nombres de los campos son hipotéticos para que sean legibles por humanos, pero se asignan directamente a la especificación 82540EM):```
context_1.header_length = 0
context_1.maximum_segment_size = 0x3010
context_1.tcp_segmentation_enabled = true
data_2.data_length = 0x10
data_2.end_of_packet = false
data_2.tcp_segmentation_enabled = true
data_3.data_length = 0
data_3.end_of_packet = true
data_3.tcp_segmentation_enabled = true
context_4.header_length = 0
context_4.maximum_segment_size = 0xF
context_4.tcp_segmentation_enabled = true
data_5.data_length = 0x4188
data_5.end_of_packet = true
data_5.tcp_segmentation_enabled = true
Aprenderemos por qué deberían ser así en nuestro análisis paso a paso.
Supongamos que los descriptores anteriores se escriben en el Tx Ring en el orden especificado y el registro TDT es actualizado por el invitado. Ahora el anfitrión ejecutará la función e1kXmitPending en el archivo src/VBox/Devices/Network/DevE1000.cpp (la mayoría de los comentarios se omiten y se omitirán en aras de la legibilidad):```c static int e1kXmitPending(PE1KSTATE pThis, bool fOnWorkerThread) { ... while (!pThis->fLocked && e1kTxDLazyLoad(pThis)) { while (e1kLocateTxPacket(pThis)) { fIncomplete = false; rc = e1kXmitAllocBuf(pThis, pThis->fGSO); if (RT_FAILURE(rc)) goto out; rc = e1kXmitPacket(pThis, fOnWorkerThread); if (RT_FAILURE(rc)) goto out; }
e1kTxDLazyLoad leerá los 5 descriptores Tx del anillo Tx. Luego se llama a e1kLocateTxPacket por primera vez. Esta función itera a través de todos los descriptores para establecer un estado inicial, pero no los maneja realmente. En nuestro caso, la primera llamada a e1kLocateTxPacket manejará los descriptores context_1, data_2 y data_3. Los dos descriptores restantes, context_4 y data_5, se manejarán en la segunda iteración del bucle while (cubriremos la segunda iteración en la siguiente sección). Esta división del arreglo en dos partes es crucial para desencadenar la vulnerabilidad, así que averigüemos por qué.
e1kLocateTxPacket se ve así:```c
static bool e1kLocateTxPacket(PE1KSTATE pThis)
{
...
for (int i = pThis->iTxDCurrent; i < pThis->nTxDFetched; ++i)
{
E1KTXDESC *pDesc = &pThis->aTxDescriptors[i];
switch (e1kGetDescType(pDesc))
{
case E1K_DTYP_CONTEXT:
e1kUpdateTxContext(pThis, pDesc);
continue;
case E1K_DTYP_LEGACY:
...
break;
case E1K_DTYP_DATA:
if (!pDesc->data.u64BufAddr || !pDesc->data.cmd.u20DTALEN)
break;
...
break;
default:
AssertMsgFailed(("Impossible descriptor type!"));
}
El primer descriptor (context_1) es de tipo E1K_DTYP_CONTEXT, por lo que se llama a la función e1kUpdateTxContext. Esta función actualiza un contexto de segmentación TCP si la segmentación TCP está habilitada para el descriptor. Esto es cierto para context_1, por lo que el contexto de segmentación TCP se actualizará. (Lo que realmente es la actualización del contexto de segmentación TCP no es importante, y lo usaremos solo para referirnos al código siguiente).
El segundo descriptor (data_2) es de tipo E1K_DTYP_DATA, por lo que se realizarán varias acciones innecesarias para la discusión.