
Analyse détaillée et implémentation d'exploit pour Windows PrintNightmare (CVE-2021-1675/34527) avec élévation de privilèges basée sur RPC et exécution de code à distance via l'installation d'un pilote d'imprimante malveillant.
= Rapport d'analyse de Print Nightmare :imagesdir: Figures :toc: :icons: font :figure-caption: Figure :xrefstyle: short :pdf-theme: basic-theme.yml
Le 29 juin 2021, une vulnérabilité très grave du service d'impression Windows a été exposée en tant que 0day, avec un score de base de 8,8 ; l'exploit a été publié sur GitHub (depuis supprimé). Cette vulnérabilité est la célèbre PrintNightmare : CVE-2021-34527, dont le niveau de dangerosité dépasse même celui d'EternalBlue.
== Informations de base sur la vulnérabilité
La vulnérabilité 34527 affecte pratiquement toutes les versions à partir de Windows 7 et Windows Server 2008 ; voir <> pour plus de détails.
Du point de vue de l'impact, un attaquant peut, avec une authentification d'utilisateur ordinaire, exécuter du code arbitraire à distance avec des privilèges administrateur. En ce qui concerne la difficulté d'exploitation, cette vulnérabilité est très facile à exploiter et présente donc un danger considérable.
En ce qui concerne les caractéristiques de la vulnérabilité, la vulnérabilité 34527 est basée sur la vulnérabilité CVE-2021-1675. La vulnérabilité 1675 est une vulnérabilité d'élévation de privilèges locale et d'exécution de code à distance ; elle présente de très grandes similitudes avec la vulnérabilité 34527.
Avant de comprendre le principe de fonctionnement de la vulnérabilité, il convient d'avoir une compréhension approximative de l'architecture du spooler d'impression Windows, ce qui nous permettra de clarifier les relations entre les différents modules impliqués dans la vulnérabilité.
== Flux d'appel de CVE-2021-1675
=== Architecture du spooler d'impression Windows
L'architecture du spooler peut être représentée par <<spooler_arch>> :
[[spooler_arch]] .Architecture du spooler d'impression image::Print Spooler Architecture.png[]
Plus précisément, le spooler d'impression sert à gérer les tâches d'impression et se compose des éléments suivants :
winspool.drv:: Bibliothèque de liens dynamiques fournie à l'utilisateur. Ce fichier définit les API Win32 liées au spooler pour que les utilisateurs puissent les appeler. Toutes les API qu'il contient utilisent l'appel de procédure distante pour obtenir les services.
spoolsv.exe:: spoolsv.exe joue le rôle de serveur dans l'architecture, en tant que premier programme à traiter les appels d'API. Cette conception permet au spooler d'impression de traiter à la fois les travaux d'impression locaux et les travaux d'impression distants sans distinction.
spoolsv.dll:: Programme de routage. Il achemine les requêtes d'impression reçues par spoolsv.exe vers les différents fournisseurs d'impression et détermine quel fournisseur d'impression traitera finalement la requête. Son rôle est de distinguer si une tâche d'impression est distante ou locale. Sur une machine distante, il affecte la tâche au fournisseur d'impression local.
localspl.dll:: Fournisseur d'impression local. Le rôle principal du fournisseur d'impression est de répondre aux besoins de gestion des tâches d'impression ; la grande majorité des API sont implémentées dans ce module.
En poursuivant l'étude selon la théorie ci-dessus, prenons un exemple : lorsque j'appelle la fonction AddPrinterDriverEx (CVE-2021-1675), le flux suivant est suivi :
=== Sélection de la version de la fonction
Tout d'abord, cette fonction est en réalité une macro qui sélectionne la version Unicode (W) ou Ansi (A) en fonction de l'environnement de compilation local, comme <> :
[[AddPrinterDriverEx]] .AddPrinterDriverEx image::AddPrinterDriverEx.png[]
Mais qu'il s'agisse de la version à caractères larges ou à caractères étroits, le résultat est en fait identique, car les chaînes du noyau Windows sont encodées en Unicode ; l'appel de la version Ansi sera donc finalement converti en un appel de la version Unicode, comme <> :
[[AnsiToUnicode]] .AnsiToUnicode image::AnsiToUnicode.png[]
Une fois que tous les paramètres de la fonction Ansi sont convertis en version Unicode, une fonction est appelée (<>) :
[[AnsiCallUnicode]] .AnsiCallUnicode image::AnsiCallUnicode.png[]
Cette fonction n'est en réalité que la version Unicode d'AddPrinterDriverEx (<>) :
[[GetUnicodeProcAddress]] .GetUnicodeProcAddress image::GetUnicodeProcAddress.png[]
=== La fonction API envoie une requête RPC au serveur spooler
Une fois à l'intérieur de la fonction en version Unicode, une sélection du type des paramètres de la fonction est d'abord effectuée en fonction de la valeur de Level :
image::pDriverInfo.png[]
Dans cette vulnérabilité, nous définissons Level à 2, ce qui sélectionne le type du paramètre pDriverInfo comme étant la structure DRIVER_INFO_2. Ensuite, Windows traite les paramètres de la fonction et, une fois le traitement terminé, poursuit le traitement de cette API via un appel de procédure distante :
image::set arguments.png[]
image::NdrClientCall3.png[]
=== Mécanisme MSRPC
Le mécanisme d'appel de procédure distante de Microsoft est construit sur la norme DCE. En termes simples, l'appel de procédure distante consiste à exécuter des processus sur un système distant, ces processus étant prédéfinis par les programmeurs ou par le système.
Concrètement, RPC consiste à sérialiser la fonction que l'on souhaite appeler à distance, à la transmettre via le réseau vers le système distant, qui la désérialise puis l'exécute. Dans l'architecture construite par Microsoft, TCP/IP et SMB sont les protocoles habituellement choisis pour transporter les appels RPC.
Pour utiliser MSRPC, il faut d'abord définir la description d'interface IDL de la fonction à appeler, puis utiliser l'outil MIDL pour générer les stubs de sérialisation correspondants côté client et côté serveur. Pour certaines API Win32, le stub serveur est déjà défini par défaut ; il suffit donc de générer et d'utiliser le stub client.
MSRPC utilise un UUID pour identifier un type de protocole. Par exemple, MS-RPRN est utilisé pour décrire le protocole d'impression à distance ; toutes les fonctions liées à l'impression à distance font partie de ce protocole. MSRPC utilise l'UUID 12345678-1234-ABCD-EF00-0123456789AB pour identifier ce protocole (<<rprn_uuid>>) :
[[rprn_uuid]] .UUID MS-RPRN image::spoolss uuid.png[]
Ensuite, toujours sur la base de cette connexion, on peut utiliser le numéro d'opération (opnum) pour identifier les fonctions du protocole et les appeler à distance. Par exemple, AddPrinterDriverEx s'identifie par le numéro 89 (<<addPrinterDriverEx_opnum>>) :
[[addPrinterDriverEx_opnum]] .Opnum AddPrinterDriverEx image::AddPrinterDriverEx Opnum.png[]
Lors de l'utilisation de MSRPC, deux points méritent une attention particulière :
"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 traite les requêtes API
[[call_flow]] .Flux d'appel de RpcAddPrinterDriverEx image::Function Calls.png[]
Comme le montre <<call_flow>>, spoolsv.exe appelle ces fonctions, et d'après l'analyse interne des fonctions, ce module n'effectue aucune opération en dehors de l'initialisation. Enfin, ce module appelle la fonction pointée par pLocalProvidor, c'est-à-dire la fonction LocalAddPrinterDriverEx du module localspl.dll. En tant que fournisseur d'impression local, localspl est bien le module qui implémente les fonctionnalités de l'API.
=== Logique d'implémentation des fonctions du fournisseur d'impression local