
VirtualBox E1000 Évasion invité-vers-hôte
J'aime VirtualBox et cela n'a rien à voir avec le fait que je publie une vulnérabilité 0day. La raison est mon désaccord avec l'état actuel de l'infosec, en particulier de la recherche en sécurité et du bug bounty :
J'en ai marre des deux premiers, donc mon geste est une divulgation complète. Infosec, avancez s'il vous plaît.
Logiciel vulnérable : VirtualBox 5.2.20 et versions antérieures.
OS hôte : n'importe lequel, le bug se trouve dans une base de code partagée.
OS invité : n'importe lequel.
Configuration de la VM : par défaut (la seule exigence est qu'une carte réseau soit une Intel PRO/1000 MT Desktop (82540EM) et que le mode soit NAT).
Jusqu'à ce que la version corrigée de VirtualBox soit disponible, vous pouvez changer la carte réseau de vos machines virtuelles pour PCnet (l'une des deux) ou pour Paravirtualized Network. Si vous ne pouvez pas, changez le mode de NAT vers un autre. La première méthode est plus sûre.
Un périphérique réseau virtuel par défaut de VirtualBox est l'Intel PRO/1000 MT Desktop (82540EM) et le mode réseau par défaut est NAT. Nous l'appellerons E1000.
L'E1000 présente une vulnérabilité permettant à un attaquant disposant de privilèges root/administrateur dans un invité de s'échapper vers un ring3 de l'hôte. L'attaquant peut ensuite utiliser des techniques existantes pour élever les privilèges jusqu'au ring 0 via /dev/vboxdrv.
Pour envoyer des paquets réseau, un invité fait ce qu'un PC ordinaire fait : il configure une carte réseau et lui fournit des paquets réseau. Les paquets sont des trames de la couche de liaison de données et d'autres en-têtes de plus haut niveau. Les paquets fournis à l'adaptateur sont enveloppés dans des descripteurs Tx (Tx signifie transmission). Le descripteur Tx est une structure de données décrite dans la fiche technique 82540EM (317453006EN.PDF, Révision 4.0). Il stocke des métadonnées telles que la taille du paquet, la balise VLAN, les indicateurs de segmentation TCP/IP activés, etc.
La fiche technique 82540EM prévoit trois types de descripteurs Tx : legacy, context, data. Legacy est obsolète, je crois. Les deux autres sont utilisés ensemble. La seule chose qui nous importe est que les descripteurs context définissent la taille maximale du paquet et activent la segmentation TCP/IP, et que les descripteurs data contiennent les adresses physiques des paquets réseau et leurs tailles. La taille de paquet du descripteur data doit être inférieure à la taille maximale du paquet du descripteur context. En général, les descripteurs context sont fournis à la carte réseau avant les descripteurs data.
Pour fournir des descripteurs Tx à la carte réseau, un invité les écrit dans le Tx Ring. Il s'agit d'un tampon en anneau résidant en mémoire physique à une adresse prédéfinie. Lorsque tous les descripteurs sont écrits dans le Tx Ring, l'invité met à jour le registre MMIO TDT de l'E1000 (Transmit Descriptor Tail) pour indiquer à l'hôte qu'il y a de nouveaux descripteurs à traiter.
Considérez le tableau suivant de descripteurs Tx :``` [context_1, data_2, data_3, context_4, data_5]
Attribuons leurs champs de structure comme suit (les noms de champs sont hypothétiques pour être lisibles par un humain mais correspondent directement à la spécification 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
Nous apprendrons pourquoi ils doivent être ainsi lors de notre analyse pas à pas.
Supposons que les descripteurs ci-dessus soient écrits dans la Tx Ring dans l'ordre spécifié et que le registre TDT soit mis à jour par le guest. Le host exécutera alors la fonction e1kXmitPending dans le fichier src/VBox/Devices/Network/DevE1000.cpp (la plupart des commentaires sont et seront retirés pour des raisons de lisibilité) :```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 lira les 5 descripteurs Tx du Tx Ring. Ensuite, e1kLocateTxPacket est appelé pour la première fois. Cette fonction parcourt tous les descripteurs pour établir un état initial, mais ne les traite pas réellement. Dans notre cas, le premier appel à e1kLocateTxPacket traitera les descripteurs context_1, data_2 et data_3. Les deux descripteurs restants, context_4 et data_5, seront traités lors de la deuxième itération de la boucle while (nous aborderons la deuxième itération dans la section suivante). Cette division du tableau en deux parties est cruciale pour déclencher la vulnérabilité, donc voyons pourquoi.