Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-50416-writeup-and-poc — CVE-2026-50416: contournement du KASLR sous Windows 11 | Kitploit
Outils/GitHubGitHub/karollooool/cve-2026-50416-writeup-and-poc
Frameworks d'ExploitationCriminalistique MémoireAnalyse des VulnérabilitésExploitationCollecte d'InformationsCTFAnalyse de BinairesArticles et RechercheApprentissage et Éducation
Labs et Pratique
GitHubkarollooool/cve-2026-50416-writeup-and-poc

CVE-2026-50416-writeup-and-poc

CVE-2026-50416: contournement du KASLR sous Windows 11

Voir le dépôtSite web
4451il y a 23 joursVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-50416 : Un QWORD de trop dans le tas de bureau (Desktop Heap)

Sur la build Windows 11 Insider 10.0.28020.2149, le mappage en mode utilisateur du tas de bureau Win32k exposait un pointeur brut de pool du noyau à l'offset 0x100.

La lecture en elle-même est presque offensivement petite :

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

Dans ma session de test, cela a renvoyé :

root@kitploit:~
0xffffc600dcc00040

La valeur restait identique entre les processus sur le même bureau et changeait après un redémarrage. Un processus lancé sur un autre bureau recevait une valeur différente car il disposait d'un tas de bureau différent. À partir de ce seul QWORD, la preuve de concept (PoC) a récupéré la base du tas de bureau du noyau, puis a utilisé user32!gSharedInfo pour dériver les adresses noyau des objets fenêtre actifs.

La même lecture fonctionnait depuis une intégrité Low, un AppContainer, une configuration LPAC sans aucune capacité, et un enfant AppContainer à intégrité Low sans aucune capacité.

Le tas de bureau est censé être partagé. Le pointeur du noyau ne l'est pas.

Le tas de bureau depuis le mode utilisateur

Win32k stocke les objets USER tels que les fenêtres, menus, classes, hooks et métadonnées associées dans les tas de bureau. Chaque bureau possède son propre tas. Une partie de ce tas est mappée dans les processus associés au bureau afin que le mode utilisateur puisse lire l'état GUI partagé sans demander chaque champ au noyau.

Sur la build x64 testée, le mappage en mode utilisateur peut être atteint via les données client du TEB du thread courant :

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

Les offsets sont spécifiques à la build, mais le chemin est simple :

root@kitploit:~
GS:[0x30]
    -> TEB
    -> ClientInfo à TEB + 0x800
    -> ClientInfo[5]
    -> mappage du tas de bureau en mode utilisateur

La PoC appelle VirtualQuery sur l'adresse renvoyée et enregistre la région mappée et sa protection. Rien n'a encore mal tourné. Un mappage de tas de bureau en lecture seule est un comportement Win32k normal.

Le problème commence 256 octets plus loin.

Le pointeur à l'offset 0x100

La PoC principale lit un QWORD depuis le tas mappé :

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

La valeur a passé les vérifications de base attendues pour une adresse virtuelle du noyau sur le système testé :

  • Bits hauts canoniques
  • Alignement sur huit octets
  • Aucune des valeurs sentinelles connues filtrées par la PoC
  • Stable pendant la création et la destruction de fenêtres
  • Identique dans les processus testés sur le même bureau
  • Différente après redémarrage
  • Différente sur un autre bureau

Le test de stabilité crée des fenêtres STATIC, BUTTON et EDIT, lit la valeur avant la création, la relit pendant que les fenêtres existent, les détruit, puis effectue une troisième lecture.

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

Les trois lectures ont renvoyé la même valeur. L'activité d'allocation de fenêtres ne l'a pas déplacée. Ce comportement est cohérent avec un champ des métadonnées du tas de bureau plutôt qu'avec un pointeur d'objet à courte durée de vie.

La propriété inter-processus est tout aussi importante. Deux processus attachés au même bureau observent la même valeur divulguée car ils regardent le même tas de bureau. Après un redémarrage, KASLR donne une nouvelle adresse à la session. Un enfant placé sur un autre bureau observe un autre pointeur car ce bureau possède un autre tas.

Cela donne à la fuite une identité utile :

root@kitploit:~
même démarrage + même bureau      -> même pointeur
même démarrage + autre bureau     -> pointeur différent
nouveau démarrage                 -> pointeur différent

Récupération de la base du tas de bureau du noyau

Sur la build testée, le pointeur divulgué se situe 0x40 octets au-dessus de la base du tas de bureau du noyau utilisée par la PoC :

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

En utilisant la valeur de session enregistrée :

root@kitploit:~
pointeur divulgué            = 0xffffc600dcc00040
base du tas de bureau noyau  = 0xffffc600dcc00000

Cette relation est spécifique à la build. Pour la build utilisée lors des tests, elle fournit l'ancre côté noyau nécessaire pour l'étape suivante.

Un pointeur est déjà utile. Une adresse pour un objet choisi est beaucoup plus utile.

Résolution d'un objet fenêtre via gSharedInfo

user32.dll exporte gSharedInfo, qui expose la liste des entrées de handles USER et la taille de chaque entrée :

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

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

Un HWND contient un index dans la table des handles USER. La PoC prend les 16 bits de poids faible du handle, parcourt jusqu'à l'entrée correspondante et lit l'offset du tas de bureau qui y est stocké.

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

Le même offset désigne l'objet dans les deux mappages :

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

Le calcul complet est donc :

root@kitploit:~
base du tas de bureau noyau = desktop_heap[0x100] - 0x40
index du handle             = HWND & 0xffff
offset du tas               = aheList[index du handle].offset
adresse fenêtre noyau       = base du tas de bureau noyau + offset du tas

La PoC crée six classes de fenêtres et effectue le calcul pour chacune :

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

Pour chaque objet, elle affiche le HWND, l'index du handle, l'adresse de l'objet en mode utilisateur, l'offset du tas et l'adresse noyau.

root@kitploit:~
HWND
  -> index de handle sur 16 bits de poids faible
  -> entrée de handle gSharedInfo
  -> offset du tas de bureau
  -> base du tas de bureau noyau + offset
  -> adresse noyau de cet objet fenêtre

C'est la partie qui transforme la divulgation d'un simple pointeur noyau lâche en un oracle d'adresses pour des objets USER sélectionnés sur le tas de bureau testé.

Pourquoi les tests de sandbox comptent

Le tas de bureau arrive via un mappage partagé. Les niveaux d'intégrité et les restrictions AppContainer ne réécrivent pas le contenu de ce mappage pour chaque processus. Si le processus reçoit le tas de bureau, il reçoit le QWORD à 0x100 avec lui.

La PoC sandbox lance des enfants dans plusieurs contextes et fait lire à chaque enfant la valeur depuis son propre TEB et son propre mappage de tas de bureau.

ContexteConfigurationRésultat
Intégrité MediumProcessus utilisateur standardDivulgué
Intégrité LowIntégrité du jeton abaissée à LowDivulgué
AppContainerZéro capacité demandéeDivulgué
Configuration LPACTous les packages d'application en opt-out, zéro capacité demandéeDivulgué
AppContainer à intégrité LowLow IL plus AppContainer, zéro capacité demandéeDivulgué
Bureau alternatifEnfant assigné à un nouveau bureauDivulgué une valeur différente

Les cinq premiers enfants étaient attachés au bureau par défaut et ont renvoyé la même adresse. L'enfant du bureau alternatif a renvoyé une autre adresse car il a reçu un autre tas de bureau.

La sortie de l'enfant a un format compact afin que le parent puisse comparer les résultats :

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

L'assistant plus strict enregistre également l'état du jeton et le nombre de capacités :

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

Le détail important n'est pas que l'enfant puisse appeler une API Win32k spéciale. Il n'en a pas besoin. Une fois le mappage présent, la fuite est une simple lecture mémoire en mode utilisateur.

Aucune création de fenêtre requise

Un assistant séparé effectue la lecture sans appeler CreateWindow.

Il vérifie le pointeur du tas de bureau, lit desktop_heap[0x100], charge explicitement user32.dll, revérifie le mappage, et ne crée toujours aucune fenêtre. Un autre enfant de type renderer charge user32.dll, effectue la même lecture et se termine sans créer de fenêtre.

Le résultat utile est simple :

root@kitploit:~
Aucun objet fenêtre ne doit être créé avant de lire le QWORD divulgué.

La fuite appartient au mappage du tas de bureau lui-même, pas à une fenêtre créée par le processus attaquant.

L'enfant de type renderer

supporting_proof_remote_trigger.c crée un enfant AppContainer à intégrité Low avec zéro capacité demandée. L'enfant ne fait qu'une petite quantité de travail :

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

Sortie enregistrée :

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

Cela démontre la lecture depuis une configuration de jeton de type renderer. Un bug de corruption mémoire de navigateur séparé qui donne déjà une exécution de code natif dans un tel processus n'aurait pas besoin d'une autre divulgation d'informations avant de lire ce pointeur de tas de bureau.

Quoi d'autre était visible dans le mappage

Une fois que j'ai eu un pointeur fiable, j'ai scanné la région mappée pour voir ce qui d'autre était présent.

Valeurs supplémentaires en forme d'adresses noyau

Le scanner a trouvé six à dix valeurs QWORD uniques supplémentaires par exécution qui passaient les mêmes vérifications d'adresse canonique et d'alignement. Le nombre exact changeait avec l'activité du bureau. L'offset 0x100 était la fuite principale stable, mais ce n'était pas la seule valeur avec une forme d'adresse noyau dans le mappage.

Titres de fenêtres d'autres processus

L'assistant de données sensibles énumère les fenêtres de premier niveau avec EnumWindows, collecte leurs PID propriétaires et leurs titres, puis recherche dans le mappage du tas de bureau les mêmes titres sous forme de chaînes UTF-16.

Dans l'exécution enregistrée, il a trouvé vingt titres uniques appartenant à d'autres processus. Les exemples incluaient des onglets de navigateur, Discord, Explorer, Spotify et des fenêtres de la barre d'état système.

Le programme n'affiche un titre que lorsque les deux conditions sont vraies :

  1. La chaîne existe dans la région mappée du tas de bureau.
  2. EnumWindows signale une fenêtre avec ce titre et un PID propriétaire différent du processus de test.

Cela rend la sortie facile à vérifier au lieu de se fier à des chaînes imprimables aléatoires trouvées en mémoire.

Occurrences de PID

L'assistant scanne également les valeurs DWORD dans le mappage. Une valeur n'est comptée que lorsque :

  1. Elle ressemble à un PID plausible.
  2. OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) réussit pour elle.
  3. Le PID possède également une fenêtre trouvée par EnumWindows.

L'exécution enregistrée a trouvé 605 occurrences DWORD correspondantes. C'est un compte d'occurrences dans le tas, pas 605 processus uniques. Le même PID peut apparaître plus d'une fois.

Texte de mot de passe

L'assistant crée un contrôle EDIT avec ES_PASSWORD, définit son texte sur SecretPassword123 et recherche le préfixe SecretP dans la région mappée. Il n'a pas été trouvé dans l'exécution testée.

Le mappage exposait donc des titres, des occurrences de PID et des valeurs en forme d'adresses noyau, tandis que la chaîne de mot de passe testée n'y apparaissait pas.

Ce que la fuite change pendant l'exploitation

Pour un bug de corruption mémoire Win32k, savoir qu'un objet existe n'est pas la même chose que savoir où il vit dans la mémoire du noyau.

Sans la divulgation, l'attaquant doit composer avec une base de tas de bureau inconnue et des adresses d'objets inconnues. Avec la divulgation, le côté adresse devient :

root@kitploit:~
lire un QWORD
soustraire 0x40
lire l'entrée de handle cible
ajouter son offset de tas

Pour un HWND choisi, l'attaquant dispose désormais de l'adresse correspondante du tas de bureau du noyau sur la build testée. Cela peut aider à :

  • Suivre un objet cible à travers l'activité du tas
  • Distinguer l'objet visé des allocations voisines
  • Calculer l'adresse utilisée par une primitive de lecture, d'écriture ou de corruption séparée
  • Vérifier si le façonnage du tas a produit la disposition attendue
  • Supprimer les suppositions d'adresse du tas de bureau d'une chaîne d'exploitation Win32k

La fuite résout le problème d'adresse. Le façonnage du tas, le remplacement d'objets et la primitive de corruption mémoire restent des parties séparées de l'exploitation.

Cette division compte. KASLR n'arrête pas la corruption mémoire. Elle rend le ciblage fiable plus difficile. Ce QWORD supprime cette incertitude pour la région du tas de bureau utilisée par la PoC.

Reproduction

Environnement testé

root@kitploit:~
Windows 11 Insider Build 10.0.28020.2149
Utilisateur standard
Intégrité Medium de base

Fichiers

  • kaslr_bypass_poc.c : PoC principale de fuite et de résolution d'adresses de fenêtres
  • kaslr_sandbox_proof.c : tests Medium IL, Low IL, AppContainer, configuration LPAC, AppContainer Low IL et bureau alternatif
  • supporting_proof_no_window.c : lecture sans créer de fenêtre
  • supporting_proof_no_caps_lpac.c : configurations AppContainer et LPAC sans capacité
  • supporting_proof_sensitive_data.c : titres, occurrences de PID, scan de pointeurs supplémentaires et vérification du champ mot de passe
  • supporting_proof_exploitability.c : six classes de fenêtres et calculs d'adresses noyau
  • supporting_proof_remote_trigger.c : enfant AppContainer Low IL de type renderer
  • compile.bat : menu de compilation

Compilation

Exécutez :

root@kitploit:~
compile.bat

Sélectionnez la cible dans le menu.

La PoC principale peut également être compilée directement depuis une invite de commandes développeur Visual Studio :

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

Valider le pointeur

Exécutez la PoC principale deux fois sans redémarrer :

root@kitploit:~
kaslr_bypass_poc.exe
kaslr_bypass_poc.exe

Le pointeur à desktop_heap + 0x100 doit être identique dans les deux exécutions.

Ouvrez un second terminal et exécutez-la depuis un autre processus sur le même bureau. La valeur doit correspondre à nouveau.

Redémarrez et répétez. La valeur doit changer.

Exécuter le test sandbox

root@kitploit:~
kaslr_sandbox_proof.exe

Le test lance chaque enfant, capture sa sortie et compare les valeurs divulguées. Les enfants sur le bureau par défaut doivent signaler la même valeur. L'enfant du bureau alternatif doit signaler une valeur différente.

Exécuter les assistants ciblés

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

Chaque assistant isole une partie du résultat afin qu'elle puisse être reproduite sans lire toute la sortie de la PoC complète.

Correctif

Le mappage en mode utilisateur ne devrait pas contenir d'adresses virtuelles brutes du noyau.

Le correctif le plus simple est d'assainir le champ d'en-tête du tas de bureau avant que la page ne devienne visible en mode utilisateur. Windows utilise déjà une valeur opaque 0x6000000000 pour d'autres champs de pointeurs du tas de bureau, donc le même style de remplacement pourrait être utilisé ici si le mode utilisateur a encore besoin du champ.

Si le mode utilisateur n'a pas besoin de la page d'en-tête, le correctif plus propre est de ne pas exposer cette page dans le mappage partagé.

Le test de régression est simple : créer des processus en configurations Medium IL, Low IL, AppContainer et LPAC, mapper le tas de bureau et rejeter toute adresse canonique du noyau trouvée dans l'en-tête visible par l'utilisateur.

Conclusion

Toute la chaîne commence par une lecture ordinaire depuis un mappage en lecture seule :

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

Ce QWORD identifie le tas de bureau du noyau. gSharedInfo fournit l'offset par objet. Ensemble, ils transforment un HWND en mode utilisateur en l'adresse noyau correspondante sur la build testée.

Aucun déclencheur compliqué ne se cache ici. Windows a placé le tas de bureau là où le mode utilisateur pouvait le lire, puis a laissé un pointeur noyau à l'intérieur de la partie qu'il a partagée.

Un QWORD a suffi.

Télécharger l’outil