VMware Workstation PVSCSI LFH Escape (VMware-vmx on Windows 11)

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Anatomia do bug: realloc de tamanho fixo + escritas OOB dispersas

  • PVSCSI_FillSGI copia entradas scatter/gather do guest para um array interno. Começa com um buffer estático de 512 entradas (0x2000). Acima de 512 entradas ele realoca para 0x4000 bytes e, por causa de um bug funcional, realoca em cada iteração.
  • O tamanho da realocação nunca cresce: 0x4000 / entradas de 0x10 bytes = 1024 entradas utilizáveis. Quando o guest fornece >1024 entradas, cada nova entrada é escrita 16 bytes além do chunk 0x4000 recém-alocado, corrompendo o header do chunk adjacente ou o objeto.
  • Overflow content: VMware armazena {u64 addr; u64 len}; o guest fornece {u64 addr; u32 len; u32 flags}. O len de 32 bits é estendido com zeros, então o último dword de cada elemento OOB de 16 bytes é sempre 0x00000000.

Restrições do LFH & posicionamento determinístico “Ping-Pong”

  • Alocações de 0x4000 caem no Windows 11 LFH (16 chunks/bucket, metadata de 0x10 bytes com checksum keyed). Qualquer chunk cujo checksum do header for afetado depois fará o processo terminar, então headers corrompidos nunca devem ser reutilizados.
  • LFH retorna um chunk livre aleatório, mas prefere o bucket contendo o chunk liberado mais recentemente. Force apenas dois slots livres:
  1. Alocar todos os chunks livres de 0x4000 para alinhar o allocator; spray 32 SVGA shaders para preencher os buckets B1 e B2.
  2. Liberar B1 exceto por um shader fixo (Hole0) para que B1 permaneça ativo; alocar 15 URBs em B1.
  3. Liberar um shader em B2 (PONG) e, imediatamente, liberar Hole0. O LFH vai alternar alocações entre os dois slots disponíveis PING (B1) e PONG (B2).
  • A iteração 1025 corrompe o header depois de PONG (nunca tocado novamente); a iteração 1026 atinge os primeiros 16 bytes do URB depois de PING (bypass seguro de metadata). Reivindique PING/PONG com shaders placeholder para manter o layout estável e repetível.

Reap Oracle: rotulagem de buracos contíguos

  • UHCI URBs vivem em uma fila FIFO e são liberadas quando totalmente reaped. A sobrescrita restrita de 16 bytes sempre zera actual_len, fornecendo um marcador.
  • Reap os URBs em ordem; quando um actual_len zerado é observado, imediatamente preencha o slot liberado com um shader reconhecível. Iterar permite mapear Hole0–Hole3 como quatro chunks contíguos em ordem conhecida para primitivas dependentes de adjacência posteriores.

Transformando escritas restritas em overwrite arbitrário (abuso de coalescing)

PVSCSI coalesces entradas adjacentes usando AddrA + LenA == AddrB e compacta as entradas posteriores para cima.

  • Two-pass overflow: Dispare começando em PING (índices ímpares) e saia cedo para pular coalescing; dispare novamente começando em PONG (índices pares) para preencher as lacunas e continuar escrevendo em um shader sprayado contendo entradas S/G falsas.
  • Vacuum + payload: Configure as entradas [1023..2047] para {addr=0,len=0} para que o coalescing as colapse em uma só, criando um buraco lógico. Entradas de payload colocadas depois (no shader) são movidas para cima para memória anterior, pousando dentro do URB vítima.
  • Bypass da checagem de adjacência: Ao definir LenA=0, a condição vira AddrA==AddrB. Construa pares
{addr = X, len = 0}
{addr = X, len = Y}

para que o coalescing os una em {addr=X,len=Y}. Elementos de tamanho zero em índices pares vêm do overflow restrito; valores em índices ímpares vivem no shader. Resultado: padrões arbitrários de 16 bytes apesar do dword forçado zero.

Hybrid URB infoleak via efeitos colaterais do coalescing

  • Arranje chunks contíguos: [Hole0 (free/PING), URB1 (alvo), URB2 (válido, actual_len=0), URB3 (alvo de leak)].
  • Preencha URB1 com entradas falsas contíguas (tamanhos 0xFFFFFFFF), tocando minimamente URB2. O coalescing os funde em uma entrada; a soma 0xFFFFFFFF * 0x401 define o dword superior no offset actual_len de URB1 para 0x400.
  • A compactação copia os dados seguintes para cima, puxando o header de URB2 para dentro de URB1. URB1 agora tem um header válido (ponteiros pipe/list), actual_len=0x400, e um ponteiro de dados já no final do buffer de URB2.
  • Reaping de URB1 copia 0x400 bytes começando logo antes de URB3, produzindo uma leitura OOB do cabeçalho/autorreferências de URB3, o que revela endereços absolutos do heap e derrota ASLR para estruturas forjadas subsequentes.

Primitivas pós-leak (sem re-disparar o bug)

  • Forge um structure URB dentro de um shader ocupando Hole0, então use o “move up” do coalescing para substituir URB1 pelos dados forjados.
  • Faça o URB persistente: defina URB1.next = Hole0 e incremente refcount; reaping URB1 coloca o fake URB backed por Hole0 no head da FIFO. Primitivas futuras são apenas realocações de Hole0 com novos fake URBs.
  • Arbitrary read: URB fake com data_ptr e actual_len escolhidos, então reap para copiar memória do host para o guest.
  • Arbitrary write (32-bit): URB fake cujo pipe aponta para memória controlada e abuse do UHCI TDBuffer writeback para armazenar um dword escolhido em um endereço arbitrário.
  • Arbitrary call: sobrescreva um callback de pipe USB; o host o chama com dados controlados em RCX+0x90. Resolva WinExec dinamicamente (leitura guest-side de Kernel32) e pivote através de um gadget CFG-valid dentro de vmware-vmx que carrega args de RCX+0x100 antes de despachar para WinExec("calc.exe").

LFH timing side-channel para aprender o offset inicial do bucket

  • O Ping-Pong determinístico requer saber o offset do chunk livre do LFH (qual dos 16 slots será atingido primeiro). Use a instrução backdoor do VMware (inl %%dx, %%eax) com o comando síncrono do VMware Tools vmx.capability.unified_loop e uma string de 0x4000 bytes, que força duas alocações de 0x4000 por chamada.
  • Cronometre 8 chamadas (16 alocações) via gettimeofday; uma chamada mostra um spike consistente quando o LFH cria um novo bucket. Repita com uma alocação extra: se o spike permanecer no mesmo índice o offset é ímpar, se ele se deslocar é par; caso contrário reinicie devido ao ruído.
  • Caveat: unified_loop armazena strings únicas numa lista não liberável, causando overhead de lookup O(n) e aumentando o ruído, então o side-channel deve convergir rápido.

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks