Der kleine Bug, der es geschafft hat: CVE-2024-30089 ist eine subtile Kernel-Sicherheitslücke, die ich ausgenutzt habe, um einen vollständig aktualisierten Windows 11-Rechner (mit allen Virtualization Based Security- und Hardware-Sicherheitsmaßnahmen aktiviert) zu knacken und meinen ersten Sieg bei Pwn2Own in diesem Jahr zu erringen.
In diesem Artikel skizziere ich meinen einfachen Ansatz zur Fehlersuche: Ich wähle einen Ausgangspunkt und folge intuitiv einem Pfad, bis etwas meine Aufmerksamkeit erregt. Dieser Bug ist interessant, weil er aufgrund eines Logikfehlers zuverlässig ausgelöst werden kann. Der Fehler tritt in einem bestimmten Zustand innerhalb eines prozessübergreifenden Kommunikationssystems auf, was dann zu einem Use-After-Free führt. Um den Fehler zu finden, mussten die Codepfade des Programms in den verschiedenen möglichen Zuständen verglichen werden, ein Prozess, den ich im Detail beschreibe. Ebenso interessant sind der Ursprung des Fehlers und Microsofts Vorgehensweise bei dessen Behebung. Diese Themen werden ebenfalls in diesem Beitrag behandelt.
Eine Frage, die mir im Zusammenhang mit Schwachstellenanalyse häufig gestellt wird, ist, wo man eigentlich anfängt. Tatsächlich ist die Wahl eines Ziels und das Festhalten daran möglicherweise einer der schwierigsten Schritte im Forschungsprozess. Die hier behandelte Sicherheitslücke befindet sich im Microsoft Kernel Streaming Service (mskssrv.sys). Sehen Sie sich diesen Blogbeitrag an, um einen allgemeinen Überblick über das Subsystem zu erhalten. In diesem Beitrag habe ich auf einige Eigenschaften des MSKSSRV-Subsystems hingewiesen, die es zu einer guten Angriffsfläche machen könnten, insbesondere auf seinen prozessübergreifenden Kommunikationsmechanismus (IPC).
Die Codebasis von MSKSSRV ist ziemlich klein, und die letzte Schwachstelle in diesem Subsystem, die ich entdeckt habe, wurde ebenfalls unabhängig davon in der Realität als Zero-Day-Schwachstelle ausgenutzt. Ich habe auch von weiteren Bemühungen anderer Forscher und Unternehmen gehört, diesen Treiber zu überprüfen. Aus diesem Grund bin ich zunächst in die übliche Falle getappt, anzunehmen, dass es in dieser Angriffsfläche keine weiteren Fehler mehr zu finden gibt. Da ich es aber in meinem vorherigen Blogbeitrag vorgeschlagen hatte, beschloss ich, meinem Instinkt zu vertrauen und weiter zu suchen.
Eine hervorragende Möglichkeit, neue Forschungsideen zu erhalten, besteht darin, über die aktuelle Forschung auf dem Laufenden zu bleiben. Ich habe einen ausgezeichneten Blogbeitrag von k0shl gelesen, der mich dazu inspiriert hat, nach einer bestimmten Art von Fehler zu suchen. In der von k0shl gefundenen Schwachstelle wird die Referenzanzahl eines Objekts ohne ordnungsgemäße Sperrung initialisiert und erhöht, wodurch nach dem freien Fenster eine Verwendung entsteht. Obwohl es sich bei dem Fehler von k0shl um einen Userland-Fehler und nicht um einen Fehler im Kernel handelt, erinnerte mich die Codierung der anfälligen Bibliothek an die Prüfung des MSKSSRV-Treibers.
Der MS KS Server (MSKSSRV) interagiert mit einem Userland-Prozess über FSStreamReg-Objekte. und FSContextReg sind beide von der BasisklasseFSRegObject keine sperrende vtable-Funktion implementiert, und die Implementierung der Basisklassen-Implementierung () ist einfach eine nop-Anweisung. Das bedeutet, dass für -Objekte tatsächlich kein Sperrmechanismus implementiert ist. Umgekehrt gilt:FSStreamReg verwendet:
Codeblock 1: FSRendezvousServer::Close, Sperren und Entsperren des Pfads für FSRegObjects
Es gibt zwei Objekte, die von der Basisklasse
Wie ich in meinem letzten Blogbeitrag erwähnt habe, ist der Aspekt der prozessübergreifenden Objektfreigabe von MSKSSRV ein interessanter Ansatzpunkt für Schwachstellen, weshalb ich beschlossen habe, mich weiter darauf zu konzentrieren. Der IPC-Mechanismus des Subsystems wird im folgenden Diagramm dargestellt:
Diagramm 1: Prozessübergreifende Kommunikation im MS KS Server
Das Öffnen eines Dateihandles auf dem MSKSSRV-Gerät über CreateFile erzeugt eine FILE_OBJECT, die diesem Handle entspricht. Mithilfe dieses Handles kann ein Prozess einen neuen Stream oder ein Kontextobjekt initialisieren, indem er dem Gerät über die Funktion DeviceIoControl einen IOCTL sendet. Der initialisierende Prozess gibt an, welcher entfernte Prozess das Objekt registrieren kann, indem er die Prozess-ID über daslpInBufferFSRegObject wird gespeichert in
Irp->CurrentStackLocation->FileObject->FsContext2.
Der gleiche Zeiger auf das FSRegObject-Objekt wird zweimal in FsContext2 gespeichert, einmal in dem , der vom Initialisierungsprozess verwendet wird, und einmal in dem , der im Registrierungsprozess verwendet wird. Auf diese Weise können Referenzen auf das -Objekt prozessübergreifend genutzt werden. Zum Beispiel können mehrere Prozesse mit einem FSStreamReg-Objekt Zugriff auf den Stream-Frame-Puffer haben, wie in Diagramm 1 gezeigt. Die -Referenzanzahl wird auf 1 initialisiert und nach Abschluss der Initialisierung wieder erhöht. Das Registrieren erhöht die -Referenzanzahl erneut, sodass es insgesamt drei Referenzen pro Objekt gibt.
Diagramm 2: Initialisieren und Registrieren von FSContextReg-Objekten
Ich beschloss, einen weiteren Blick auf die Bereiche zu werfen, die ich zuvor geprüft hatte, um nach Sicherheitslücken im Sperrsystem zu suchen. Mir ist aufgefallen, dass die Funktion
Codeblock 2: DispatchCleanup- und DipatchCleanup-Routinen für den MS KS Servertreiber
Eine Dispatch-Routine bearbeitet eine oder mehrere Arten von IRPs, die verpackte I/O-Anfragen sind. In Windows erhält der entsprechende Dateisystemtreiber für eine Datei, wenn alle Handle-Referenzen zu der Datei geschlossen wurden, eine Anforderung
In MSKSSRV
Codeblock 3: FSRendezvousServer::Close, Prozessprüfungen an FSRegObjects
Prozessspezifische Informationen werden im
Irp->CurrentStackLocation->FileObject->FsContext2
zum Zeitpunkt der Initialisierung oder Registrierung. Der Treiber bestimmt, welche Prozesse welche Ressourcen verarbeiten (
Objekte, Ereignisobjekte und andere Dinge), die er freigeben muss, indem er die Prozess-ID des Aufrufers überprüft. Wenn es sich um den Initialisierungs- oder Registrierungsprozess handelt, werden einige zusätzliche Bereinigungen für die prozessspezifischen Ressourcen durchgeführt.
Das fiel mir besonders auf, weil im Allgemeinen alle Dispatch-Routinen in einem beliebigen Prozesskontext ausgeführt werden, mit einigen Ausnahmen. Mit anderen Worten: Das System wählt einen Thread aus, der die Dispatch-Arbeit erledigt; welcher Faden es wählt, ist willkürlich. Ich habe entdeckt, dass
Zusätzlich können Handles als Funktion des Windows-Betriebssystems mit anderen Prozessen geteilt werden (durch Vererbung von Kinderprozessen oder mithilfe der DuplicateHandle-API-Funktion). Über den gemeinsamen Dateihandle kann der andere Prozess ebenfalls mit demselben
Diagramm 3: Gemeinsame Nutzung des MS KS Server-Geräte-Handles
Deshalb ist es auch möglich, dass der Prozess während
Diagramm 4: Fremder Prozess schließt letzten Handle zu einem FILE_OBJECT
Mir ist aufgefallen, dass die Funktion
Codeblock 4: FSRegObject::Release kann zweimal von FSRendezvousServer::Close aufgerufen werden
Im Allgemeinen ist die Tatsache, dass mehr Dereferenzierungen als Referenzen auf ein Objekt vorhanden sind, nicht die einzige Möglichkeit, wie eine Verwendung nach dem Freigeben eines Objekts auftreten kann. In diesem Fall können wir jedoch sicher sein, dass wenn ein freigegeben wurde, die Anzahl seiner Referenzen auf null gesunken ist. Das letzte Mal, dass auf einen gültigen während der IRP-Anfragen zugegriffen wurde, ist in einem Aufruf von . Wenn also eine Verwendung nach dem Freigeben möglich ist, erfolgt ein Aufruf von immer, nachdem das Objekt bereits freigegeben wurde. Während des Aufrufs wird das Objekt noch einmal dereferenziert. Aus diesem Grund ist das Zählen der Anzahl der Dereferenzen eine gute Heuristik, um Use-after-frees für diesen speziellen Fall zu finden.
Es blieb nur noch, die möglichen Zustände des Programms nachzuverfolgen und dabei zu notieren, wann das Objekt freigegeben und wieder aufgerufen wird. Ich habe das getan, indem ich die Programmlogik während -Anfragen mental emuliert habe, die jeweils mit den entsprechenden Dispatch-Funktionen (Codeblock 2) für jeden möglichen Fall beginnen.
Nachfolgend werden die Zustände dargestellt, je nachdem, welcher Prozess die letzte Referenz auf ein Handle schließt. Jeder Eintrag repräsentiert die Anzahl der Dereferenzierungen des FSContextReg-Objekts, die auftreten, wenn der entsprechende Prozess das letzte Handle schließt. Hinweis: Es gibt keinen funktionalen Unterschied zwischen HANDLE #1 (initialisierendes Handle) und HANDLE #2 (registrierendes Handle), da das Feld auf denselben Speicher in beiden weist, die durch die entsprechenden Handles repräsentiert werden.
FSContextReg-Dereferenzierungen für jeden der möglichen MSKSSRV-IPC Zustände
Erfolg! Der letzte Zustand führt zu vier Dereferenzierungen: zwei durch den Fremdprozess und zwei durch den Initialisierungsprozess (und auch den Registrierungsprozess), während nur drei Referenzen vorhanden sind, was bedeutet, dass ein Zugriff nach der Freigabe möglich ist! Ich habe dieselbe Übung auch mit
Während ich die oben beschriebene Gehirnübung für virtuelle Maschinen durchführte, habe ich das Problem gefunden. Wenn der Prozess der initialisierende oder registrierende Prozess ist, erfolgt die entsprechende Bereinigung und der Zeiger wird in
Codeblock 5: FSRegObject::CloseInitProcess setzt den FSContext-Hinweis auf NULL
Das bedeutet, dass
Wenn der aufrufende Prozess jedoch ein fremder Prozess ist, findet keine Bereinigung statt und
Wenn nun der zweite Handle von einem Prozess geschlossen wird, der das FSContextReg-Objekt sowohl initialisiert als auch registriert hat, werden alle darin gespeicherten Ressourcen des Objekts freigegeben, sodass es leer ist. Dies führt dazu, dass
Das ergibt insgesamt vier Dereferenzierungen auf einem einzigen
Diagramm 5: CVE-2024-30089 dargestellt
Der geneigte Leser wird sich vielleicht fragen, warum ein fremder Prozess nicht der letzte sein kann, der beide Handles schließt, da dies anscheinend auch zu vier Dereferenzen führen würde. Bevor das Objekt zerstört und freigegeben wird, wird es aus einer Liste entfernt, die im globalen Objekt FSRendezvousServer wird der Zeiger in geprüft, ob er ein gültiges Mitglied der Liste ist. In diesem Fall wird das Objekt im zweiten Aufruf von am Ende der Funktion freigegeben. Während des vierten Aufrufs auf wurde das Objekt bereits von der Liste entkoppelt, wodurch es ein ungültiges Objekt ist, sodass es nicht verwendet werden kann. Um ein Use-after-free auszulösen, muss es erst erfolgen, nachdem das Objekt in abgerufen und validiert wurde . Der folgende Codeausschnitt zeigt das Use-after-free-Primitiv, das durch die Sicherheitslücke erhalten werden kann:
Code Block 6: UAF-Primitivpfad
Im Leitfaden für Sicherheitsupdates für diese Sicherheitslücke gibt der CVSS-Score an, dass eine „Ausbeutung eher wahrscheinlich [ist]“ und die Angriffskomplexität für diese Sicherheitslücke „niedrig“ ist. Microsoft gibt zwar keine detaillierten Erklärungen für die Bewertung ab, aber ich habe beim Patchen anderer Sicherheitslücken einige Muster festgestellt. Die Schwachstelle erhielt diese Bewertung wahrscheinlich, weil sie auf einem Logikfehler beruht und daher zuverlässig auslösbar ist. Durch Befolgen der in Diagramm 5 beschriebenen Schritte kann ein Angreifer das in Codeblock 6 dargestellte Use-After-Free-Szenario konstant auslösen. Dies bedeutet jedoch nicht, dass das Ausnutzen in der Praxis unkompliziert ist. Im nächsten Teil dieser Serie werden wir die einzelnen Schritte der Ausbeutung ausführlich erläutern.
Zu verstehen, wie ein Bug aufgetreten ist, ist wichtig, um einen proaktiven Ansatz für sichere Entwicklungspraktiken zu entwickeln. Um herauszufinden, wie die Sicherheitslücke entstanden ist, habe ich frühere Versionen des Treibers aus Winbindex analysiert und nach Unterschieden in der Logik der Funktion
Im Abschnitt über die Schwachstellen habe ich erwähnt, dass die eigentliche Ursache dieses Fehlers nicht die Einstellung
Irp->CurrentStackLocation->FileObject->FsContext2
zu
wenn der aufrufende Prozess ein fremder Prozess ist. Zu meiner Überraschung entdeckte ich genau diese Codezeile in einer frühen Version von mskssrv.sys:
Code Block 7: Frühe Version von FSRendezvousServer::Close, FsContext2 ist auf NULL gesetzt
In dem oben gezeigten Codeblock ist
Codeblock 8: FsContext wurde innerhalb einer Funktions-Flagprüfung auf NULL gesetzt
Im obigen Code-Block wird eine Überprüfung für das Feature-Flag
angezeigt.
Feature_Servicing_TeamsUsingMediaFoundationCrashes
.
Funktions-Flags sind eine Komponente von Windows, die verschiedene Funktionen und Experimente aktiviert, obwohl es nicht viele öffentliche Informationen über sie gibt. In diesem vorherigen Blogbeitrag besprechen wir, wie Funktions-Flags für das Beheben von Schwachstellen verwendet werden. Manchmal werden Funktions-Flags verwendet, um eine Funktion zu testen, bevor sie offiziell übernommen wird. In diesem Fall, wenn die
Feature_Servicing_TeamsUsingMediaFoundationCrashes
Funktion ist aktiviert, wird nicht auf NULL gesetzt, wodurch die Sicherheitslücke entsteht. Diese Funktion wurde bei Windows-10-Installationen standardmäßig aktiviert. In Windows 11 ist, wie in Codeblock 1 gezeigt, diese Funktionsflag-Bedingung nicht vorhanden und der Zeiger ist nicht auf gesetzt, wodurch es auch angreifbar wird.
Aufgrund des Namens der Funktion habe ich mir die Funktionalität der Videokonferenzsoftware Microsoft Teams angesehen. Ich überprüfte, ob die Anwendung die MSKSSRV-Funktionalität nutzen kann, um Medienströme zwischen Prozessen zu teilen. Möglicherweise war die gemeinsame Nutzung des Stream-Handles die Ursache für den Absturz von Teams. Ein interessantes Thema für weitere Untersuchungen wäre ein genauer Blick darauf, wie Teams MSKSSRV-Gerätehandles über Prozesse hinweg gemeinsam nutzt und warum eine ordnungsgemäße Hinweisbereinigung zum Absturz der Anwendung führen könnte.
Dieser Teil der Serie konzentriert sich auf die Sicherheitslücke selbst, einschließlich des zugehörigen Patches. Ich war besonders daran interessiert, den Patch für diesen Fehler zu untersuchen, da mein vorgeschlagener Fix anscheinend Abstürze in Microsoft Teams auslöste. Wichtig ist anzumerken, dass ich zum jetzigen Zeitpunkt noch nicht untersucht habe, wie eine reale Anwendung den MSKSSRV-Treiber in der Praxis einsetzt. Das Fehlen dieses Kontextes führt zu blinden Flecken beim Verständnis dafür, warum ein System auf bestimmte Weise gestaltet ist. Ein vollständiger Patch für diesen Fehler würde eine gewisse Umstrukturierung des Basiscodes erfordern und könnte weitere Details über die beabsichtigte Funktionsweise des IPC-Systems offenbaren. Ich hatte außerdem gehofft, von Microsoft-Entwicklern einige Erkenntnisse in sichere Codierung zu gewinnen.
Zu meiner Enttäuschung wurde der Logikfehler, der die Sicherheitslücke verursachte und mit den Sicherheitsupdates vom Juni 2024 behoben wurde, nicht direkt behoben. Stattdessen wurde vor den verwundbaren Codepfaden eine Zugriffs-Token-Prüfung hinzugefügt. Sehen Sie sich den folgenden Code für den Initialisierungskontext IOCTL an, der von der Funktion
Codeblock 9: Die IOCTL-Funktion beginnt mit einer Feature-Flag-Prüfung und überprüft, ob der aufrufende Prozess ein Frame-Server ist.
Die obige Funktion beginnt mit der Überprüfung, ob ein Feature aktiviert ist. Dies ist wahrscheinlich das Funktion-Flag, das dem Patch entspricht. Wenn die Funktion aktiviert ist, muss
Werfen wir einen Blick auf
Code Block 10: KsIsCurrentProcessFrameServer führt eine SID-Prüfung des Zugriffstokens des aufrufenden Threads durch.
Diese Funktion vergleicht das Token des aufrufenden Threads mit zwei spezifischen Sicherheitskennungen (SIDs). Die SIDs entsprechen einem Token in der Gruppe . Wenn eine der SIDs im Token des aufrufenden Threads aktiviert ist, kann der verwundbare Funktionscode ausgeführt werden.
Nachdem mir dies aufgefallen war, vermutete ich, dass wahrscheinlich noch ein Fehler zwischen Administrator und Kernel vorlag. Letztendlich wurden die Probleme mit der Speicherbeschädigung überhaupt nicht angegangen. Ich habe dies bestätigt, indem ich eine kleine Änderung an meinem ursprünglichen Exploit vorgenommen habe: Ein Administrator-Nutzer kann den Dienst starten, einen Handle für den Dienst öffnen und den Exploit-Prozess mit dem Handle erstellen. Ich konnte ein vollständiges Kernel-R/W-Primitiv auf einem vollständig gepatchten System erhalten.
Obwohl Microsoft Administrator to Kernel nicht als Sicherheitsgrenze betrachtet, wurden ähnliche Fehler von Bedrohungsakteuren genutzt, um eine Kernel-R/W-Primitive zu erlangen und diese für EDR-Blinding und Rootkit-Operationen zu verwenden. Wenn Sie daran interessiert sind, was man mit dieser Primitive alles machen kann, schauen Sie sich meinen BlackHat-Vortrag zusammen mit FuzzySec an.
Dieser Beitrag konzentrierte sich auf den Teil meiner Pwn2Own-Bemühungen, der sich mit der Suche nach einer 0-Day-Kernel-Sicherheitslücke befasste, die für eine Privilegienerweiterung ausgenutzt werden kann. Dieser Beitrag skizziert den Weg dorthin: Inspiration durch andere Forschungen, erfolglose Suche nach einem Bug, Wahl eines neuen Ansatzes, Entdeckung etwas Verdächtigen und schließlich die Lokalisierung der Schwachstelle. Nachdem nun ein Bug identifiziert wurde und es eine Use-after-free-Primitive gibt, sollte der Rest doch ein Kinderspiel sein, oder? Microsoft scheint das zu glauben, denn sie stuften diesen Bug als „Exploitation More Likely” (Ausnutzung eher wahrscheinlich) mit einer Angriffskomplexität von „low” (gering) ein. Haben sie Recht? Darauf werde ich im nächsten Teil eingehen, ebenso wie auf die Ausnutzungsstrategie, und die Bedeutung des Serientitels enthüllen!
Bleiben Sie mit dem Think-Newsletter über die wichtigsten – und faszinierendsten – Branchentrends in den Bereichen KI, Automatisierung, Daten und mehr auf dem Laufenden. Weitere Informationen finden Sie in der IBM Datenschutzerklärung.
Andréa Piazza, für die tollen Diagramme
Emma Kirkpatrick, weil sie mir geduldig das Windows-Sicherheitsmodell erklärt hat