Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-54107 — Root-Cause-Analyse von CVE-2026-54107: ein Use-after-Free in Windows win32kfull.sys mit Race-Condition-Debugging, statischer Analyse, MSRC-Triage-Erkenntnissen und praktischer Forschung zur Kernel-Exploitation. | Kitploit
Tools/GitHubGitHub/pravin761/cve-2026-54107
Statische AnalyseSchwachstellenanalyseExploitationReverse EngineeringDebuggerPapers & ForschungLernen & BildungBinary-Exploitation
GitHubpravin761/cve-2026-54107

CVE-2026-54107

Root-Cause-Analyse von CVE-2026-54107: ein Use-after-Free in Windows win32kfull.sys mit Race-Condition-Debugging, statischer Analyse, MSRC-Triage-Erkenntnissen und praktischer Forschung zur Kernel-Exploitation.

Repository anzeigen
13vor 1 MonatNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Wenn ValidateHwnd kein Tor ist: Root-Cause-Analyse von CVE-2026-54107

Ein Use-after-Free in der Fenster-Lebenszyklusverwaltung von win32kfull.sys – wie ich ihn gefunden habe, wie ich mich davon überzeugt habe, dass er echt ist, und wie der MSRC-Prozess aus Sicht des Forschers tatsächlich aussieht.

CVE-2026-54107 CWE-362 CVSS 8.8 MSRC 11xxxxx Bounty $8.000

Es war kurz vor 2 Uhr morgens, als die Ziel-VM nicht mehr auf den Heartbeat des Debuggers reagierte und exakt an der Anweisung in einen Break fiel, von der ich wochenlang behauptet hatte, dass sie erreichbar sei. Kein Assertion, kein Corrupted-Pool-Stopp – eine einfache Access Violation auf dem Nachrichten-Dispatch-Pfad, die ein Objekt dereferenzierte, das ein anderer Thread bereits abgerissen hatte.

kd> !pool 2

Dieser Break wurde zu CVE-2026-54107, MSRC-Fall 11xxxxx, gepatcht im Sicherheitsupdate vom Juli 2026 für 27 Windows-Produkte.

Dieser Beitrag ist die nicht unter Embargo stehende Hälfte der Geschichte: Root Cause, warum die Bug-Klasse genau diese ist, und die Argumentation, die mich dorthin geführt hat. Exploitation-Details bleiben außen vor.

Inhaltsverzeichnis

  • 1. Warum win32k – und warum gerade Fensterobjekte
  • 2. Der Geruch, der mich stoppen ließ
  • 3. Root Cause
  • 4. Warum die Impact-Bewertung genau so ist, wie sie ist
  • 5. Falsifikation kam zuerst – die meisten Kandidaten starben
  • 6. Verifikation: Statik liefert Hypothesen, der Debugger liefert Wahrheit
  • 7. Über die Nutzung von KI in der Kernel-Forschung
  • 8. Die MSRC-Zeitleiste, ehrlich
  • 9. Snapshot
  • 10. Was ich jemandem sagen würde, der anfängt
  • 11. Was als Nächstes kommt

1. Warum win32k – und warum gerade Fensterobjekte

Win32k ist die Kernel-Mode-Hälfte des Windows-Grafiksubsystems. Es ist alt, es ist riesig – und, entscheidend: Es ist aus Kontexten erreichbar, die als nicht vertrauenswürdig gelten. Diese letzte Eigenschaft ist der Grund, warum es trotz zwanzig Jahren Härtung, Filterung und Syscall-Einschränkungen ein permanentes Forschungsziel bleibt.

Innerhalb von win32k ist das Objekt tagWND (PWND) ungewöhnlich interessant, weil seine Lebensdauer von mehr als einem Mechanismus gleichzeitig verwaltet wird. Ein Fenster ist:

  • per Handle referenziert, über die User-Handle-Tabelle und Lookups im Stil von ValidateHwnd,
  • per Pointer referenziert, gehalten über verschachtelte Aufrufe und Nachrichten-Dispatch,
  • implizit referenziert über Eltern/Kind-, Besitzer/Besessen- sowie Thread/Desktop-Beziehungen,
  • und wird über einen Zerstörungspfad abgerissen, der all das oben Genannte in der richtigen Reihenfolge abwickeln muss.

Jedes Objekt mit mehreren unabhängigen Referenzpfaden und einem gemeinsamen Zerstörungspfad ist es wert, langsam gelesen zu werden. Das ist kein Vulnerability-Anspruch – es ist eine Heuristik dafür, wo man Zeit investieren sollte.

2. Der Geruch, der mich stoppen ließ

Was mich bei dieser Komponente verweilen ließ, war die Import-Oberfläche. win32kfull.sys zieht drei verschiedene Objektreferenz-Primitive aus ntoskrnl herein:```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:~
Drei Wege hinein, ein `ObfDereferenceObject`-Pfad hinaus.

Das bedeutet nicht, dass der Code falsch ist. Es bedeutet, dass die **Invariante verteilt ist** — keine einzelne Funktion besitzt *"dieses Objekt ist jetzt gerade lebendig,"* daher hängt die Korrektheit davon ab, dass jeder Aufrufer sich darüber einig ist, welche Referenz er hält und wie lange sie gültig ist. Verteilte Invarianten sind der Ort, an dem Race Conditions leben, denn eine Race ist niemals ein Logikfehler, den man in einer einzelnen Funktion sehen kann. Es ist ein Fehler in einer Annahme, die über zwei hinweg gehalten wird.

Also war die Frage, die ich jeder Funktion, die ein `PWND` berührte, zu stellen begann, nicht *"ist dieser Code korrekt?"*, sondern:

> **Wenn dieser exakte Funktionsrumpf auf zwei Threads im Abstand weniger Anweisungen ausgeführt wird, welcher von ihnen ist falsch?**



## 3. Grundursache

Der Defekt ist eine **Time-of-Check/Time-of-Use-Lücke zwischen Referenzfreigabe und Objektabbau** im Fensterzerstörungspfad, ohne ausreichende Synchronisation mit einem gleichzeitigen Konsumenten, der Handles validiert.

Auf seine Grundform reduziert:```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      */
    }
}

I apologize, but I notice the input chunk appears to be empty — no Markdown content was provided after "INPUT:".

Could you please resend chunk 5 of 13 with the actual English content you'd like translated into German? Once the source text is provided, I'll translate it immediately while preserving all Markdown structure exactly as specified.```c /* Thread B — consumer, concurrent / NtUserMessageCall(HWND hwnd, UINT msg, ...) { PWND pWnd = ValidateHwnd(hwnd); / may resolve a handle whose object is mid-teardown */

root@kitploit:~
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

In Kombination mit Pool-Tag-Filterung ist dies das, was „intermittierender Bugcheck unter Last“ in einen reproduzierbaren, zurechenbaren Fehler verwandelt.

Wenn Sie eines aus diesem Beitrag mitnehmen: aktivieren Sie den Special Pool bevor Sie beginnen, nicht erst, wenn Sie festhängen.

Pool-Forensik am Fehler

Sobald Sie einen Fehler haben, stellt sich die Frage, ob Sie es mit Korruption oder mit einem Lebensdauerfehler zu tun haben. Diese benötigen unterschiedliche Berichte. Die Pool-Metadaten beantworten das:``` kd> !pool

root@kitploit:~
Ein Block, der mit einem plausiblen Tag und Müll-Inhalt *zugewiesen* ist, deutet auf **Korruption** hin. Ein Block, der *freigegeben* ist oder in einer No-Access-Seite eines Special-Pools liegt, deutet auf einen **Lebensdauerfehler** hin — etwas hat einen Zeiger über den Tod des Objekts hinaus gehalten. Genau das ist der Unterschied zwischen *„Angreifer hat hier geschrieben“* und *„Dieses Objekt hätte nicht erreichbar sein dürfen,“* und er ist der Unterschied zwischen einer Heap-Korruptionsmeldung und einer CWE-362-Meldung.

Überprüfe den Objekttyp, bevor du dich für eine der beiden Möglichkeiten entscheidest. Ein `PWND` hat eine erkennbare Form; wenn der Speicher, an dem der Zugriffsfehler aufgetreten ist, noch die Überreste eines solchen trägt, befindest du dich fast sicher in einem Problem im Lebensdauerfenster und nicht in einer zufälligen Überschreibung.

### Live-Kernel-Debugging bei der Verschachtelung

Selbst mit Special Pool ist ein Wettlauf ein Scheduling-Problem, und **der Debugger verändert das Scheduling.** Das ist der zentrale Frust bei der Arbeit an Wettläufen: Das Instrument stört das, was es misst. Ein Haltepunkt im Teardown-Pfad serialisiert genau die beiden Threads, die du zu überlappen versuchst, und der Bug verschwindet höflich.

Der Ausweg besteht darin, nicht mehr zu versuchen, den Wettlauf mit einem Haltepunkt zu fangen, und stattdessen:

- **das Fenster künstlich vergrößern** — alles, was das Intervall zwischen Referenzfreigabe und Teardown verlängert, macht die Kollision bei normalen Scheduling-Raten erreichbar,
- **eher Kollisionsversuche als Präzision erhöhen** — die beiden Pfade kontinuierlich ausführen und die Wahrscheinlichkeit die Arbeit erledigen lassen,
- **bedingte und einmalige Haltepunkte verwenden**, die sich erst aktivieren, wenn der interessante Zustand existiert, anstatt bei jedem Eintritt zu unterbrechen,
- **die Verschachtelung nach dem Fehler bestätigen**, anhand des Thread-Zustands und der Stacks, anstatt zu versuchen, sie live zu beobachten.

Der Fehler selbst ist, sobald du ihn hast, unspektakulär — eine Dereferenzierung eines `PWND` auf dem Nachrichtenverteilungspfad, auf dem das Objekt bereits auf einem anderen Thread den Teardown durchlaufen hat, wobei `!pool` bestätigt, dass der Block freigegeben und nicht überschrieben wurde:```
kd> !analyze -v

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

(Offsets, Adressen und Reproduktionsdetails werden im Rahmen der koordinierten Offenlegung zurückgehalten.)

Die Grenzbehauptung bestätigen

Ein Absturz beweist keine Auswirkung. Bewiesen wird sie dadurch, wer den Absturz auslösen kann. Jeder Trigger-Lauf wurde von einem standardmäßigen, nicht-administrativen Benutzerkonto auf dem Zielsystem ausgeführt, denn ein Kernel-Fehler, der nur aus einem bereits privilegierten Kontext erreichbar ist, ist ein Stabilitätsfehler, kein Sicherheitsfehler. Die Integritätsebene des Prozesses zu prüfen, der die Kollision ausgelöst hat, ist ein Dreißig-Sekunden-Schritt, der entscheidet, ob du einen Bounty-Fall hast oder einen Eintrag im Windows Feedback Hub.

Die entscheidende Disziplin: Ich habe keinem einzelnen Absturz vertraut. Ein einzelner Absturz bei einer Race ist Rauschen. Meldefähig wurde es erst durch Wiederholbarkeit unter kontrolliertem Timing — in der Lage zu sein, „diese beiden Pfade, diese Reihenfolge, dieses Zeitfenster" zu sagen und denselben Fehler zurückzubekommen. Das ist der Unterschied zwischen einem Bericht, mit dem MSRC etwas anfangen kann, und einem, den sie als nicht reproduzierbar schließen.

7. Über den Einsatz von KI in der Kernel-Forschung

Ich habe KI-gestützte Triage genutzt, um dekompilierten Output schneller durchzuarbeiten, und ich sage klar, wofür es gut war und wofür nicht.

Gut für: Abdeckung der Angriffsfläche. Das Lesen großer Mengen an HLIL und das Markieren von „diese Funktionen greifen auf ein gemeinsames Objekt ohne sichtbare Synchronisierung zu" ist Mustererkennung, und Mustererkennung in großem Umfang ist genau das, was diese Werkzeuge gut können. Es hat Wochen des Überfliegens auf Tage komprimiert.

Nutzlos für: Urteilsvermögen. Es erzählt selbstbewusst eine Angriffskette, die es nicht gibt, behauptet Erreichbarkeit, die es nicht nachgewiesen hat, und produziert eine wunderschön strukturierte Ausarbeitung für einen Bug, der nicht existiert. Jede einzelne Schlussfolgerung musste die manuelle Verifizierung in WinDbg überstehen, bevor sie in die Nähe eines Berichts kam.

Die Fehlerart, die man fürchten muss, ist nicht, dass das Werkzeug falsch liegt. Es ist das Werkzeug, das fließend falsch liegt, um 2 Uhr nachts, wenn du willst, dass es richtig liegt.

Ein erfundener Befund, der an MSRC geschickt wird, kostet deren Ingenieure echte Zeit und kostet dich einen Ruf, den du nicht schnell wieder aufbauen kannst.

8. Die MSRC-Zeitleiste, ehrlich

DatumEreignis
May 7, 2026Eingereicht — VULN-186460
May 7, 2026Fall eröffnet — MSRC-Fall 11xxxxx
Jun 11, 2026Verhalten von Microsoft bestätigt; Bounty-Prüfung eröffnet
Jun 27, 2026Fix für Juli-Release geplant; CVE-2026-54107 zugewiesen (Vorabversion)
Jun 30, 2026Bounty vergeben — US$8,000, Windows Insider Preview Bounty Program
Jul 14, 2026Patch ausgeliefert; CVE veröffentlicht

Fünf Wochen Stille zwischen Einreichung und Bestätigung. Das ist der Teil, der dich auf die Probe stellt. Du hast eine Behauptung über den Kernel von jemand anderem niedergeschrieben und hast noch keine Ahnung, ob deine Argumentation trägt, ob es ein Duplikat ist oder ob es sich überhaupt auf deren Build reproduzieren lässt. Die Bestätigungs-E-Mail ist der Moment, in dem es aufhört, eine Theorie zu sein, die du hast, und zu einer Schwachstelle wird, die existiert.

Eine kleine Sache, über die ich selbst lachen musste: Am 14. Juli, in meiner Zeitzone, habe ich den Fall angeschrieben und gefragt, warum die CVE noch nicht veröffentlicht worden war. Die Antwort, höflich: es ist der 13. Juli in Seattle. Der Veröffentlichungskalender von Microsoft läuft nach Pazifikzeit. Jetzt weiß ich das.


9. Überblick

FeldDetail
CVECVE-2026-54107
MSRC-Fall11xxxxx (VULN-186460)
Komponentewin32kfull.sys — Lebenszyklus von Fensterobjekten
KlasseRace Condition → Use-after-Free
CWECWE-362
AuswirkungPrivilegienausweitung
SchweregradImportant (MSRC)
CVSS v3.18.8 (High)
VektorLokal, authentifiziert
ProgrammWindows Insider Preview Bounty Program
PrämieUS$8,000
BehobenSicherheitsupdate Juli 2026

10. Was ich jemandem am Anfang raten würde

Lies mit Blick auf Invarianten, nicht auf Bugs. „Wo nimmt dieser Code etwas an, das er nicht durchsetzt?" findet mehr als „wo ist der Überlauf?" — gerade in ausgereiften, stark geprüften Komponenten, in denen die einfachen Schwachstellenklassen verschwunden sind.

Eine Race ist eine interprozedurale Behauptung. Man kann sie weder aus einer einzelnen Funktion ableiten noch widerlegen. Wenn deine Analyse an der Funktionsgrenze endet, erzeugst du Kandidaten, die du nie abschließen kannst.

Deine Debugging-Umgebung ist der Job. Ich habe mehr Stunden an einer instabilen seriellen COM-Verbindung verloren als an der eigentlichen Jagd, und eine kaputte Pipeline erzeugt falsche Negative, die exakt wie „hier ist nichts" aussehen. Ich hätte dieses Ziel fast wegen einer COM-Port-Einstellung aufgegeben.

Melde die Ursache, nicht den Absturz. MSRC bekommt Abstürze. Was einen Fall voranbringt, ist eine kohärente Erzählung darüber, welche Invariante gebrochen wurde und warum die Durchsetzung fehlte.

Bestätigt heißt nicht fertig. Zwischen Bestätigung und ausgeliefertem Patch gibt es Follow-up zur Reproduzierbarkeit, Canary-Verhalten und Review. Bleib dran.

11. Was als Nächstes kommt

Dieselbe Methodik, andere Angriffsflächen — tcpip.sys, afd.sys, clfs.sys. Ich würde lieber für ein Gesamtwerk bekannt sein als für einen einzigen Glückstreffer, und der einzige Weg dorthin ist, weiterhin Kandidaten schneller zu erledigen, als ich sie erzeuge.

Wenn du da stehst, wo ich vor einem Jahr stand — kommend von Web-Bountys, neugierig auf Kernel-Arbeit, unsicher, ob du der Typ Mensch bist, der das kann — findest du es heraus, indem du es tust. Such dir einen Treiber. Häng einen Debugger an. Lies langsam. Frag immer wieder, was passiert, wenn es zweimal läuft.

Genau dort beginnt es wirklich.

Geschrieben von Pravin Choudhary (@pr4v1nx) — unabhängiger Forscher für offensive Sicherheit. Im Rahmen koordinierter Offenlegung an Microsoft gemeldet. Exploit-Details, Offsets und Reproduktionscode werden absichtlich zurückgehalten.

Tool herunterladen