VMware Workstation PVSCSI LFH Escape (VMware-vmx on Windows 11)
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Anatomie du bug : realloc de taille fixe + écritures OOB dispersées
PVSCSI_FillSGIcopie les entrées scatter/gather du guest dans un tableau interne. Il démarre avec un buffer statique de 512 entrées (0x2000). Au-delà de 512 entrées il réalloue à 0x4000 octets et, à cause d’un bug fonctionnel, réalloue à chaque itération.- La taille de réallocation ne croît jamais : 0x4000 / entrées de 0x10 = 1024 entrées utilisables. Quand le guest fournit >1024 entrées, chaque nouvelle entrée est écrite 16 octets après le chunk 0x4000 fraîchement alloué, corrompant l’en-tête du chunk adjacent ou un objet adjacent.
- Contenu du overflow : VMware stocke
{u64 addr; u64 len}; le guest fournit{u64 addr; u32 len; u32 flags}. Lelen32-bit est zéro-étendu, donc le dernier dword de chaque élément OOB de 16 octets est toujours 0x00000000.
Contraintes LFH & placement déterministe “Ping-Pong”
- Les allocations 0x4000 tombent dans le LFH de Windows 11 (16 chunks/bucket, métadonnées de 0x10 octets avec checksum keyed). Tout chunk dont le checksum d’en-tête est touché ultérieurement fera terminer le process, donc les en-têtes corrompus ne doivent jamais être réutilisés.
- LFH retourne un chunk libre aléatoire, mais préfère le bucket contenant le chunk le plus récemment libéré. Forcer deux slots libres seulement :
- Allouer tous les chunks 0x4000 libres pour aligner l’allocator ; spray 32 SVGA shaders pour remplir les buckets B1 et B2.
- Free B1 sauf un shader épinglé (Hole0) pour que B1 reste actif ; allouer 15 URBs dans B1.
- Free un shader dans B2 (PONG), puis libérer immédiatement Hole0. LFH alternera les allocations entre les deux slots disponibles PING (B1) et PONG (B2).
- L’itération 1025 corrompt l’en-tête après PONG (jamais réutilisé) ; l’itération 1026 touche les premiers 16 octets de l’URB après PING (contournement sûr des métadonnées). Récupérer PING/PONG avec des shaders de placeholder pour garder le layout stable et répétable.
Reap Oracle : étiqueter les trous contigus
- Les UHCI URBs vivent dans une FIFO et sont freed quand elles sont entièrement reaped. L’écriture contrainte de 16 octets met toujours à zéro
actual_len, fournissant un marqueur. - Reaper les URBs dans l’ordre ; quand on voit un
actual_lenzéro, remplir immédiatement la slot libérée avec un shader reconnaissable. Itérer permet de cartographier Hole0–Hole3 comme quatre chunks contigus dans un ordre connu pour des primitives dépendantes d’adjacence.
Transformer des écritures contraintes en overwrite arbitraire (abus de coalescing)
PVSCSI coalesces les entrées adjacentes en utilisant AddrA + LenA == AddrB et compacte les entrées suivantes vers le haut.
- Overflow en deux passes : Déclencher à partir de PING (indices impairs) et sortir tôt pour éviter le coalescing ; déclencher à nouveau à partir de PONG (indices pairs) pour remplir les gaps et continuer à écrire dans un shader sprayé contenant de fausses entrées S/G.
- Vacuum + payload : Mettre les entrées
[1023..2047]à{addr=0,len=0}pour que le coalescing les écrase en une seule, créant un trou logique. Les entrées payload placées ensuite (dans le shader) sont déplacées vers le haut, atterrissant à l’intérieur de l’URB victime. - Contournement du check d’adjacence : En mettant
LenA=0, la condition devientAddrA==AddrB. Construire des paires
{addr = X, len = 0}
{addr = X, len = Y}
de sorte que le coalescing les fusionne en {addr=X,len=Y}. Les éléments de taille zéro d’indices pairs proviennent de l’overflow contraint ; les valeurs d’indices impairs vivent dans le shader. Résultat : motifs arbitraires de 16 octets malgré le dword forcé à zéro.
Infoleak URB hybride via effets secondaires de coalescence
- Arranger des chunks contigus :
[Hole0 (free/PING), URB1 (cible), URB2 (valide, actual_len=0), URB3 (leak target)]. - Remplir URB1 avec de fausses entrées contiguës (sizes
0xFFFFFFFF), en touchant URB2 minimalement. Le coalescing les fusionne en une seule entrée ; la somme0xFFFFFFFF * 0x401place le dword supérieur à l’offsetactual_lend’URB1 à 0x400. - La compaction copie les données suivantes vers le haut, rapatriant l’en-tête d’URB2 dans URB1. URB1 a maintenant un en-tête valide (pointeurs pipe/list),
actual_len=0x400, et un pointeur de données déjà à la fin du buffer d’URB2. - Reaper URB1 copie 0x400 octets commençant juste avant URB3, produisant une lecture OOB de l’en-tête/références self d’URB3, ce qui révèle des adresses absolues du heap et brise l’ASLR pour la fabrication de structures ultérieures.
Primitives post-leak (sans re-déclencher le bug)
- Forger une structure URB à l’intérieur d’un shader occupant Hole0, puis utiliser le “move up” du coalescing pour remplacer URB1 par les données forgées.
- Rendre l’URB persistante : set
URB1.next = Hole0et incrémenterrefcount; reaper URB1 place la fake URB backed by Hole0 en tête de la FIFO. Les primitives futures ne sont que des réallocations de Hole0 avec de nouvelles fake URBs. - Arbitrary read : fake URB avec
data_ptretactual_lenchoisis, puis reap pour copier la mémoire host vers le guest. - Arbitrary write (32-bit) : fake URB dont le
pipepointe vers de la mémoire contrôlée et abuser du UHCI TDBuffer writeback pour stocker un dword choisi à une adresse arbitraire. - Arbitrary call : écraser un callback de USB pipe ; l’hôte l’appelle avec des données contrôlées à
RCX+0x90. RésoudreWinExecdynamiquement (lecture guest de Kernel32) et pivoter via un gadget CFG-valid à l’intérieur de vmware-vmx qui charge les args depuisRCX+0x100avant d’appelerWinExec("calc.exe").
Side-channel de timing LFH pour apprendre l’offset initial du bucket
- Le Ping-Pong déterministe requiert de connaître l’offset du chunk libre LFH (quel slot parmi les 16 sera touché en premier). Utiliser l’instruction VMware backdoor (
inl %%dx, %%eax) avec la commande synchrone VMware Toolsvmx.capability.unified_loopet une chaîne de 0x4000 octets, ce qui force deux allocations 0x4000 par appel. - Chronométrer 8 appels (16 allocations) via
gettimeofday; un appel montre un pic constant quand LFH crée un nouveau bucket. Répéter avec une allocation supplémentaire : si le pic reste au même index l’offset est impair, s’il se décale il est pair ; sinon recommencer à cause du bruit. - Caveat :
unified_loopstocke des chaînes uniques dans une liste non libérable, causant une overhead O(n) de lookup et du bruit croissant, donc le side-channel doit converger rapidement.
References
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


