Explotación del heap de paquetes NAT de VirtualBox Slirp

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

TL;DR

  • VirtualBox incluye un fork fuertemente modificado de Slirp cuyos buffers de paquetes (mbufs) residen en un asignador de zonas personalizado con metadatos en línea y callbacks por puntero a función (pfFini, pfDtor).
  • Un invitado puede reescribir el confiable m->m_len con una longitud de encabezado IP controlada por el atacante, lo que invalida todas las comprobaciones de límites posteriores y proporciona primitivas tanto de infoleak como de overwrite.
  • Abusando de paquetes UDP con checksum 0 y ip_len sobredimensionado, el invitado puede exfiltrar mbuf tails y los metadatos de chunks vecinos para obtener las direcciones del heap y de la zona.
  • Proveer opciones IP manipuladas fuerza a ip_stripoptions() a memcpy() demasiados datos in-place, por lo que el atacante puede sobrescribir el struct item header del siguiente mbuf y apuntar su campo zone a datos totalmente controlados.
  • Liberar el mbuf corrupto desencadena zone->pfFini() con argumentos suministrados por el atacante; apuntarlo a memcpy@plt proporciona una primitiva arbitraria de copy/write que puede dirigirse hacia entradas GOT u otros datos de control dentro del binario de VirtualBox no-PIE.

Anatomía del asignador de paquetes

VirtualBox asigna cada trama Ethernet entrante desde una zona por interfaz llamada zone_clust. Cada chunk de datos de 0x800 bytes va precedido por un encabezado en línea:

struct item {
uint32_t magic;      // 0xdead0001
void    *zone;       // uma_zone_t pointer with callbacks
uint32_t ref_count;
LIST_ENTRY(item) list; // freelist / used list links
};

Cuando se libera un mbuf, la pila de llamadas m_freem -> ... -> slirp_uma_free() confía en el encabezado inline:

  1. uma_zfree_arg() recomputa item = (struct item *)mem - 1 y debería validar item->zone, pero Assert() está omitido en compilaciones release.
  2. slirp_uma_free() carga zone = item->zone y ejecuta incondicionalmente zone->pfFini(zone->pData, data_ptr, zone->size) seguido de zone->pfDtor(...).

Por lo tanto, cualquier write-what-where en el encabezado del mbuf se traduce en una llamada indirecta controlada durante free().

Infoleak a través de m->m_len override

Al inicio de ip_input(), VirtualBox añadió:

if (m->m_len != RT_N2H_U16(ip->ip_len))
m->m_len = RT_N2H_U16(ip->ip_len);

Porque la asignación ocurre antes de verificar el encabezado IP, un guest puede anunciar cualquier longitud hasta 0xffff. El resto de la pila (ICMP, UDP, manejadores de fragmentación, etc.) asume que m->m_len es fiable y lo usa para decidir cuántos bytes copiar del mbuf.

Use paquetes UDP con checksum 0 (que significa “no checksum”). El NAT fast-path reenvía m->m_len bytes sin inspeccionar la integridad de la payload, así que inflar ip_len hace que Slirp lea más allá del buffer real y devuelva residuos del heap al guest o a un helper externo cooperante más allá del NAT. Debido a que el tamaño del chunk es 2048 bytes, el leak puede incluir:

  • El struct item inline del siguiente mbuf, revelando el orden del freelist y el puntero real zone.
  • Heap cookies como los campos magic, que ayudan a crear cabeceras de apariencia válida al realizar corrupciones más adelante.

Sobrescribir cabeceras de chunks vecinos con IP options

La misma longitud falsa puede convertirse en un primitivo de overwrite forzando que el paquete pase por ip_stripoptions() (se dispara cuando el encabezado IP tiene options y la carga útil es UDP/TCP). El helper calcula una longitud de copia a partir de m->m_len y luego llama a memcpy() para desplazar el transport header sobre las opciones eliminadas:

  1. Proporcionar un ip_len grande de modo que la longitud de movimiento computada se extienda más allá del mbuf actual.
  2. Incluir un pequeño número de IP options para que Slirp entre en la ruta de stripping.
  3. Cuando memcpy() se ejecuta, lee desde el mbuf siguiente y escribe sobre la payload y el inline header del mbuf actual, corrompiendo magic, zone, ref_count, etc.

Porque el allocator mantiene los paquetes de la misma interfaz contiguos en el freelist, este overflow golpea determinísticamente el siguiente chunk tras un modesto heap grooming.

Forjar uma_zone_t para secuestrar pfFini

Una vez que el struct item adyacente es corruptible, el exploit procede de la siguiente forma:

  1. Usar direcciones de heap leaked para construir una estructura falsa uma_zone dentro de un mbuf totalmente controlado por el guest. Rellenar:
  • pfFini con la entrada PLT de memcpy().
  • pData con el puntero de destino deseado (p.ej. entrada GOT, slot de vtable, array de punteros a funciones).
  • size con el número de bytes a copiar.
  • Opcional: pfDtor como llamada de segunda etapa (p.ej. para invocar el puntero a función recién escrito).
  1. Sobrescribir el campo zone del mbuf objetivo con el puntero a la estructura falsa; ajustar los punteros list para que la contabilidad del freelist permanezca lo bastante consistente como para evitar crashes.
  2. Liberar el mbuf. slirp_uma_free() ahora ejecuta memcpy(dest=pData, src=item_data, n=size) mientras el mbuf aún contiene datos controlados por el guest, produciendo una escritura arbitraria.

Puesto que el binario Linux de VirtualBox es non-PIE, las direcciones PLT para memcpy y system son fijas y pueden usarse directamente. El guest también puede almacenar cadenas como /bin/sh dentro de otro mbuf que permanezca referenciado cuando la llamada secuestrada se ejecute.

Heap grooming via fragmentation

La zona por-interfaz de Slirp tiene 3072 chunks de profundidad y inicialmente se talla como un array contiguo cuyo freelist se recorre desde direcciones altas hacia abajo. Se puede lograr adyacencia determinista mediante:

  • Inundar el NAT con muchos fragmentos IP_MF de tamaño constante para que el código de reensamblado asigne secuencias de mbufs predecibles.
  • Reciclar chunks específicos enviando fragments que expiren, forzando frees de vuelta al freelist en orden LIFO.
  • Usar el conocimiento del recorrido del freelist para colocar el futuro mbuf víctima justo después del mbuf que llevará el overflow de IP options.

Este grooming asegura que el overflow golpee el struct item objetivo y que la uma_zone falsa permanezca dentro de los límites del leak primitive.

From arbitrary write to host code execution

Con el memcpy-on-free primitive:

  1. Copiar una cadena /bin/sh controlada por el atacante y un buffer de comandos en un mbuf estable.
  2. Usar el primitive para sobrescribir una entrada GOT o un callsite indirecto (p.ej. un puntero a función dentro del estado del dispositivo NAT) con la entrada PLT de system().
  3. Disparar la llamada sobrescrita. Puesto que VirtualBox ejecuta el dispositivo NAT dentro del proceso del host, el payload se ejecuta con los privilegios del usuario que ejecuta VirtualBox, permitiendo un guest-to-host escape.

Payloads alternativos incluyen plantar una mini ROP chain en la memoria heap y copiar su dirección en un callback invocado frecuentemente, o reapuntar pfFini/pfDtor ellos mismos a gadgets encadenados para escrituras repetidas.

Referencias

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks