
Detaillierte Analyse und Exploit-Implementierung für Windows PrintNightmare (CVE-2021-1675/34527) mit RPC-basierter Privilegieneskalation und Remote-Codeausführung über bösartige Druckertreiberinstallation.
= Print Nightmare Analysebericht :imagesdir: Figures :toc: :icons: font :figure-caption: Abb. :xrefstyle: short :pdf-theme: basic-theme.yml
Am 29. Juni 2021 wurde eine sehr schwerwiegende Windows-Druckdienst-Schwachstelle als 0day aufgedeckt, mit einem Basis-Score von 8,8, veröffentlicht auf GitHub (inzwischen gelöscht). Diese Schwachstelle ist der berühmte PrintNightmare: CVE-2021-34527, dessen Gefährlichkeit sogar die von EternalBlue übertrifft.
== Grundlegende Informationen zur Schwachstelle
Die Schwachstelle 34527 betrifft nahezu alle Versionen nach Windows 7 und Windows Server 2008, siehe <> für Details.
Aus Schadensperspektive kann ein Angreifer mit normaler Benutzerauthentifizierung unter Administratorrechten beliebigen Code remote ausführen. Hinsichtlich der Ausnutzbarkeit ist diese Schwachstelle sehr einfach auszunutzen, daher ist sie äußerst gefährlich.
Vom Schwachstellenmerkmal her basiert die 34527-Schwachstelle auf CVE-2021-1675. Die 1675-Schwachstelle ist eine lokale Privilegienausweitung und Remote Code Execution-Schwachstelle, die der 34527-Schwachstelle sehr ähnlich ist.
Bevor wir die Funktionsweise der Schwachstelle verstehen, sollten wir einen groben Überblick über die Architektur des Windows-Druckspoolers haben, um die Beziehungen zwischen den an der Schwachstelle beteiligten Modulen zu klären.
== CVE-2021-1675 Ablauf des Aufrufs
=== Architektur des Windows-Druckspoolers
Die Spooler-Architektur kann durch <<spooler_arch>> dargestellt werden:
[[spooler_arch]] .Print Spooler Architecture image::Print Spooler Architecture.png[]
Konkret dient der Druckspooler zur Verwaltung von Druckaufträgen und besteht aus folgenden Komponenten:
winspool.drv:: Die dem Benutzer bereitgestellte Dynamic-Link-Library-Datei. Diese Datei definiert die spoolerbezogenen Win32-APIs, die vom Benutzer aufgerufen werden können. Die APIs darin verwenden alle Remote Procedure Calls, um Dienste zu erhalten.
spoolsv.exe:: spoolsv.exe spielt die Rolle des Servers im System und ist das erste Programm, das API-Aufrufe verarbeitet. Dieses Design ermöglicht es dem Print Spooler, sowohl lokale als auch entfernte Druckaufträge unterschiedslos zu verarbeiten.
spoolsv.dll:: Das Routing-Programm. Es leitet die von spoolsv.exe empfangenen Druckanfragen an die einzelnen Druckanbieter weiter und entscheidet, welcher Druckanbieter die Anfrage letztendlich verarbeitet. Seine Aufgabe ist es, Druckaufträge als remote oder lokal zu unterscheiden. Auf einem entfernten Rechner weist es den Auftrag fest dem lokalen Druckanbieter zu.
localspl.dll:: Der lokale Druckanbieter. Die Hauptaufgabe des Druckanbieters besteht darin, die Anforderungen der Druckauftragsverwaltung zu erfüllen. Die überwiegende Mehrheit der APIs wird in diesem Modul implementiert.
Basierend auf der obigen Theorie untersuchen wir weiter. Wenn ich zum Beispiel die Funktion AddPrinterDriverEx aufrufe (CVE-2021-1675), durchläuft es den folgenden Ablauf:
=== Auswahl der Funktionsversion
Zunächst ist diese Funktion eigentlich ein Makro, das je nach lokaler Build-Umgebung die Unicode-Version (W) oder die ANSI-Version (A) auswählt, wie in <>:
[[AddPrinterDriverEx]] .AddPrinterDriverEx image::AddPrinterDriverEx.png[]
Aber ob Breitzeichen- oder Schmalzeichen-Version, das Ergebnis ist letztlich dasselbe, da die Zeichenketten im Windows-Kernel in Unicode kodiert sind. Daher wird der ANSI-Version-Aufruf letztendlich in einen Unicode-Version-Aufruf umgewandelt, wie in <>:
[[AnsiToUnicode]] .AnsiToUnicode image::AnsiToUnicode.png[]
Nachdem die Parameter der ANSI-Funktion in die Unicode-Version konvertiert wurden, wird eine Funktion aufgerufen (<>):
[[AnsiCallUnicode]] .AnsiCallUnicode image::AnsiCallUnicode.png[]
Und diese Funktion ist tatsächlich die Unicode-Version von AddPrinterDriverEx (<>):
[[GetUnicodeProcAddress]] .GetUnicodeProcAddress image::GetUnicodeProcAddress.png[]
=== Die API-Funktion sendet eine RPC-Anfrage an den Spooler-Server
Nach dem Eintritt in die Unicode-Version der Funktion wird zunächst basierend auf dem Wert von Level der Parameter-Typ der Funktion ausgewählt:
image::pDriverInfo.png[]
In diesem Exploit setzen wir Level auf 2, d.h. wir wählen den Typ des Parameters pDriverInfo als DRIVER_INFO_2-Struktur. Dann verarbeitet Windows die Funktionsparameter, und nach der Verarbeitung wird die API über einen Remote Procedure Call weiterverarbeitet:
image::set arguments.png[]
image::NdrClientCall3.png[]
=== MSRPC-Mechanismus
Der Remote Procedure Call-Mechanismus von Microsoft basiert auf dem DCE-Standard. Vereinfacht erklärt, werden beim Remote Procedure Call Prozesse auf einem entfernten System ausgeführt, die vom Programmierer oder System vordefiniert sind.
Die konkrete Methode von RPC besteht darin, die remote aufzurufende Funktion zu serialisieren, über das Netzwerk an das entfernte System zu senden, dort zu deserialisieren und auszuführen.
In der von Microsoft aufgebauten Architektur werden TCP/IP und SMB üblicherweise als Protokolle zur Übertragung von RPC-Aufrufen verwendet.
Um MSRPC zu verwenden, muss zunächst die IDL-Schnittstellenbeschreibung der aufzurufenden Funktion definiert werden, dann werden mit dem MIDL-Tool die entsprechenden Serialisierungs-Stubs für Client und Server generiert.
Für einige Win32-APIs ist der Server-Stub bereits vordefiniert, sodass wir nur den Client-Stub generieren und verwenden müssen.
MSRPC verwendet UUIDs, um einen bestimmten Protokolltyp zu identifizieren. Beispielsweise beschreibt MS-RPRN das Remote-Druckprotokoll. Alle Funktionen im Zusammenhang mit Remote-Druck gehören zu diesem Protokoll. MSRPC verwendet die UUID 12345678-1234-ABCD-EF00-0123456789AB zur Identifizierung des Protokolls (<<rprn_uuid>>):
[[rprn_uuid]] .MS-RPRN UUID image::spoolss uuid.png[]
Anschließend kann auf Basis dieser Verbindung mit einer Operatornummer (Opnum) die Funktion innerhalb des Protokolls identifiziert und remote aufgerufen werden. Beispielsweise verwendet AddPrinterDriverEx die Opnum 89 (<<addPrinterDriverEx_opnum>>):
[[addPrinterDriverEx_opnum]] .AddPrinterDriverEx Opnum image::AddPrinterDriverEx Opnum.png[]
Bei der Verwendung von MSRPC sind zwei Punkte zu beachten:
"As you can see in your output, the scripts are trying to connect to port 135 (endpoint mapper) in order to get the TCP/IP port where the DCOM endpoint is listening (that is a dynamic port)." -- SecureAuthCorp/impacket issue #412
=== spoolsv.exe verarbeitet API-Anfragen
[[call_flow]] .RpcAddPrinterDriverEx Call Flow image::Function Calls.png[]
Aus <<call_flow>> ist ersichtlich, dass spoolsv.exe diese Funktionen aufruft. Aus der internen Analyse der Funktionen geht hervor, dass dieses Modul außer der Initialisierung nichts weiter tut.
Schließlich ruft dieses Modul die von pLocalProvidor referenzierte Funktion auf, nämlich LocalAddPrinterDriverEx im Modul localspl.dll. Als lokaler Druckanbieter ist localspl tatsächlich das Modul, das die API-Funktionen implementiert.
=== Implementierungslogik des lokalen Druckanbieters
[[LocalAddPrinterDriverEx]] .LocalAddPrinterDriverEx image::LocalAddPrinterDriverEx.png[]
Zunächst zeigt <>, dass dieses Modul überprüft, ob der Spooler ordnungsgemäß läuft, und dann zur Funktion SplAddPrinterDriverEx springt.
[[SplAddPrinterDriverEx]] .SplAddPrinterDriverEx image::SplAddPrinterDriverEx.png[]