Runde für Runde: Der kleine Bug, der es schaffen könnte

Eine junge Frau arbeitet nachts nachdenklich am Computer.

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.

Jagd nach 0-Day-Exploits: Wo anfangen?

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.

Lock Lock: Wer ist da?

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- und FSContextReg-Objekte. FSStreamReg und FSContextReg sind beide von der BasisklasseFSRegObject abgeleitet. Ich habe festgestellt, dass FSContextReg keine sperrende vtable-Funktion implementiert, und die Implementierung der Basisklassen-Implementierung (FSRegObject) ist einfach eine nop-Anweisung. Das bedeutet, dass für FSContextReg-Objekte tatsächlich kein Sperrmechanismus implementiert ist. Umgekehrt gilt:FSStreamReg implementiert eine Sperrfunktion, die einen Mutex verwendet. Der Verriegelungsmechanismus wird beim Zugriff auf die zur Bereinigung bearbeiteten Objekte in der Funktion FSRendezvousServer::Close verwendet:

Screenshot der Funktion FSRendezvousServer::Close

Codeblock 1: FSRendezvousServer::Close, Sperren und Entsperren des Pfads für FSRegObjects

Es gibt zwei Objekte, die von der Basisklasse FSRegObject  abgeleitet sind, aber ein Objekttyp implementiert einen geeigneten Sperrmechanismus und der andere nicht. Das war mir suspekt. Um meine Theorie zu testen, habe ich versucht, undefiniertes Verhalten durch Verweise auf das ungeschützte FSContextReg-Objekt auszulösen. Aufgrund des Sperrschutzes für das globale Objekt FSRendezvousServer , das die Zeiger auf die Listen von FSRegObjects enthält, konnte ich jedoch nichts Interessantes auslösen, obwohl es keinen Sperrschutz für die Objekte FSContextReg  gibt. Dennoch hatte ich das Gefühl, dass an dem Referenzzählsystem für FSRegObjects etwas „verdächtig“ war. Ich wusste nur noch nicht, was.

IPC im MS KS-Server

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 des IPC-Mechanismus des Subsystems

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 daslpInBuffer -Argument spezifiziert. Der entfernte Prozess, der einen neuen Dateihandle für das MSKSSRV-Gerät verwendet, kann das Objekt nun über das Gerät-IOCTL registrieren. Der Zeiger für das FSRegObject wird gespeichert in

Irp->CurrentStackLocation->FileObject->FsContext2.

Der gleiche Zeiger auf das FSRegObject-Objekt wird zweimal in FsContext2 gespeichert, einmal in dem FILE_OBJECT , der vom Initialisierungsprozess verwendet wird, und einmal in dem FILE_OBJECT , der im Registrierungsprozess verwendet wird. Auf diese Weise können Referenzen auf das FSRegObject -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 FSRegObject -Referenzanzahl wird auf 1 initialisiert und nach Abschluss der Initialisierung wieder erhöht. Das Registrieren erhöht die FSRegObject -Referenzanzahl erneut, sodass es insgesamt drei Referenzen pro Objekt gibt.

Diagramm zur Initialisierung und Registrierung von FSContextReg-Objekten

Diagramm 2: Initialisieren und Registrieren von FSContextReg-Objekten

Einen Bug finden

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 FunktionFSRendezvousClose, , dieFSRendezvousServer::Close aufruft, innerhalb der Routinen der Dispatch Cleanup und Close-Funktion des Treibers aufgerufen wird:

Screenshot eines CodeBlock mit DispatchCleanup- und DipatchCleanup-Routinen für den MS KS Server-Treiber

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 IRP_MJ_CLEANUP  und eine Anforderung IRP_MJ_CLOSE  , die von den Funktionsroutinen DispatchCleanup und DispatchClose des Treibers gehandhabt werden.

In MSKSSRVFSRendezvousClose wird sowohl innerhalb der DispatchCleanup als auch der DispatchClose-Funktionsroutinen des Treibers aufgerufen, wenn der inIrp->CurrentStackLocation->FileObject->FsContext2 gespeicherte Zeiger nicht NULL ist. Was mir anFSRendezvousServer::Close auffiel und das ich zuvor analysiert hatte, waren die verschiedenen Überprüfungen der Prozess-ID des Aufrufers. Der Prozesskontext ist wichtig, da der gesamte Kernel-Moduscode innerhalb eines einzelnen Kernel-Adressraums arbeitet, der von Benutzer-Modus-Adressen getrennt ist. Jeder Prozess hat seinen eigenen Benutzer-Modus-Speicherkontext, und der Prozesskontext, in dem ein Kernel-Thread ausgeführt wird, bestimmt, auf welchen Benutzer-Modus-Adressraum des Prozesses zugegriffen wird, wenn der Thread auf Benutzeradressen zugreift. Der folgende Code prüft, ob der aufrufende Prozess der Initialisierungs- oder Registrierungsprozess ist:

Screenshot der Funktion FSRendezvousServer::Close

Codeblock 3: FSRendezvousServer::Close, Prozessprüfungen an FSRegObjects

Prozessspezifische Informationen werden im FSRegObject gespeichert

 Irp->CurrentStackLocation->FileObject->FsContext2

 
zum Zeitpunkt der Initialisierung oder Registrierung. Der Treiber bestimmt, welche Prozesse welche Ressourcen verarbeiten (

EPROCESS

 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, dassDispatchCleanup im Prozesskontext des Prozesses aufgerufen wird, der das letzte Handle geschlossen hat. In diesem Fall sind die Prüfungen der Prozess-IDs also sinnvoll. JedochDispatchClose wird aus einem beliebigen Prozesskontext aufgerufen. Das bedeutet, dass, wennFSRendezvousServer::Close als Ergebnis einerIRP_MJ_CLOSE -Anfrage aufgerufen wird, dies aus einem beliebigen Prozesskontext geschieht, was den Zweck der Prozess-ID-Prüfungen zunichte macht. Das war ein wichtiger Hinweis darauf, dass hier etwas nicht stimmte.

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 demselbenFILE_OBJECT interagieren.

Diagramm zur gemeinsamen Nutzung des MS KS Server-Geräte-Handles

 Diagramm 3: Gemeinsame Nutzung des MS KS Server-Geräte-Handles

Deshalb ist es auch möglich, dass der Prozess während DispatchCleanup  weder der Initialisierungsprozess noch der Registrierungsprozess ist. Wenn einer dieser Handles dupliziert und an einen anderen Prozess weitergegeben wird und dieser Prozess der letzte ist, der das Handle schließt, wird DispatchCleanup im Kontext dieses Fremdprozesses aufgerufen (der nicht der Initialisierungs- oder Registrierungsprozess ist).

Diagramm eines Fremdprozesses, der das letzte Handle für ein FILE_OBJECT schließt

Diagramm 4: Fremder Prozess schließt letzten Handle zu einem FILE_OBJECT

Mir ist aufgefallen, dass die Funktion FSRendezvousServer::Close zweimal aufgerufen werden könnte, wenn ein Datei-Handle geschlossen ist (einmal für die Anfrage IRP_MJ_CLEANUP und einmal für die Anfrage IRP_MJ_CLOSE , Codeblock 2). Innerhalb dieser Funktion gibt es zwei mögliche Aufrufe von FSRegObject::Release (Codeblock 4), der das Objekt dereferenziert und seinen Speicher freigibt, wenn der Referenzzähler auf 0 fällt. Das bedeutet FSRegObject::Release könnte bis zu viermal für eine Telefonnummer aufgerufen werden. Zusätzlich gibt es zwei Datei-Handles, für die FILE_OBJECT  über den Zeiger FSContext2 auf dasselbe FSRegObject zeigt. Das bedeutet die Möglichkeit, FSRegObject::Release  bis zu acht Mal am selben Objekt, vier Mal pro Griff, aufzurufen. Wenn die ursprüngliche Referenzanzahl des Objekts nur drei beträgt, könnte es eine mögliche Verwendung nach dem Freigeben geben, die durch zu viele Dereferenzen ausgelöst wurde. Das war jedenfalls meine Überlegung. Ich wusste, dass die Programmstruktur wahrscheinlich verhindern würde, die theoretische maximale Anzahl an Dereferenzen zu erreichen, aber vielleicht nicht ganz genug. An diesem Punkt spürte ich, dass ich auf einer Spur war und beschloss, die Sache weiter zu untersuchen.

Screenshot von FSRegObject::Release kann zweimal von FSRendezvousServer::Close aufgerufen werden.

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 FSRegObject freigegeben wurde, die Anzahl seiner Referenzen auf null gesunken ist. Das letzte Mal, dass auf einen gültigen FSRegObject während der IRP_MJ_CLEANUP/CLOSE IRP-Anfragen zugegriffen wurde, ist in einem Aufruf von FSRegObject::Release. Wenn also eine Verwendung nach dem Freigeben möglich ist, erfolgt ein Aufruf von FSRegObject::Release 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 IRP_MJ_CLEANUP/CLOSE-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 FileObject->FSContext2 auf denselben Speicher in beiden FILE_OBJECT weist, die durch die entsprechenden Handles repräsentiert werden.

Screenshot der FSContextReg Dereferenzierungen für jeden der möglichen MSKSSRV-IPC Zustände

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 FSStreamReg-Objekten wiederholt, aber aufgrund eines Speicherlecks im Code ist es tatsächlich nicht möglich, ein FSStreamReg-Objekt jemals freizugeben, nachdem es registriert wurde.

Die Sicherheitslücke

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 Irp->CurrentStackLocation->FileObject->FsContext2 gespeichert und auf NULL eingestellt. Der folgende Codeblock zeigt, wo das passiert, falls der Anrufer der Initialisierungsprozess ist:

Screenshot von FSRegObject::CloseInitProcess setzt den FSContext-Hinweis auf NULL

Codeblock 5: FSRegObject::CloseInitProcess setzt den FSContext-Hinweis auf NULL

Das bedeutet, dass FSRendezvousClose während des Abschlusses der Anfrage IRP_MJ_CLOSE in DispatchClose (SrvDispatchClose, Codeblock 2) nicht erneut aufgerufen wird.

Wenn der aufrufende Prozess jedoch ein fremder Prozess ist, findet keine Bereinigung statt und FSRegObject::Release wird einmal gegen Ende der Funktion aufgerufen. Da FileObject->FsContext2 nicht NULL ist, wird FSRendezvousServer::Close während der nachfolgenden IRP_MJ_CLOSE-Anfrage erneut aufgerufen und es erfolgt ein weiterer Aufruf von FSRegObject::Release.

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 FSRegObject::Release  zweimal innerhalb von FSRendezvousServer::Close (Code Block 4) aufgerufen wird. Die zusätzliche Dereferenzierung dient dazu, die zusätzliche Initialisierungsreferenz zu berücksichtigen, sobald das Objekt leer ist.

Das ergibt insgesamt vier Dereferenzierungen auf einem einzigen FSContextReg-Objekt, was darauf hinweist, dass eine Nutzung nach der kostenlosen Nutzung erfolgt.

Diagramm von CVE-2024-30089

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 gespeichert wird. Zu Beginn von FSRendezvousServer::Close wird der Zeiger in FSContext2 geprüft, ob er ein gültiges Mitglied der Liste ist. In diesem Fall wird das Objekt im zweiten Aufruf von FSRegObject::Release am Ende der Funktion freigegeben. Während des vierten Aufrufs auf FSRendezvousServer::Close 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 FSRendezvousServer::Close. Der folgende Codeausschnitt zeigt das Use-after-free-Primitiv, das durch die Sicherheitslücke erhalten werden kann:

Screenshot des Code-UAF-Primitivpfads

Code Block 6: UAF-Primitivpfad

Komplexität des Angriffs

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.

Eine Retrospektive

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 FSRendezvousServer::Close gesucht.

Im Abschnitt über die Schwachstellen habe ich erwähnt, dass die eigentliche Ursache dieses Fehlers nicht die Einstellung
 

Irp->CurrentStackLocation->FileObject->FsContext2

 
zu 

NULL

 wenn der aufrufende Prozess ein fremder Prozess ist. Zu meiner Überraschung entdeckte ich genau diese Codezeile in einer frühen Version von mskssrv.sys:

Screenshot des Codes der frühen Version von FSRendezvousServer::Close, FsContext2 ist auf NULL gesetzt.

Code Block 7: Frühe Version von FSRendezvousServer::Close, FsContext2 ist auf NULL gesetzt

In dem oben gezeigten Codeblock ist FileObject->FsContext2 explizit auf NULL gesetzt, unabhängig vom Ergebnis der vorangegangenen Prozess-ID-Checks. Dadurch wird verhindert, dass FSRendezvousServer::Close in der nachfolgenden Anfrage IRP_MJ_CLOSE erneut aufgerufen wird, so dass die zusätzliche Dereferenzierung nicht auftreten kann. Seltsam – warum wurde diese Codezeile entfernt? Lassen Sie uns einen Blick auf eine spätere Version der Funktion werfen, in der der Fehler zum ersten Mal auftrat:

Screenshot von FsContext, der bei einer Funktions-Flag-Überprüfung auf NULL gesetzt wurde

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, FileObject->FsContext2 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 NULL 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.

Der Patch

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 FSRendezvousServer::InitializeContext verarbeitet wird:

Der Screenshot der IOCTL-Funktion beginnt mit einer Feature-Flag-Prüfung und überprüft, ob es sich bei dem aufrufenden Prozess um einen Frame-Server handelt.

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 KsIsCurrentProcessFrameServer TRUE zurückgeben, andernfalls wird NTSTATUS -Wert STATUS_ACCESS_DENIED zurückgegeben.

Werfen wir einen Blick auf KsIsCurrentProcessFrameServer:

Screenshot von KsIsCurrentProcessFrameServer, der eine SID-Prüfung am Token des aufrufenden Threads durchführt

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 NT SERVICE\FrameServer . 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 FrameServer 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.

Schlussfolgerung und nächste Schritte

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!

Danksagungen

Andréa Piazza, für die tollen Diagramme

Emma Kirkpatrick, weil sie mir geduldig das Windows-Sicherheitsmodell erklärt hat

Mixture of Experts | 12. Dezember, Folge 85

KI entschlüsseln: Wöchentlicher Nachrichtenüberblick

Schließen Sie sich unserer erstklassigen Expertenrunde aus Ingenieuren, Forschern, Produktführern und anderen an, die sich durch das KI-Rauschen kämpfen, um Ihnen die neuesten KI-Nachrichten und Erkenntnisse zu liefern.