Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2023-28252 | Kitploit
Outils/GitHubGitHub/fortra/cve-2023-28252
Escalade de PrivilègesCriminalistique MémoireAnalyse des VulnérabilitésExploitationRétro-ingénierieDébogueursExploitation de Binaires
GitHubfortra/cve-2023-28252

CVE-2023-28252

Voir le dépôt
18444il y a 3 ansVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Depuis février 2022, un nouveau ransomware a été signalé, qui semble utiliser une vulnérabilité zero-day de Windows, selon les recherches menées par Trend Micro.
Plus d'informations sur ce ransomware peuvent être trouvées à ce lien.
Selon l'analyse de Kaspersky, le groupe de ransomware Nokoyawa a utilisé d'autres exploits ciblant le pilote Common Log File System (CLFS) depuis juin 2022, avec des caractéristiques similaires mais distinctes, tous liés à un seul développeur d'exploit.
En avril 2023, lorsque Microsoft a publié le correctif, le CVE-2023-28252 a été attribué.
Auparavant, en 2022, un bogue similaire dans le même composant a été recherché par nous, et documenté dans ce billet de blog.

Format de fichier Common Log File System (CLFS) :

Pour mener l'analyse, il est nécessaire de connaître le format de fichier .blf, qui est géré par le pilote Common Log File System vulnérable appelé CLFS.sys et qui se trouve dans le dossier des pilotes dans system32.

Plus d'informations sur ce type de fichier peuvent être trouvées dans les liens ci-dessous :

https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part

https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system

https://github.com/ionescu007/clfs-docs/blob/main/README.md

https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe

La vulnérabilité :

Cette analyse est réalisée pour Windows 11 21H2, clfs.sys version 10.0.22000.1574 bien qu'elle fonctionne également sur Windows 10 21H2, Windows 10 22H2, Windows 11 22H2 et Windows server 2022.

Dans les versions précédentes de Windows, il est nécessaire d'ajuster certaines valeurs, sinon nous produirions un BSOD.

Microsoft Patch Tuesday avril 2023.

Une capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne Vous pouvez vérifier la version du pilote comme indiqué.

Lorsque la vulnérabilité a été publiée, en avril 2023, j'ai commencé avec Esteban Kazimirow à effectuer le reverse engineering du pilote CLFS.sys, bien que dans ce cas, analyser uniquement le correctif était très difficile pour déduire où se trouvait le bogue et comment le déclencher, car l'exploitation est très complexe.

Plus tard, un billet de blog est sorti dont l'auteur, à partir d'un échantillon de malware, a montré certaines parties du code décompilé par HexRays et quelques informations qui ont guidé la direction à prendre pour l'exploitation.

Évidemment, les informations fournies n'étaient pas complètes, mais sans cette aide, il aurait été peu probable d'arriver à construire le PoC puis un exploit fonctionnel.

Pour faciliter la compréhension, nous expliquerons d'abord comment construire le PoC, puis nous ferons l'analyse de la vulnérabilité.

Ce billet de blog contient deux sections :

Construction du PoC:

1- Obtenir les adresses noyau dont nous avons besoin pour l'exploitation.

2- Préparer le chemin pour créer les fichiers .blf :

3- Créer le fichier "trigger blf" en utilisant la fonction CreateLogFile()

4- Façonner le fichier "trigger blf"

5- Obtenir l'adresse noyau du BLOC DE BASE du trigger blf

6- Appeler AddLogContainer avec le handle du trigger blf

7- Préparer les fichiers spray blf

8- Préparer la mémoire pour effectuer le spray

9- Déclencher le bogue

Débogage :

1- Vérifier le spray mémoire

2- Regarder le RecordOffset[12] du trigger blf

3- Regarder la valeur iFlushBlock dans le fichier spray blf

4- Pourquoi lit-il à partir du BLOC 1 SHADOW au lieu du BLOC 0 CONTROL ?

5- Pourquoi la somme de contrôle est-elle égale à zéro dans les fichiers spray blf ?

6- Terminer l'exploitation.

7- Le vrai correctif

Construction du PoC:

1- Obtenir les adresses noyau dont nous avons besoin pour l'exploitation

Je vais créer une fonction nommée InitEnvironment pour obtenir certaines adresses noyau nécessaires.

Obtenez l'adresse EPROCESS de mon processus et stockez-la dans la variable g_EProcessAddress, puis l'adresse EPROCESS du processus SYSTEM, et stockez-la dans system_EPROCESS, puis l'adresse EHTREAD du thread principal de mon processus, et je la stocke dans g_EThreadAddress et enfin l'adresse du PREVIOUS MODE qui dans cette version du PoC ne sera pas utilisée.

Une capture d'écran d'un code d'ordinateur Description générée automatiquement avec une faible confiance

Cette méthode est bien connue, la fonction GetObjectKernelAddress appelle NtQuerySystemInformation deux fois avec le premier argument SystemExtendedHandleInformation, le premier appel est passé avec une taille incorrecte et retourne une erreur, mais retourne également la taille correcte qui est utilisée dans le second appel et obtient les informations de tous les handles, puis en parcourant dans une boucle les informations de chaque handle et dans le champ Object du handleinfo correct, elle obtient l'adresse recherchée dans le noyau.

Une image contenant du texte, une capture d'écran, une police, une ligne Description générée automatiquement

J'ai aussi besoin des adresses noyau des fonctions suivantes exportées par CLFS.sys :

• ClfsEarlierLsn

• ClfsMgmtDeregisterManagedClient

Et les fonctions exportées de NTOSKRNL.exe

• RtlClearBit/PoFxProcessorNotification

• SeSetAccessStateGenericMapping

Pour obtenir ces adresses, on utilise une méthode similaire à celle utilisée pour obtenir la base noyau des deux modules, en appelant NtQuerySystemInformation deux fois, mais dans ce cas le premier argument sera SYSTEM_INFORMATION_CLASS (dans le PoC nous utilisons la fonction FindKernelModulesBase à cet effet).

Une image contenant du texte, une police, une capture d'écran, une ligne Description générée automatiquement Ensuite, il charge CLFS.sys et NTOSKRNL.exe en tant que modules normaux en mode utilisateur en appelant LoadLibrary, obtient les adresses en mode utilisateur avec GetProcAddress, puis soustrait la base d'image de chacun, ce qui donne le décalage de la fonction et enfin ajoute chaque décalage aux bases noyau correspondantes et obtient ainsi les adresses noyau de toutes les fonctions nécessaires.

Une image contenant du texte, une police, une ligne, une capture d'écran Description générée automatiquement

2- Préparer le chemin pour créer les fichiers .blf :

Je crée une fonction appelée createInitialTriggerBlfFile qui va générer et écrire un fichier .blf.

Le chemin utilisé comme argument dans CreateLogFile est différent d'un chemin normal, par exemple pour ouvrir le fichier 1280.blf situé dans le dossier C:\Users\Public, nous devons définir le chemin LOG:C:\Users\Public\1280. Cela sera enregistré dans la variable stored_name_CreateLog.

Je fais cela en utilisant wsprintfW() car stored_env stocke le chemin C:\Users\Public, précédemment obtenu à partir des variables d'environnement. À cette chaîne, je vais ajouter la chaîne LOG: et un nom aléatoire à la fin, sans l'extension .blf.

Une capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Ce sera le chemin de mon fichier initial que j'appellerai "trigger blf". Bien sûr, je dois aussi sauvegarder le chemin normal vers le même fichier sans le LOG: devant et avec l'extension BLF pour l'ouvrir et le modifier avec CreateFile(), WriteFile() comme n'importe quel autre fichier ; ce chemin sera, par exemple : C:\Users\Public\1280.blf, et il sera stocké dans la variable stored_name_fopen.

Une image contenant du texte, une police, une ligne, une capture d'écran Description générée automatiquement

Bien sûr, les deux chemins correspondent au même fichier, et je dois utiliser l'un ou l'autre selon le cas.

3- Créer le fichier "trigger blf" en utilisant la fonction CreateLogFile().

La fonction CreateLogFile remplit une fonction assez similaire à CreateFile() (crée de nouveaux fichiers ou ouvre des fichiers existants et obtient leur handle), certains arguments sont même similaires, mais CreateLogFile() fonctionne uniquement avec les fichiers blf.

De plus, lorsqu'il ouvre un fichier existant, il vérifie que le format est correct, même si chaque bloc a une somme de contrôle et si celle-ci n'est pas correcte, il retourne une erreur.

Je vais créer 2 types de fichiers BLF :

  1. Le Trigger blf

  2. Le Spray blf

Les deux sont des fichiers blf mais modifiés de manière différente.

Un plan rapproché d'un code d'ordinateur Description générée automatiquement avec une faible confiance De cette façon, le PoC crée d'abord le fichier "trigger blf", en utilisant CreateLogFile, avec le chemin par exemple : LOG:C:\Users\Public\1280 que j'ai configuré auparavant, et qui était stocké dans la variable stored_name_CreateLog.

Le cinquième argument fCreateDisposition, comme dans CreateFileA(), peut prendre les valeurs suivantes :

Une image contenant du texte, une police, une ligne, un reçu Description générée automatiquement

Dans ce cas, j'utiliserai l'argument OPEN_ALWAYS, donc le fichier sera créé s'il n'existe pas et s'il existe, il sera ouvert. Comme le fichier n'existe pas encore, il sera créé avec un nom aléatoire.

logFile = CreateLogFile(stored_name_CreateLog, GENERIC_READ | GENERIC_WRITE, 1, 0, 4, 0);

CreateLogFile() créera notre fichier "trigger blf" avec ses 6 blocs et leurs sommes de contrôle correspondantes et retournera le handle qui sera stocké dans la variable logFile.

Une image contenant du texte, une capture d'écran, une police, un nombre Description générée automatiquement

Chaque bloc aura, à partir du décalage indiqué dans la colonne de gauche, un en-tête dont la taille est de 0x70 octets.

Ainsi, par exemple, l'en-tête du BLOC DE CONTRÔLE va du décalage 0x0 à 0x70.

Une capture d'écran d'un ordinateur Description générée automatiquement avec une faible confiance

Tous les en-têtes de tous les blocs ont la même structure appelée _CLFS_LOG_BLOCK_HEADER.

Voici la structure de l'en-tête :

Une capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Au décalage 0xC de l'en-tête, je trouve la somme de contrôle, donc comme le BLOC DE CONTRÔLE commence au décalage 0, la somme de contrôle sera au décalage 0xC du fichier, et ainsi chaque bloc aura sa somme de contrôle à 0xC du début de son bloc.

Une capture d'écran d'un ordinateur Description générée automatiquement avec une faible confiance

4- Façonner le fichier "trigger blf" :

Pour modifier le fichier trigger blf, je dois l'ouvrir comme un fichier normal soit avec CreateFileA soit avec fopen puis le modifier avec WriteFile ou fwrite respectivement, j'effectue cela au début de la fonction fun_prepare du PoC.

Rappelez-vous que le chemin normal est stocké dans la variable stored_name_fopen, donc je l'utilise pour ouvrir le fichier avec wfopen_s (qui est une variante de fopen qui supporte les chaînes Unicode).

Le fichier est modifié dans la fonction craftTriggerBlfFile appelée depuis fun_prepare.

Une image contenant du texte, une ligne, une police, une capture d'écran Description générée automatiquement

Ensuite, j'appelle fseek pour pointer vers le décalage à modifier, puis avec fwrite le fichier est modifié.

Une capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenneLes modifications à apporter au fichier "trigger blf" sont les suivantes :

Après avoir effectué ces modifications, la fonction FixCRCFile est appelée pour calculer la nouvelle somme de contrôle et corriger les sommes de contrôle des 4 premiers blocs. Les deux blocs suivants n'ont aucune modification, il n'est donc pas nécessaire de recalculer leurs sommes de contrôle.

Une image contenant du texte, une police, une capture d'écran, un nombre Description générée automatiquement

5- Obtenir l'adresse noyau du BLOC DE BASE du trigger blf :

Le pilote CLFS.sys lit les six blocs du fichier, et pour stocker leur contenu, il effectue une allocation dans le pool noyau.

Une image contenant du texte, une capture d'écran, une police, un nombre Description générée automatiquement

Il y a une structure très importante de taille 0x90 qui, dans le billet de blog précédent sur CVE-2022-37969, grâce au reverse engineering, j'ai trouvé certains champs et je l'ai appelée pool_0x90. Après beaucoup plus de reverse engineering, je sais maintenant que son vrai nom est m_rgBlocks et que, au fur et à mesure que le contrôleur alloue de la mémoire pour copier le contenu de chaque bloc à partir du fichier, il y enregistre la taille de chaque bloc, le décalage de début et l'adresse noyau où il a été stocké.

Une image contenant du texte, une capture d'écran, une police Description générée automatiquement

Elle contient six CLFS_METADATA_BLOCK qui correspondent à chaque bloc par son numéro.

Chaque structure CLFS_METADATA_BLOCK a une longueur de 0x18 octets. (0x18*6=0x90)

Une image contenant du texte, une police, une ligne, un nombre Description générée automatiquement Au décalage 0, il y a une union, mais au moins dans cet exploit seul le champ pbImage est utilisé, donc en simplifiant cela donnerait :

L'allocation de cette structure peut se faire à partir de deux endroits différents du pilote CLFS.sys, selon la création d'un nouveau fichier ou si un fichier existant est ouvert. Dans le cas de la création d'un nouveau fichier, le pilote alloue les 0x90 octets depuis CClfsBaseFilePersisted::CreateImage+28A, tandis que dans le cas d'un fichier existant, il alloue depuis CClfsBaseFilePersisted:ReadImage+6E.

Après cela, je vais obtenir l'adresse de début du bloc 2 qui correspond au fichier trigger blf, appelé BLOC DE BASE qui commence au décalage 0x800 et dont la longueur est 0x7a00.

Une image contenant du texte, une capture d'écran, une police, un nombre Description générée automatiquement

À l'intérieur de la fonction fun_prepare ci-dessous, cette adresse sera trouvée dans le noyau en utilisant cette partie du code.

Une capture d'écran d'un code d'ordinateur Description générée automatiquement avec une faible confiance

Tout d'abord, la fonction getBigPoolInfo trouve toutes les allocations dans le pool qui ont le tag "Clfs" et une taille de 0x7a00, puis les stocke dans un tableau.

Après cela, elle ouvre à nouveau le fichier trigger blf précédemment modifié en utilisant CreateLogFile avec l'argument OPEN_EXISTING, donc elle ouvre un fichier existant, ce qui effectuera l'allocation de son BLOC DE BASE.

Lorsque getBigPoolInfo est appelée à nouveau, il y aura un nouveau pool "Clfs" de taille 0x7a00, et son adresse est récupérée en appelant NtQuerySystemInformation deux fois.

L'adresse du BLOC DE BASE du fichier trigger blf est stockée dans la variable CLFS_kernelAddrArray.

Notez que si le fichier trigger blf modifié n'a pas la somme de contrôle correcte, la fonction CreateLogFile() échouera.

6- Appeler AddLogContainer avec le handle du trigger blf :

La dernière partie de la fonction fun_prepare appelle l'api AddLogContainer en utilisant le handle du fichier trigger blf.

Un plan rapproché d'un code d'ordinateur Description générée automatiquement avec une faible confiance

7- Préparer les fichiers spray blf :

Dans la dernière fonction du PoC appelée to_trigger, un deuxième type de fichier blf sera créé.

Je le nommerai spray blf.

Ce type de fichier sera utilisé pour remplir un espace mémoire (spray), 10 exemplaires de ce type sont nécessaires, mais initialement un seul est créé. Une image contenant du texte, une police, une ligne, une capture d'écran Description générée automatiquement

Trois tableaux seront créés pour stocker les noms aléatoires de ces fichiers :

stored_log_arrays: stocke dix nouveaux noms aléatoires de fichiers .blf qui seront utilisés avec CreateLogFile.

stored_container_arrays: stocke des noms aléatoires pour créer dix nouveaux fichiers conteneurs.

stored_fopen_arrays: stocke les noms des fichiers journaux du premier tableau (variable stored_log_arrays), mais avec leur chemin normal (sans la chaîne "LOG:") et avec l'extension .blf.

Une capture d'écran d'un code d'ordinateur Description générée automatiquement avec une confiance moyenne

À chaque itération, le fichier blf est copié en utilisant CopyFileW, les noms stockés dans les tableaux sont attribués.

La fonction fun_trigger appelle craftSprayBlfFile où des modifications sont apportées à chaque fichier et FixCRCFile corrigera les CRC.

Une capture d'écran d'un programme informatique Description générée automatiquement avec une confiance moyenne

En résumé, j'ai créé 10 fichiers similaires (spray blf) avec des noms aléatoires avec les modifications suivantes :

Une capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

La dernière modification consiste à copier l'intégralité du bloc 0 (BLOC DE CONTRÔLE) vers le bloc 1 (BLOC DE CONTRÔLE SHADOW)

Une capture d'écran d'un ordinateur Description générée automatiquement avec une faible confiance

L'effet de ces modifications, plus celles apportées au fichier trigger blf, sera expliqué plus tard dans le chapitre de débogage.

Certaines de ces modifications sont celles qui produisent la vulnérabilité, tandis que d'autres ne sont nécessaires que pour contourner les vérifications du pilote.

À ce stade, les fichiers sont déjà créés et modifiés, prêts à effectuer le spray, puis lorsqu'ils sont ouverts avec CreateLogFile, ils seront situés dans la zone mémoire que nous souhaitons, comme cela sera montré plus tard.

8- Préparer la mémoire pour effectuer le spray

Dans la fonction to_trigger, un tableau de 12 éléments est créé, contenant l'adresse du BLOC DE BASE du fichier trigger blf plus 0x30.

Ensuite, dans la fonction fun_pipeSpray, la mémoire est remplie avec un spray de pipes, à l'intérieur il y a une boucle qui appelle CreatePipe et crée le nombre de pipes passé comme premier argument, le deuxième argument est un tableau qui stockera les handles de toutes les pipes créées.

Dans une boucle, elle appelle CreatePipe créant des pipes en lecture-écriture.

De cette façon, d'abord 0x5000 pipes seront créées, puis elle appelle à nouveau pour créer d'autres 0x4000 pipes.

Ensuite, elle utilise WriteFile pour écrire dans les 5000 premières pipes le tableau récemment créé avec les adresses de BASE BLOCK + 0x30 du fichier trigger blf.

Une capture d'écran d'un code d'ordinateur Description générée automatiquement avec une faible confiance

Maintenant, elle a déjà un bloc compact créé en mémoire, elle libérera 0x667 pipes à partir du numéro 0x2000 et jusqu'au 0x2667, car en mémoire les pipes ne sont pas dans le même ordre que celui dans lequel elles ont été créées ; ce qui va se passer, c'est qu'il y aura des espaces libres dans ce bloc mémoire.

Une image contenant du texte, une capture d'écran, une police, un logiciel Description générée automatiquement Notez que les allocations des pipes ont une taille utilisateur de 0x90 octets, donc lorsqu'elles sont libérées, nous aurons

Cela libère les espaces mémoire de taille 0x90 entre la mémoire remplie de pipes.
Ensuite, elle boucle pour appeler CreateLogFile avec les 10 fichiers spray blf.
Lorsque CreateLogFile est appelé pour ouvrir des fichiers existants, l'allocation de 0x90 octets est effectuée pour les m_rgBlocks, un pour chaque fichier spray blf, donc ces allocations occuperont les trous laissés lors de la libération des pipes puisqu'elles ont la même taille.

Une capture d'écran d'un code d'ordinateur Description générée automatiquement avec une confiance moyenne

Ensuite, elle répète le processus d'écriture dans les 0x4000 pipes finales du tableau qui contient l'adresse de BASE BLOCK +0x30 du trigger blf.

9- Déclencher le bogue

Toutes ces manipulations créent un espace mémoire contrôlé, je vais vous montrer comment il est lorsqu'il est en cours de débogage, mais l'idée est que les m_rgBlocks de chaque fichier spray blf occupent les trous de 0x90 octets qui ont été libérés.

Ensuite, déjà dans la partie finale, le bogue est déclenché à l'intérieur d'un while(1) en utilisant un appel à AddLogContainer sur les fichiers spray blf.A picture containing text, font, line, number Description
automatically generated

Pendant que cela se produit, le bogue est déclenché :

A screenshot of a computer program Description automatically generated
with low confidence

Cette boucle while se termine lorsqu'elle trouve le jeton Système, en utilisant la fonction NtFsControlFile qui lira les attributs des pipes.

A screenshot of a computer code Description automatically generated
with low confidence

Ensuite, en utilisant CreateLogFile, il réécrit à nouveau le jeton de notre processus avec le jeton Système récemment trouvé et de cette manière nous obtenons l'élévation de privilèges.

A picture containing text, font, line, screenshot Description
automatically generated

Ensuite, il restaure certaines valeurs, ferme les handles des pipes et des fichiers blf, et exécute un Notepad en tant que Système pour vérifier que l'élévation a bien eu lieu.

A screenshot of a computer Description automatically generated with
medium confidence

Notez les fichiers blf créés dans le dossier PUBLIC. Souvenez-vous que si vous voulez réessayer, vous devez d'abord supprimer les fichiers créés. Certains seront verrouillés et ne pourront pas être supprimés, mais le PoC fonctionnera quand même.

A screenshot of a computer Description automatically generated with
medium confidence

Débogage :

1- Vérification du spray mémoire

Avant de commencer avec l'effet des modifications sur trigger blf et spray blf pour réaliser l'exploitation, je dois vérifier que m_rgBlocks des spray blf files se trouvent dans les trous qui apparaissent dans la distribution mémoire, après avoir effectué le spray de pipes et la libération ultérieure d'un nombre fixe de pipes.

Lorsque cette procédure se termine, un pipe devrait se trouver sous les 0x90 octets de m_rgBlocks, donc lorsque m_rgBlocks est utilisé, un OUT OF BOUNDS se produira et il lira à partir de ce pipe qui se trouve en dessous.

Le PoC a un point idéal pour placer un point d'arrêt :

A screen shot of a computer code Description automatically generated
with low confidence

À ce stade, l'ouverture des fichiers spray blf est terminée et la fonction AddLogContainer n'est pas encore appelée.

Pour déboguer en mode utilisateur, j'utiliserai x64dbg et pour le mode noyau, IDA avec le plugin Windbg.

A screen shot of a computer Description automatically generated with
low confidence

À ce stade, la mémoire devrait déjà être préparée, et je peux voir la distribution.

Je vais mettre IDA en pause pour trouver un point intéressant où placer un point d'arrêt.

Je vais configurer un point d'arrêt à CClfsBaseFilePersisted::AddContainer, qui est appelé depuis AddLogContainer et au début, il a le registre RCX pointant vers la structure CClfsBaseFilePersisted et à l'offset 0x30 il y a un pointeur vers m_rgBlocks.

A screen shot of a computer Description automatically generated with
medium confidence

Lorsque le point d'arrêt est atteint, je vérifie sur la pile d'appels que AddLogContainer est appelé depuis mon PoC.

A screenshot of a computer Description automatically generated with
medium confidence

Le registre RCX pointe vers :

A screenshot of a computer Description automatically generated with
low confidence A screenshot of a computer
Description automatically generated with low
confidence

Le premier champ est le pointeur vers une vtable (CLFS! CClfsBaseFilePersisted::'vftable') et à l'offset 0x30 se trouve le pointeur vers m_rgBlocks.

A screenshot of a computer code Description automatically generated
with medium confidence

Les blocs 0, 1, 4 et 5 n'ont pas encore enregistré le pbImage, tandis que les blocs 2 (BASE BLOCK) et 3 (SHADOW BLOCK) l'ont fait.

Chaque bloc dans la table m_rgBlocks a son cbOffset qui est le décalage où le bloc commence dans le fichier, cbImage est la taille du bloc, et eBlockType est le type de bloc.

Si le spray est correct, en dessous de m_rgBlocks il devrait y avoir un pipe et à l'intérieur, les pointeurs vers BASE BLOCK + 0x30 du trigger blf.

A screenshot of a computer code Description automatically generated
with low confidence

La commande "!pool" sur windbg affiche la distribution mémoire :

A screenshot of a computer Description automatically generated with
medium confidence

Chaque m_rgBlocks a une étiquette "Clfs" et sa taille est 0xa0 car c'est la taille utilisateur 0x90 plus l'en-tête 0x10 et en dessous il y a un pipe avec l'étiquette "NpFr" qui a la même taille utilisateur 0x90

  • l'en-tête 0x10.

Comme la distribution n'est pas une science exacte, certains "Clfs" ont été placés continuellement, ce qui n'est pas souhaitable, mais celui avec lequel je travaille est correctement placé, suivi d'un pipe.

2-Examen du RecordOffset[12] du trigger blf

L'une des premières modifications qui affecte est celle apportée dans le fichier trigger blf à l'offset 0x858, où la valeur 0x369 est stockée.

A close-up of a sign Description automatically generated with low
confidence

Le BASE BLOCK commence à l'offset 0x800 dans le fichier.

A picture containing text, screenshot, font, line Description
automatically generated

À l'intérieur du _CLFS_LOG_BLOCK_HEADER à l'offset 0x800+0x58 (0x58 depuis le début de l'en-tête BASE BLOCK).

A picture containing text, screenshot, font, display Description
automatically generated

À l'offset 0x28 commence le tableau RecordOffsets (DWORD).

En avançant de 0x30 octets, à l'offset 0x58 (0x828+0x30=0x858 depuis le début), se trouve le champ 12 de RecordOffsets.

A picture containing text, font, screenshot, graphics Description
automatically generated

J'exécute le PoC jusqu'à CreateLogFile comme montré dans l'image ci-dessous :

A screenshot of a computer code Description automatically generated
with low confidence A screenshot of a computer
code Description automatically generated with low
confidence

A screenshot of a computer Description automatically
generated

Avant d'entrer dans CreateLogFile, je vais placer un point d'arrêt à un endroit où la valeur 0x369 n'a pas encore été utilisée.

Dans le cas où CreateLogFile ouvre un fichier existant, la structure m_rgBlocks est allouée ici :

CClfsBaseFilePersisted::ReadImage+6E

Donc, je vais placer un point d'arrêt sur IDA juste ici :

A screenshot of a computer Description automatically generated with
medium confidence

Lorsque le point d'arrêt est déclenché :

A picture containing text, screenshot, font, number Description
automatically generated

Dans m_rgBlocks il y a encore des déchets car il n'est pas encore initialisé, mais dès que pbImage du bloc 2 est alloué, l'adresse sera sauvegardée à l'offset 0x30 depuis le début, car le premier champ à l'intérieur de chaque CLFS_METADATA_BLOCK est pbImage.

A screen shot of a computer Description automatically generated with
medium confidence

A picture containing text, screenshot, font, line Description
automatically generated

Maintenant, je configure un point d'arrêt matériel en écriture : ba w1 ffffd003'7f5bea30

Après l'initialisation à zéro, il s'arrête lorsqu'il sauvegarde pbImage.

L'analyse dit que cela correspond au block0, car il ne prend pas en compte la constante r14*8 qui est 0x30 après, par conséquent il écrit vraiment le pbImage du block 2.

A picture containing text, screenshot, font Description automatically
generated

Notez que CClfsBaseFilePersisted::ReadMetadataBlock est utilisé pour allouer n'importe lequel des blocs, en utilisant la taille passée en argument.

A picture containing text, font, screenshot, line Description
automatically generated

Maintenant, placez un point d'arrêt lecture/écriture à 0x58 du bloc de base, pour voir quand il utilise la valeur 0x369.

ba r1 FFFF978A'16ECF000+0x58

Lorsque le point d'arrêt est atteint, il lit la valeur 0x369 située au RecordOffset[12], l'ajoute à un pointeur étrange dans r14 et incrémente le contenu de RAX+r14.

Quelques lignes plus haut dans le code, ESI a la valeur 0x13 et multiplie par 0x18, qui est la taille de chaque bloc dans m_rgBlocks.

WINDBG>? 0x18*0x13

Evaluate expression: 456 = 00000000'000001c8

Si j'ajoute la valeur de r8= 0x1c8 qui est supérieure à 0x90, à l'adresse initiale de m_rgBlocks, il lira OUT OF BOUNDS.

A screenshot of a computer Description automatically
generated

En dessous de m_rgBlocks, se trouve le pipe avec le pointeur vers BASE BLOCK + 0x30, il lit ce pointeur qui a été stratégiquement placé à l'intérieur du pipe.

A screenshot of a computer program Description automatically generated
with low confidenceLa position actuelle dans le code a été appelée depuis l'instruction while(1) du module principal.

À l'intérieur du fichier spray blf j'ai stratégiquement placé la valeur 0x13 à l'offset 0x48a (iFlushBlock).

A picture containing text, font, line, screenshot Description
automatically generated

3-Examen de la valeur iFlushBlock dans le fichier spray blf.

À l'offset 0x8a du fichier spray blf, iFlushBlock du BLOCK 0 est situé, dont la valeur est 4, tandis que l'offset 0x48a appartient à iFlushBlock du BLOCK 1, et sa valeur est 0x13

A screenshot of a computer Description automatically generated with
medium confidence

Maintenant, je dois découvrir pourquoi il lit iFlushBlock = 0x13 du BLOCK 1 au lieu de iFlushBlock = 4 du BLOCK 0.

4-Pourquoi lit-il depuis le BLOCK 1 SHADOW au lieu du BLOCK 0 CONTROL ?

Si je regarde en arrière pour trouver d'où vient le 0x13, je vois sur la pile d'appels que WriteMetadataBlock est appelé depuis CClfsBaseFilePersisted::ExtendMetadataBlock+416, là le deuxième argument iFlushBlock est EDX=0x13, qui vient de r9w.

A screenshot of a computer code Description automatically generated
with low confidence

A picture containing text, screenshot, font, line Description
automatically generated

A screenshot of a computer program Description automatically generated
with low confidenceQuelques lignes avant, CClfsBaseFile::GetControlRecord a été appelé pour récupérer l'adresse du BLOCK 0, peut-être que le problème est ici, donc je vais redémarrer et placer un point d'arrêt dessus.

GetControlRecord appelle CClfsBaseFile::AcquireMetadataBlock qui devrait remplir la table m_rgBlocks avec l'adresse du block 0, quand je passe outre cette fonction, elle obtient l'adresse du block 1, donc le problème se produit à l'intérieur de CClfsBaseFile::AcquireMetadataBlock.

En ajoutant 0x8A à l'adresse récupérée, je peux confirmer que la valeur 0x13 qui appartient au BLOCK 1 est présente.

A screenshot of a computer code Description automatically generated
with medium confidence

Je vais redémarrer et placer un point d'arrêt là :

A screenshot of a computer program Description automatically generated
with low confidence

CClfsBaseFile::GetControlRecord+27 appelle CClfsBaseFile::AcquireMetadataBlock

A screenshot of a computer Description automatically
generated

Le second argument passé à AcquireMetadataBlock est zéro, il correspond au block 0, il va copier depuis le fichier et stocker son adresse dans m_rgBlocks A screenshot of a computer Description
automatically generated with medium confidence.

Dans l'énumération _CLFS_METADATA_BLOCK_TYPE, les noms des types de bloc sont différents de ceux que j'ai utilisés, mais ce sont les mêmes 6 blocs.

A picture containing text, screenshot, font, line Description
automatically generated

Après avoir vérifié que le type de bloc est inférieur au maximum m_cBlocks=6, il sauvegarde une valeur de référence pour éviter de lire le même bloc deux fois.

A screenshot of a computer code Description automatically generated
with low confidence

ReadMetadataBlock est appelé, le problème de la lecture du block 1 au lieu du block 0 serait à l'intérieur de cette fonction.

A picture containing text, font, number, line Description
automatically generated

Si tout va bien, il alloue en utilisant cbImage comme taille et stocke l'adresse dans le champ block 0-> pbImage dans m_rgBlocks.

A picture containing text, font, line, screenshot Description
automatically generated

La commande !pool affiche l'étiquette et la taille allouée.

A picture containing text, screenshot, font, line Description
automatically generated

A picture containing text, screenshot, font, line Description
automatically generatedDonc, j'ai déjà l'adresse de pbImage du block 0 stockée dans m_rgBlocks, donc j'ai besoin de voir pourquoi il copie les octets du block 1 là au lieu des octets du block 0.

J'arrive à un appel à CClfsContainer::ReadSector où un pointeur vers une variable contenant pbImage est passé, pour écrire les octets.

Remarquez les modifications dans le contenu de pbimage lorsque je passe outre ReadSector.

En ajoutant 0x8a à pbImage je peux trouver la valeur 4 qui est la valeur correcte, au lieu de 0x13, donc le problème doit se produire plus tard.

Après avoir appelé ClfsDecodeBlock, il retourne une erreur 0x0C01A000A.

CClfsBaseFilePersisted::ReadMetadataBlock+153 appelle ClfsDecodeBlock

Après cette erreur, il ajoute 1 au type et appelle CClfsBaseFilePersisted::ReadMetadataBlock à nouveau mais avec le type 1 pour lire le block 1.

A screenshot of a computer Description automatically
generated

Dans CClfsBaseFilePersisted::ReadMetadataBlock, il alloue et stocke un nouveau pbImage dans m_rgBlocks pour le block 1.

A screenshot of a computer code Description automatically generated
with low confidence

Les blocks 0 et 1 ont des adresses différentes, maintenant si j'ajoute 0x8a à l'adresse du block 1, sa valeur est 0x13.

Peut-être que puisque le block 0 a retourné une erreur, il utilise le block 1 et le retourne à GetControlRecord en tant que Block de Contrôle.

Comme montré précédemment, lorsqu'il utilise la valeur 0x13 au lieu de 4, il va au-delà des limites de m_rgBlocks et lit les valeurs du spray de pipes contrôlées par moi.

A picture containing text, screenshot, font Description automatically
generatedEnsuite, il libère le pbImage du block 0 et copie le pointeur du block 1 vers le block 0.

A screenshot of a computer Description automatically
generated

Il serait nécessaire de trouver la valeur qui cause l'erreur 0x0C01A000A à l'intérieur de ClfsDecodeBlock.

À l'intérieur de ClfsDecodeBlock, la checksum du premier bloc est zéro, c'est l'erreur 0xC01A000A.

A screenshot of a computer Description automatically generated with
medium confidence

5-Pourquoi la somme de contrôle est égale à zéro dans les fichiers blf spray ?

Avant d'appeler AddLogContainer, en ouvrant n'importe quel fichier spray blf avec un éditeur hexadécimal, la checksum a été changée en zéro.

A screenshot of a computer Description automatically
generated

Elle aurait dû être changée avant lorsqu'il a été ouvert avec CreateLogFile.

A picture containing text, screenshot, display, font Description
automatically generated

Pour une raison quelconque, les fichiers spray blf se retrouvent après la sortie de CreateLogFile avec la checksum du block 0 égale à 0 et retournent un handle valide, voyons pourquoi cela se produit.

Je m'arrête à CreateLogFile avant d'ouvrir un fichier spray blf.

A screenshot of a computer Description automatically generated with
medium confidence

Notez qu'avant d'appeler CreateLogFile, les fichiers spray ont la checksum correcte dans block 0 et après avoir terminé la fonction, la valeur de la checksum devient zéro.

A screenshot of a computer code Description automatically generated
with low confidence

Donc, je place un point d'arrêt sur CClfsBaseFile::GetControlRecord, pour regarder à l'intérieur.

A picture containing text, screenshot, font, line Description
automatically generatedAprès avoir passé CClfsContainer::ReadSector, la checksum n'est pas zéro.

Avant d'entrer dans le calcul du CRC32, il met le champ checksum à zéro en mémoire pour calculer le CRC, et le résultat est correct.

A picture containing text, font, screenshot Description automatically
generated

A picture containing text, font, line, screenshot Description
automatically generated

Ensuite, il vérifie la valeur de eExtendState =2 et va à WriteMetadataBlock.

A screenshot of a computer Description automatically generated with
medium confidence

Ici, la checksum est encore zéro en mémoire, j'ai juste besoin de voir quand cette valeur est écrite dans le fichier.

A picture containing text, font, line, screenshot Description
automatically generated

Il vérifie certaines valeurs qui sont conçues dans le fichier blf spray pour atteindre CClfsBaseFilePersisted::ExtendMetadataBlock.

A screenshot of a computer program Description automatically generated
with medium confidence

Après une boucle pour lire les blocs qui n'ont pas encore été lus, le block 0 continue avec checksum = 0.

A white rectangle with black text Description automatically generated
with low confidence

En arrivant à WriteMetadataBlock.

Comme je suis en train de l'exécuter avant qu'il ne remplace le block 0 par 1, la valeur iFlushBlock du fichier blf spray est encore 4, la valeur correcte.

A screenshot of a computer Description automatically generated with
medium confidence

Maintenant, il travaille avec le block 4, et il va écrire le block 4 dans le fichier, ici n'est pas encore le problème.

Ensuite, il arrive à CClfsBaseFilePersisted::FlushControlRecord

A screenshot of a computer program Description automatically generated
with medium confidence

À l'intérieur, il atteint WriteMetadataBlock, mais avec l'argument 0, pour écrire le block 0 dans le fichier**.

A screenshot of a computer Description automatically generated with
medium confidence

Ensuite, ClfsEncodeBlock retourne l'erreur 0xC01A000A, bien qu'il va écrire le fichier avec le mauvais block 0 dans CClfsContainer::WriteSector, juste en dessous.

A screenshot of a computer Description automatically generated with
medium confidence

La variable var_54 stocke la valeur d'erreur 0xC01A000A et sera vérifiée avant de quitter la fonction.

A screenshot of a computer Description automatically generated with
medium confidence

Mais après avoir appelé CClfsContainer::WriteSector qui ne retourne pas d'erreur, le contenu de var_54 est écrasé avec zéro.

Donc, la fonction retourne zéro sans erreur et continue de fonctionner car CreateLogFile retournera un handle au lieu d'une valeur d'erreur.

A screenshot of a computer Description automatically
generated

6-Fin de l'exploitation.

La valeur 0x13 dans iFlushBlock provoque un dépassement hors limites et il lira le pointeur qui se trouve dans les pipes et qui pointe vers le Base Block +30 du trigger blf.

A screenshot of a computer Description automatically
generatedEnsuite, il ajoute 0x28 à ce pointeur, ( 0x58 depuis le début du bloc de base du déclencheur blf) qui a la valeur 0x369.

Capture d'écran d'un programme informatique Description générée automatiquement avec une confiance moyenne

L'instruction INC va augmenter la valeur 0x14 de 1 et se répète 4 fois, donc 0x14 se termine à 0x18.

WINDBG>db r14+369

ffffcb82'091e7397 14 00 00 00

Après cela, CreateLogFile est appelé, et lit la valeur 0x1858.

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Gros plan d'une carte Description générée automatiquement avec une faible confianceGetSymbol vérifie si le bloc factice précédemment créé dans déclencheur blf, pointé par l'offset 0x1858, a les bonnes valeurs.

Une image contenant du texte, police, ligne, capture d'écran Description générée automatiquement

si le pointeur n'avait pas été incrémenté plusieurs fois, il aurait la valeur originale 0x1458 et pointerait vers le bon bloc.

Après avoir quitté GetSymbol, il va utiliser ce bloc factice ici.

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Ensuite, il va lire la valeur de l'offset 0x18 du bloc factice où j'ai placé 0x05000000 et sauter au contenu de ce qui s'y trouve.

WINDBG>dps 0x5000000

00000000'05000000 00000000'05001000

Il lit le contenu de 0x05000000 et son 0x05001000 et là se trouve ClfsEarlierLsn.

Capture d'écran d'un programme informatique Description générée automatiquement avec une faible confiance

Cette fonction est utilisée pour retourner la valeur 0xFFFFFFFF dans RDX bien que cette première fois cette valeur ne soit pas utilisée.

Le deuxième appel a lieu ici, il appelle PoFxProcessorNotification qui se trouvait à 0x501000 +8

Une image contenant du texte, police, capture d'écran, ligne Description générée automatiquement Une image contenant du texte, police, capture d'écran, ligne Description générée automatiquement

WINDBG>dps 00000000**'05001000**

00000000'05001000 fffff805'7ab13220 CLFS! ClfsEarlierLsn

00000000'05001008 fffff805'769dc3b0 nt! PoFxProcessorNotification

Capture d'écran d'un écran d'ordinateur Description générée automatiquement avec une faible confiance

dans cette fonction RCX = 0x05000000 , il vérifie que 0x40 octets plus loin doit être non nul

WINDBG>dps rcx+40

00000000'05000040 00000000'05000000

L'adresse vers laquelle sauter sera 0x68 plus loin.

WINDBG>dps rcx+68

00000000'05000068 fffff805'7ab2bfb0 CLFS! ClfsMgmtDeregisterManagedClient

Et l'argument sera 0x48 octets plus loin.

WINDBG>dps rcx+48

00000000'05000048 00000000'05000400

Le ClfsMgmtDeregisterManagedClient, c'est une fonction pratique car je peux contrôler l'argument et j'ai également deux sauts vers des fonctions que je contrôle.

Capture d'écran d'un ordinateur Description générée automatiquement

Le premier appel est de nouveau à ClfsEarlierLsn qui a retourné RDX=0xFFFFFFFF.

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

il va prendre la source à écrire à partir du contenu de RDX=0xFFFFFFFF.

WINDBG>dps rdx

00000000'ffffffff ffff8005'3a4ee000

À l'adresse 0xFFFFFFFF j'avais stocké le system_EPROCESS & 0xfffffffffffff000.

Une image contenant du texte, police, ligne, nombre Description générée automatiquement

La destination est le pointeur situé à 0x5000400 +0x48

*(UINT64*)0x5000448 = para_PipeAttributeobjInkernel + 0x18;

Capture d'écran d'un ordinateur Description générée automatiquement

Le pointeur PipeAttribute dans le noyau qui pointe vers un tampon rempli de « A » sera écrasé avec la partie haute du pointeur SYSTEM EPROCESS.

Ce pointeur a été créé lorsque j'ai appelé précédemment _NtFsControlFile avec un tampon plein de « A ».

Capture d'écran d'un programme informatique Description générée automatiquement avec une confiance moyenne

Le contenu de cet attribut peut être lu en utilisant NtFsControlFile.

Capture d'écran d'un écran d'ordinateur Description générée automatiquement avec une confiance moyenne

Maintenant, l'attribut du pipe ne pointe plus vers le tampon avec « A » mais vers system_EPROCESS & 0xffffffffffffff000.

Capture d'écran d'un programme informatique Description générée automatiquement avec une faible confiance

Ce code sera répété jusqu'à ce que le jeton système soit récupéré.

Capture d'écran d'un code informatique Description générée automatiquement avec une confiance moyenne

Capture d'écran d'un ordinateur Description générée automatiquement

Sur Windows 11, le jeton système se trouve à l'offset 0x4b8 de la structure EPROCESS récemment lue.

Capture d'écran d'un ordinateur Description générée automatiquement

J'ai seulement besoin d'écrire ce jeton système dans mon processus en appelant CreateLogFile.

Une image contenant du texte, police, ligne, capture d'écran Description générée automatiquement

Pour effectuer ce travail, il suffit de répéter l'étape utilisée pour lire le jeton système.

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Dans le double appel, il appelle d'abord ClfsEarlierLsn pour retourner 0xFFFFFFFF dans RDX puis appelle nt_SeSetAccessStateGenericMapping.

Une image contenant du texte, capture d'écran, police, ligne Description générée automatiquement

Je vérifie que la valeur pointée par RDX est le jeton système.

Une image contenant du texte, capture d'écran, police Description générée automatiquement

Le jeton de mon processus est :

Il va écrire là.

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'f601c06c

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'ef841919

Maintenant mon processus est System je peux exécuter un Notepad pour vérifier.

Une image contenant du texte, capture d'écran, police, ligne Description générée automatiquement

Capture d'écran d'un ordinateur Description générée automatiquement

Capture d'écran d'un ordinateur Description générée automatiquement

7-Le vrai correctif

BINDIFF montre beaucoup de fonctions modifiées

Capture d'écran d'un ordinateur Description générée automatiquement

La fonction vulnérable est ici :

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Capture d'écran d'un ordinateur Description générée automatiquement

Le primaire est la version patchée, le secondaire est la version vulnérable.

Le correctif teste la valeur de retour de CflsEncodeBlock, qui est 0xC01A000A, la stocke dans la variable var_54, et comme elle est négative, la vérifie et évite le WriteSector.

Le correctif, en plus de ne pas écrire le fichier, la fonction retourne correctement 0xc01a000a, avec lequel CreateLogFile ne retourne aucun handle et l'exploitation ne peut pas continuer.

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenne

Capture d'écran d'un ordinateur Description générée automatiquement

Seulement si ClfsDecodeBlock n'est pas négatif, il va à WriteSector mais laisse retourner la valeur négative 0xC01A000A.

Capture d'écran d'un ordinateur Description générée automatiquement avec une confiance moyenneC'est le correctif réel qui empêche véritablement l'exploitation en utilisant le PoC que je viens de joindre.

À ce stade, nous avons expliqué comment le bug a été exploité, cela conduit à contrôler les fonctions qui nous permettent de lire le jeton SYSTEM et de l'écrire dans notre propre processus pour réaliser l'élévation de privilèges locale. Vous pouvez trouver le PoC fonctionnel sur le GitHub de Fortra.

Nous espérons que cela vous sera utile, si vous avez des questions, vous pouvez nous contacter :

[email protected] 
@ricnar456

 [email protected]
@solidclt

Télécharger l’outil