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
CVE-2026-54107 — Análise de causa-raiz da CVE-2026-54107: um use-after-free no Windows win32kfull.sys com depuração de condição de corrida, análise estática, insights de triagem do MSRC e pesquisa prática de exploração de kernel. | Kitploit
Ferramentas/GitHubGitHub/pravin761/cve-2026-54107
Análise EstáticaAnálise de VulnerabilidadesExploraçãoEngenharia ReversaDepuradoresPapers e PesquisaAprendizado e EducaçãoExploração de Binários
GitHubpravin761/cve-2026-54107

CVE-2026-54107

Análise de causa-raiz da CVE-2026-54107: um use-after-free no Windows win32kfull.sys com depuração de condição de corrida, análise estática, insights de triagem do MSRC e pesquisa prática de exploração de kernel.

Ver Repositório
13há 1 mêsAinda não revisado

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

Quando ValidateHwnd Não É uma Barreira: Causa-Raiz da CVE-2026-54107

Um use-after-free no gerenciamento do ciclo de vida de janelas em win32kfull.sys — como o encontrei, como me convenci de que era real e como o processo do MSRC realmente se parecia do lado do pesquisador.

CVE-2026-54107 CWE-362 CVSS 8.8 MSRC 11xxxxx Bounty $8,000

Eram quase 2 da manhã quando a VM alvo parou de responder ao heartbeat do depurador e caiu em um break exatamente na instrução que eu passei semanas argumentando que era alcançável. Não uma asserção, não uma parada de pool corrompido — uma simples violação de acesso no caminho de despacho de mensagens, desreferenciando um objeto que outra thread já havia destruído.

Esse break se tornou a , caso do MSRC, corrigida na em 27 produtos Windows.

kd> !pool 2
CVE-2026-54107
11xxxxx
Atualização de Segurança de julho de 2026

Este post é a metade não embargada da história: causa raiz, por que a classe de bug é o que é e o raciocínio que me levou até lá. Detalhes de exploração ficam de fora.

Sumário

  • 1. Por que win32k e, especificamente, objetos de janela
  • 2. O cheiro que me fez parar
  • 3. Causa raiz
  • 4. Por que a classificação de impacto é o que é
  • 5. A falsificação veio primeiro — a maioria dos candidatos morreu
  • 6. Verificação: a estática dá hipóteses, o depurador dá a verdade
  • 7. Sobre usar IA em pesquisa de kernel
  • 8. A linha do tempo do MSRC, honestamente
  • 9. Resumo
  • 10. O que eu diria a alguém que está começando
  • 11. O que vem a seguir

1. Por que win32k e, especificamente, objetos de janela

Win32k é a metade em modo kernel do subsistema gráfico do Windows. É antigo, é enorme e — criticamente — é alcançável a partir de contextos que deveriam ser não confiáveis. Essa última propriedade é a razão pela qual continua sendo um alvo permanente de pesquisa apesar de vinte anos de endurecimento, filtragem e restrição de chamadas de sistema.

Dentro do win32k, o objeto tagWND (PWND) é excepcionalmente interessante porque seu tempo de vida é gerenciado por mais de um mecanismo ao mesmo tempo. Uma janela é:

  • referenciada por handle, por meio da tabela de handles do usuário e buscas no estilo ValidateHwnd,
  • referenciada por ponteiro, mantida em chamadas aninhadas e no despacho de mensagens,
  • referenciada implicitamente pelas relações pai/filho, dono/propriedade e thread/desktop,
  • e destruída por meio de um caminho de destruição que precisa desfazer todos os itens acima na ordem correta.

Qualquer objeto com vários caminhos de referência independentes e um caminho de destruição compartilhado vale a pena ser lido com calma. Isso não é uma afirmação de vulnerabilidade — é uma heurística para onde gastar tempo.

2. O cheiro que me fez parar

O que me fez parar nesse componente foi a superfície de importação. O win32kfull.sys importa três primitivas distintas de referência de objeto do ntoskrnl:```c NTSTATUS ObReferenceObjectByPointer( void *Object, uint32_t DesiredAccess, POBJECT_TYPE ObjectType, char AccessMode);

NTSTATUS ObReferenceObjectByHandle( HANDLE Handle, uint32_t DesiredAccess, POBJECT_TYPE ObjectType, char AccessMode, void **Object, OBJECT_HANDLE_INFORMATION *HandleInformation);

NTSTATUS ObReferenceObjectByName(/* ... */);

root@kitploit:~
Três formas de entrada, um caminho de saída `ObfDereferenceObject`.

Isso não significa que o código esteja errado. Significa que o **invariante é distribuído** — nenhuma função individual é dona de *"este objeto está vivo agora"*, portanto a corretude depende de cada chamador concordar sobre qual referência ele detém e por quanto tempo ela é válida. Invariantes distribuídos são onde as condições de corrida moram, porque uma corrida nunca é um bug de lógica que você pode ver em uma única função. É um bug em uma suposição mantida entre duas.

Então a pergunta que comecei a fazer a toda função que tocava em um `PWND` não era *"este código está correto?"* mas sim:

> **Se exatamente este corpo de função for executado em duas threads com algumas instruções de diferença, qual delas está errada?**



## 3. Causa raiz

O defeito é uma **lacuna de tempo de verificação / tempo de uso entre a liberação da referência e a destruição do objeto** no caminho de destruição da janela, sem sincronização adequada contra um consumidor concorrente que valida handles.

Reduzido à sua forma:```c
/* Thread A — teardown */
NtUserDestroyWindow(HWND hwnd)
{
    PWND pWnd = ValidateHwnd(hwnd);
    if (pWnd) {
        ObfDereferenceObject(pWnd);   /* reference released */

        /* <-- race window: object may become reclaimable here */

        FreeWindowObject(pWnd);       /* teardown proceeds on a pointer
                                         no longer guaranteed live      */
    }
}
  • Ambiente Diversos

  • Análise de Malware

  • Análise de Dados

  • Análise de Logs

  • Análise de Memória

  • Captura de Pacotes

  • Scanners

  • ModSecurity e OWASP CRS

  • Inteligência de Ameaças

    • DNA
    • Firewall de Aplicações Web
    • Honeypots / Decepção
    • DFIR
    • CTFs e Wargames
    • Sandboxing / Reversing
    • Fingerprinting
    • Avaliação de Vulnerabilidades
    • Ataques de Rede
    • Relatórios e Modelos
    • Phishing / Engenharia Social
    • Privacidade
    • Diversos```c /* Thread B — consumer, concurrent / NtUserMessageCall(HWND hwnd, UINT msg, ...) { PWND pWnd = ValidateHwnd(hwnd); / may resolve a handle whose object is mid-teardown */

    if (pWnd->fnid == FNID_BUTTON) /* use-after-free */ ... }

root@kitploit:~
Two things have to be true for this to matter, and both were:

**(a) The window is real.** `ValidateHwnd` is the gate that's supposed to make handle-based access safe. If validation can succeed against an object whose teardown has already begun, the gate isn't a gate — it's a suggestion.

**(b) The freed memory is attacker-influenceable.** The fields read immediately after validation include `fnid`, which drives message dispatch. A dispatch decision made from reclaimed memory is the difference between *"unreliable crash"* and *"security boundary violation."* That distinction is the entire reason this is CWE-362 with EoP impact and not a stability bug.

> The observed **corruption** is a use-after-free; the **cause** is CWE-362, concurrent execution using a shared resource with improper synchronization. Those are two different statements and MSRC cares about the second one. **Report the cause, not just the symptom.**

### Why win32k races are structurally harder than they look

If you've raced bugs in other subsystems, win32k will frustrate you, because the architecture fights you in three specific ways.

**Windows have thread affinity.** A window belongs to the thread that created it. A lot of the subsystem is built around the assumption that the owning thread is the one touching the object, which means the naive "spin two threads calling the same API" approach frequently doesn't overlap anything — you're not racing, you're queueing. Getting two paths to genuinely collide on the same object requires understanding which operations actually execute on the caller's thread versus which get marshalled to the owner's.

**Message dispatch partially serializes you.** Sends and posts behave differently, and cross-thread versus same-thread dispatch behave differently again. Some of what looks like a concurrency opportunity is silently converted into an ordered operation before it ever reaches the code you care about. If you don't know which category your trigger falls into, you'll conclude a real race is unreachable — a false negative that looks identical to "no bug here."

**The critical section hides in the caller.** Much of the subsystem runs under a coarse lock acquired well above the function you're staring at. This is the single biggest source of wasted time in win32k auditing: a function with no visible synchronization that is nonetheless perfectly safe because every path into it is already serialized. **Lock coverage is an interprocedural property.** You must walk up the call graph, not just read the function.

That third point is why *"no lock in this function"* is worth almost nothing as a signal, and why most of the work in this hunt was spent on reachability rather than on the defect itself.


## 4. Why the impact rating is what it is

MSRC assessed this as **Important, Elevation of Privilege, CVSS 8.8, attack vector local, authenticated.** Two properties drive that:

**Reachability from low integrity.** Win32k message-call surface is reachable from contexts far below SYSTEM. That's what makes it relevant to sandbox-escape chains — a renderer process that has already achieved code execution inside its sandbox can still reach this surface. A kernel bug's severity is mostly a function of *who can touch it*, not how clever the corruption is.

**Dispatch-influencing corruption.** Corrupting a field that a `switch` runs on is qualitatively worse than corrupting a field that only gets logged. The former turns a memory bug into a control-flow question.

I want to be precise about something here, because I've seen first-CVE posts overstate this: **I demonstrated the race and the use-after-free. I did not ship a weaponized SYSTEM-level exploit.** The sandbox-escape framing describes the *class of chain* this bug type belongs to and why the surface is valuable — it is an argument about reachability, not a claim that I built one. Overclaiming impact is the fastest way to burn credibility with a vendor, and MSRC's assessment is the number that matters, not mine.



## 5. Falsification came first — most candidates died

The part nobody writes about: this was not the first candidate. It was the one that survived.

My working rule is that **a candidate is guilty until proven guilty.** Every promising pattern gets a specific, written-down reason it *shouldn't* be exploitable, and I go try to establish that reason before I go try to trigger it. Candidates I closed before this one included:

- paths that looked unsynchronized but were serialized by a lock acquired one frame up,
- paths where the "freed" object was actually cached rather than released,
- paths that were genuinely racy but not reachable from any caller a low-privilege user could drive.

Every one of those is a finding I *did not* send to MSRC. That's the point. A researcher's throughput isn't how many candidates they generate — it's how fast they can kill the wrong ones so they're not still holding them at 2 AM.

**The three questions that killed most candidates:**

1. **Is anything above me holding a lock?** Interprocedural, not local. The absence of a lock in a function means nothing.
2. **Can an unprivileged caller actually reach both sides?** A race between two paths that require different privilege levels isn't a race, it's a thought experiment.
3. **Is the freed memory reclaimable in a window I can influence?** If teardown completes atomically for practical purposes, there's no bug worth reporting.



## 6. Verification: static gives hypotheses, the debugger gives truth

Static analysis of `win32kfull.sys` gave me the hypothesis. It could never give me the bug. **Race conditions aren't visible in a decompiler** because the defect isn't in the instructions — it's in the interleaving.

### Lab

| Role | Setup |
| --- | --- |
| Host / debugger | Windows 11, WinDbg |
| Target | Windows Server 2022, Build 20348.2159 |
| Analysis | Kali Linux + Windows 11 VM |
| Debug transport | VMware serial COM, host → target kernel debugging |
| Static analysis | Ghidra via GhidraMCP |
| Triage assist | AI-assisted pass over decompiled output |

Three instrumentation layers did the actual work.

### Special pool and Driver Verifier

The single highest-leverage step in any kernel UAF investigation. By default, freed pool memory is reused almost immediately by the next allocation of a similar size — which means a use-after-free usually *doesn't fault*. It reads someone else's valid data, keeps executing, and detonates somewhere unrelated minutes later. You then spend three days auditing an innocent function.

Special pool changes that. Each allocation gets its own page with a guard page adjacent, and freed pages are marked no-access rather than recycled. The result is that the offending dereference faults **at the instruction that performs it**, not downstream:```
!verifier 0x1 win32kfull.sys        ; special pool on the target driver
!verifier 0x8 win32kfull.sys        ; pool tracking

Combinado com a filtragem por tag de pool, é isso que transforma "bugcheck intermitente sob carga" em uma falha reproduzível e atribuível.

Se você tirar apenas uma coisa deste post: habilite o special pool antes de começar, não depois de ficar preso.

Análise forense do pool na falha

Quando você tem uma falha, a questão é se está diante de corrupção ou de um bug de tempo de vida. Elas exigem relatórios diferentes. Os metadados do pool respondem isso:``` kd> !pool

root@kitploit:~
Um bloco que foi *alocado* com uma tag plausível e conteúdo de lixo aponta para **corrupção**. Um bloco que foi *liberado*, ou que está em uma página sem acesso do pool especial, aponta para um **bug de tempo de vida** — algo segurou um ponteiro além da morte do objeto. Essa é a distinção entre *"o atacante escreveu aqui"* e *"este objeto não deveria ter sido alcançável"*, e é a diferença entre um relatório de corrupção de heap e um relatório CWE-362.

Faça a verificação cruzada do tipo de objeto antes de se comprometer com qualquer um dos casos. Um `PWND` tem um formato reconhecível; se a memória onde a falha ocorreu ainda carrega os remanescentes de um, você está quase certamente diante de um problema de janela de tempo de vida, e não de uma sobrescrita aleatória.

### Depuração ao vivo do kernel na intercalação

Mesmo com o pool especial, uma corrida é um problema de agendamento, e **o depurador altera o agendamento.** Essa é a principal frustração do trabalho com corridas: o instrumento perturba aquilo que mede. Um breakpoint no caminho de teardown serializa exatamente as duas threads que você está tentando sobrepor, e o bug desaparece educadamente.

O caminho para contornar isso é parar de tentar capturar a corrida com um breakpoint e, em vez disso:

- **alargar a janela artificialmente** — qualquer coisa que prolongue o intervalo entre a liberação da referência e o teardown torna a colisão alcançável em taxas normais de agendamento,
- **aumentar as tentativas de colisão em vez da precisão** — execute os dois caminhos continuamente e deixe a probabilidade fazer o trabalho,
- **usar breakpoints condicionais e de disparo único** que só armam quando o estado interessante existe, em vez de parar em toda entrada,
- **confirmar a intercalação após a falha**, a partir do estado das threads e das pilhas, em vez de tentar observá-la ao vivo.

A falha em si, quando você a obtém, é sem glamour — uma desreferência de um `PWND` no caminho de despacho de mensagens em que o objeto já passou pelo teardown em outra thread, com `!pool` confirmando que o bloco foi liberado em vez de sobrescrito:```
kd> !analyze -v

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - Access violation
FAULTING_MODULE: win32kfull

(Detalhes de offsets, endereços e reprodução foram omitidos sob divulgação coordenada.)

Confirmando a alegação de limite

O impacto não é comprovado por uma falha. É comprovado por quem consegue causar a falha. Cada execução do gatilho foi feita a partir de uma conta de usuário padrão, sem privilégios de administrador, no alvo, porque uma falha de kernel alcançável apenas a partir de um contexto já privilegiado é um bug de estabilidade, não de segurança. Verificar o nível de integridade do processo que provocou a colisão é um passo de trinta segundos que decide se você tem um caso de recompensa ou uma entrada no Windows Feedback Hub.

A disciplina essencial: eu não acreditei em uma única falha. Uma única falha em uma corrida é ruído. O que a tornou reportável foi a repetibilidade sob temporização controlada — poder dizer "estes dois caminhos, esta ordenação, esta janela" e obter a mesma falha de volta. É essa a diferença entre um relatório sobre o qual o MSRC pode agir e um que eles encerram como não reproduzível.

7. Sobre o uso de IA na pesquisa de kernel

Usei triagem assistida por IA para avançar mais rápido pela saída descompilada, e vou dizer claramente para que serviu e para que não serviu.

Boa para: cobrir superfície de ataque. Ler um grande volume de HLIL e sinalizar "estas funções tocam um objeto compartilhado sem sincronização visível" é correspondência de padrões, e correspondência de padrões em volume é exatamente o que essas ferramentas fazem bem. Isso comprimiu semanas de leitura superficial em dias.

Inútil para: julgamento. Ela vai narrar com confiança uma cadeia de ataque que não existe, afirmar alcançabilidade que não estabeleceu e produzir um relatório lindamente estruturado para um bug que não está lá. Cada conclusão teve que sobreviver à verificação manual no WinDbg antes de chegar perto de um relatório.

O modo de falha a temer não é a ferramenta estar errada. É a ferramenta estar fluente enquanto errada, às 2 da manhã, quando você quer que ela esteja certa.

Uma descoberta fabricada enviada ao MSRC custa tempo real aos engenheiros deles e custa a você uma reputação que não se reconstrói rapidamente.

8. A linha do tempo do MSRC, honestamente

DataEvento
7 de maio de 2026Submetido — VULN-186460
7 de maio de 2026Caso aberto — Caso MSRC 11xxxxx
11 de junho de 2026Comportamento confirmado pela Microsoft; análise de recompensa aberta
27 de junho de 2026Correção agendada para o lançamento de julho; CVE-2026-54107 atribuído (pré-lançamento)
30 de junho de 2026Recompensa concedida — US$8,000, Programa de Recompensas do Windows Insider Preview
14 de julho de 2026Patch lançado; CVE publicado

Cinco semanas de silêncio entre a submissão e a confirmação. É essa a parte que te testa. Você escreveu uma alegação sobre o kernel de outra pessoa e ainda não tem ideia se o seu raciocínio se sustenta, se é uma duplicata, ou se sequer reproduz no build deles. O e-mail de confirmação é o momento em que deixa de ser uma teoria que você tem e se torna uma vulnerabilidade que existe.

Uma pequena coisa que me fez rir de mim mesmo: em 14 de julho, no meu fuso, eu enviei uma mensagem no caso perguntando por que o CVE não tinha sido publicado. A resposta, educadamente: é 13 de julho em Seattle. O calendário de lançamentos da Microsoft segue o horário do Pacífico. Agora eu sei.


9. Resumo

CampoDetalhe
CVECVE-2026-54107
Caso MSRC11xxxxx (VULN-186460)
Componentewin32kfull.sys — ciclo de vida de objetos de janela
ClasseCondição de corrida → use-after-free
CWECWE-362
ImpactoElevação de Privilégio
SeveridadeImportante (MSRC)
CVSS v3.18.8 (Alta)
VetorLocal, autenticado
ProgramaPrograma de Recompensas do Windows Insider Preview
RecompensaUS$8,000
Corrigido emAtualização de Segurança de julho de 2026

10. O que eu diria a alguém que está começando

Leia procurando invariantes, não bugs. "Onde este código assume algo que ele não impõe?" encontra mais do que "onde está o estouro?" — especialmente em componentes maduros, fortemente auditados, onde as classes fáceis já não existem.

Uma corrida é uma alegação interprocedimental. Você não consegue estabelecer ou refutar uma a partir de uma única função. Se a sua análise parar no limite da função, você vai gerar candidatos que nunca conseguirá fechar.

Seu ambiente de depuração é o trabalho. Perdi mais horas com um link serial COM instável do que com a caçada em si, e um pipeline quebrado produz falsos negativos que parecem idênticos a "não há nada aqui." Quase abandonei este alvo por causa de uma configuração de porta COM.

Reporte a causa, não a falha. O MSRC recebe falhas. O que move um caso é uma história coerente sobre qual invariante foi quebrado e por que a aplicação não estava lá.

Confirmado não é concluído. Entre a confirmação e o patch lançado há acompanhamento de reprodutibilidade, comportamento Canary e revisão. Continue envolvido.

11. O que vem a seguir

Mesma metodologia, superfícies diferentes — tcpip.sys, afd.sys, clfs.sys. Prefiro ser conhecido por um corpo de trabalho do que por um único achado de sorte, e o único jeito de isso acontecer é continuar eliminando candidatos mais rápido do que eu os gero.

Se você está onde eu estava há um ano — vindo de recompensas por bugs web, curioso sobre trabalho com kernel, sem certeza se é o tipo de pessoa que consegue fazer isso — você descobre fazendo. Escolha um driver. Anexe um depurador. Leia devagar. Continue perguntando o que acontece se for executado duas vezes.

É genuinamente aí que começa.

Escrito por Pravin Choudhary (@pr4v1nx) — pesquisador independente de segurança ofensiva. Divulgado à Microsoft sob divulgação coordenada. Detalhes de exploração, offsets e código de reprodução foram intencionalmente omitidos.

Baixar ferramenta