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-2022-37969 — Exploit de preuve de concept pour CVE-2022-37969, une élévation de privilèges locale du pilote Windows Common Log File System. Démontre le heap spray, le vol de jeton et l'écriture arbitraire dans le noyau pour obtenir les privilèges SYSTEM. | Kitploit
Outils/GitHubGitHub/fortra/cve-2022-37969
Escalade de PrivilègesCriminalistique MémoireAnalyse des VulnérabilitésExploitationExploitation de Binaires
GitHubfortra/cve-2022-37969

CVE-2022-37969

Exploit de preuve de concept pour CVE-2022-37969, une élévation de privilèges locale du pilote Windows Common Log File System. Démontre le heap spray, le vol de jeton et l'écriture arbitraire dans le noyau pour obtenir les privilèges SYSTEM.

Voir le dépôt
135381il 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

CVE-2022-37969 Windows Local Privilege Escalation PoC

auteurs : Ricardo Narvaja & Daniel Kazimirow (Solid)

À des fins de démonstration uniquement. L'exploit complet fonctionne sur les systèmes vulnérables Windows 11 21H2.

PoC fonctionnel basé sur les informations précédemment publiées par Zscaler

Consultez l'article Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation.

Utilisation

Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation.

Présentation de l'exploitation :

  • Création du fichier journal BLF initial
    • Création de plusieurs fichiers journaux BLF aléatoires
    • Façonnage du fichier journal initial
    • Exécution d'un Heap Spray contrôlé
    • Préparation des méthodes CreatePipe() / NtFsControlFile()
    • Une fois la mémoire préparée, déclenchement de la vulnérabilité
    • Lecture du jeton système
    • Validation du jeton
    • Remplacement du jeton de notre processus par celui du système
    • Exécution d'un processus en tant que système
    • Rétro-ingénierie du correctif : Analyse des structures
    • Corruption du pointeur "pContainer"
    • Réexamen du correctif
    • Corruption du SignatureOffset
    • Corruption d'autres valeurs
    • Contrôle des fonctions permettant de lire le jeton SYSTEM
    • Écriture dans notre propre processus pour réaliser l'élévation de privilèges locale
    • Code source du PoC

Le scénario utilisé ici était Windows 11 21H2 (OS Build 22000.918) clfs.sys v10.0.22000.918

Création du fichier journal BLF initial

La première étape consiste à créer un fichier nommé MyLog.blf dans le dossier public (%public%), en utilisant la fonction CreateLogFile() :

Création de plusieurs fichiers journaux BLF aléatoires

Ensuite, il crée plusieurs fichiers journaux avec des noms aléatoires en utilisant une boucle.

Et dans la boucle, il appelle notre fonction getBigPoolInfo() :

Elle appelle NtQuerySystemInformation(), avec 0x42 (66 décimal) comme premier argument, cela retournera dans v5 les informations sur les allocations faites dans le bigpool, dont la structure est de type SYSTEM_BIGPOOL_INFORMATION.

Nous devons appeler cette fonction deux fois. La première retournera une erreur, mais nous donnera la taille correcte du tampon pour appeler la seconde fois et obtenir les informations souhaitées.

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

v5 recevra les informations de la structure SYSTEM_BIG_POOL_INFORMATION.

Le nombre d'allocations dans le bigpool est stocké dans le premier champ appelé Count, dans le second champ se trouve un tableau de structures SYSTEM_BIGPOOL_ENTRY.

Ensuite, nous recherchons dans toutes les structures la balise "Clfs" et la taille 0x7a00.

Il stocke dans un tableau appelé kernelAddrArray l'adresse virtuelle qui est le premier champ de chaque structure ayant la balise CLFS et la taille 0x7a00. Désormais, les pools qui remplissent ces deux conditions seront appelés : "pools corrects".

En plus de stocker chaque pool correct dans le tableau, il stocke le dernier pool correct trouvé dans le contenu de la variable a2, qui est utilisée comme argument de la fonction.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Ainsi, a2 pointe toujours vers le dernier pool correct créé avec la balise CLFS et la taille 0x7a00.

La variable v26 stocke toujours le pool correct précédent trouvé car elle est égale à v24 (v26=v24), avant d'appeler getBigPoolinfo(), mais v24 est mise à jour en sortant de cet appel avec le dernier pool correct trouvé, et v26 reste avec le pool correct précédent trouvé.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Ensuite, il soustrait les deux directions, et si le résultat est négatif, il inverse les opérandes pour qu'il soit toujours positif.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Ainsi, dans v32 sera stockée la différence entre l'adresse virtuelle des deux derniers pools corrects trouvés.

Ensuite, il fait quelque chose de similaire. Dans ce cas, v23 est initialement nul, donc il fait v23 = v32 la première fois.

La prochaine fois dans la boucle, v23 a toujours la même valeur et n'est pas nul, donc il s'arrête et va ici.

V32 a la dernière différence et v23 la précédente, si elles sont égales, il sort et incrémente de un, mais remet le compteur à zéro.

L'idée est de trouver 6 comparaisons consécutives de balises CLFS et de taille 0x7a00 dont les différences sont égales, et cette différence sera 0x11000. Nous verrons lors de l'exécution que lorsqu'il trouve 6 (car il repart de zéro) consécutives avec des distances égales, il donnera cette valeur de différence entre elles.

Texto Descripción generada automáticamente

Là, nous voyons qu'il a trouvé 6 consécutives et a quitté la boucle de création de fichiers journaux.

Dans le dossier "public", nous pouvons voir les fichiers créés

Façonnage du fichier journal initial :

Notre fonction craftFile() ouvre le fichier original (MyLog.blf) et le modifie pour déclencher le bogue.

Après avoir modifié le fichier, il est nécessaire de changer le CRC32, sinon nous obtiendrons une erreur de fichier corrompu.

Cette valeur se trouve à l'offset 0x80C du fichier.

Exécution d'un Heap Spray contrôlé

Ensuite, il effectue un Heap Spray, en utilisant la fonction VirtualAlloc() pour allouer de la mémoire, à des adresses arbitraires 0x10000 et 0x5000000 respectivement, et en sauvegardant dans la seconde allocation (0x10000), la valeur 0x5000000, tous les 0x10 octets.

Préparation des méthodes CreatePipe() / NtFsControlFile()

Il utilise CreatePipe() pour créer un tube anonyme et appelle NtFsControlFile() avec 0x11003c comme argument pour ajouter un attribut, plus tard on peut appeler cette même fonction avec l'argument 0x110038 pour le lire.

Plus de détails sur cette méthode peuvent être trouvés ICI

Là, nous voyons le tampon d'entrée qui est l'attribut que nous ajoutons. Si nous appelons à nouveau NtFsControlFile() avec l'argument 0x11038, en sortie il devrait retourner ce même attribut.

Recherche dans le pool la balise de l'attribut créé (NpAt)

Et quand il la trouve, il sauvegarde dans v30.Pointer l'adresse virtuelle de ce pool.

V30.pointer+24 pointe vers AttributeValueSize dans le pool noyau et le sauvegarde dans l'un des HeapSprays que nous avons effectués précédemment.

L'idée est d'écrire à cette adresse noyau+8, pour écraser le AttributeValue.

Texto Descripción generada automáticamente

La structure PipeAttribute a comme premier champ un LIST_ENTRY d'une taille de 16 octets, puis un pointeur vers le nom de l'attribut de taille 8 octets, puis vient à 0x18 (24 décimal) le champ AttributeValueSize qui est celui que nous stockons dans le HeapSpray.

Après cela, nous chargeons CLFS.sys et ntoskrnl en mode utilisateur, et en utilisant GetProcAddress() nous trouvons les adresses des fonctions ClfsEarlierLsn() et SeSetAccessStateGenericMapping().

Ensuite, nous appelons la fonction FindKernelModulesBase() qui trouvera la base noyau des deux mêmes modules en utilisant NtquerySystemInformation() cette fois avec l'argument SystemModuleInformation pour retourner les informations sur tous les modules.

De cette façon, nous pouvons calculer l'offset de chaque fonction, puis les obtenir dans le noyau

Une fois la mémoire préparée, déclenchement de la vulnérabilité :

La fonction pipeArbitraryWrite() est appelée deux fois. Il y a un drapeau qui est initialement nul pour le premier appel et lorsque dans le second appel il vaut 1, il changera les valeurs du HeapSpray.

Texto Descripción generada automáticamente

Dans le premier appel à l'adresse mémoire 0x5000000, les valeurs suivantes sont situées

Rappelez-vous que cette valeur, en plus d'être allouée à cette adresse, est stockée dans notre HeapSpray.

Voici à quoi ressemble la mémoire après le premier appel, comme nous l'avons dit à l'adresse autour de 0x5000000

Et dans le HeapSpray à partir de la mémoire 0x10000, il stockera le pointeur vers AttributeValueSize tous les 0x10 octets, en plus du pointeur vers 0x5000000.

Lecture du jeton système :

Cette séquence déclenchera le bogue :

CreateLogFile() est appelée à nouveau sur le fichier modifié et sur un autre avec un nom aléatoire.

AddLogContainer() est ensuite appelée en utilisant les handles de ces fichiers.

NtSetinformationFile() est appelée, et les handles sont fermés avec lesquels le pointeur est corrompu (cela sera expliqué plus tard)

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente con confianza media

Le HeapSpray empêche un BSOD de se produire à ce stade :

En plaçant un point d'arrêt ici, nous pouvons voir que le pointeur est corrompu et pointe vers notre HeapSpray, avec lequel nous pouvons contrôler les deux prochains appels de fonction de la vtable.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

RAX prend la valeur 0x5000000 et saute d'abord vers la fonction située à 0x5000000+18 puis vers 0x5000000+8.

Texto Descripción generada automáticamente con confianza media

Imagen que contiene Interfaz de usuario gráfica Descripción generada automáticamente

Donc, il saute d'abord vers fnClfsEarlierLsn() puis vers fnSeSetAccessStateGenericMapping().

Nous traçons à partir du point d'arrêt et voyons qu'il atteint CLFS!ClfsEarlierLsn().

Texto Descripción generada automáticamente

Cette fonction est appelée exclusivement car lorsqu'elle retourne, elle met EDX à 0xFFFFFFFF

Interfaz de usuario gráfica Descripción generada automáticamente con confianza media

À l'adresse 0xFFFFFFFF, nous avions stocké le résultat de SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Comme mentionné, lors du retour de CLFS!ClfsEarlierLsn(), la valeur de RDX est 0x00000000FFFFFFFF

Texto Descripción generada automáticamente

Nous arrivons à la seconde fonction nt!SeSetAccessStateGenericMapping()

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

Cette fonction est utile, car RCX pointe vers notre HeapSpray, et RDX vaut 0xFFFFFFFF, dont nous contrôlons le contenu

Interfaz de usuario gráfica, Aplicación, Teams Descripción generada automáticamente

Texto Descripción generada automáticamente

Le contenu de RCX+0x48 contient le pointeur vers AttributeValueSize qui a été stocké dans v30.Pointer+24

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Texto Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Cette valeur de pointeur de AttributeValueSize est déplacée dans RAX, puis lit le contenu de l'adresse 0xFFFFFFFF où nous avions stocké l'adresse de SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Ensuite, il écrase dans RAX+8 le champ suivant qui est AttributeValue()

Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

Bien sûr, le AttributeValue pointerait normalement dans le noyau vers l'attribut que nous avons ajouté.

Calendario Descripción generada automáticamente

Et maintenant, nous l'écrasons avec un pointeur du résultat du système _EPROCESS & 0xFFFFFFFFFFFFFFF00.

Cela signifiera que lorsque nous appellerons à nouveau la fonction NtFsControlFile(), cette fois avec l'argument 0x110038 pour lire l'attribut, au lieu de retourner le "A" pointé par le pointeur AttributeValue, il lira maintenant à partir de _EPRROCESS & 0xFFFFFFFFFFFFFFFFF000 le nombre d'octets demandé et le retournera dans le tampon de sortie, ce qui nous permettra d'obtenir dans le premier appel la valeur du TOKEN SYSTEME.

Texto Descripción generada automáticamente

v9b est l'adresse de début du Tampon de sortie où le contenu du résultat de System EPROCESS & 0xFFFFFFFFFFFFFFF000 a été copié.

À cela, il ajoute v14 qui sont les 3 derniers octets du EPROCESS système, puis ajoute 0x4b8 qui est l'offset du Token pour cette version de Windows 11, puis trouve le contenu de cette adresse qui contiendra la valeur du Token Système.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

Validation du jeton

Texto Descripción generada automáticamente con confianza baja

Rappelez-vous que les 4 derniers bits ont été modifiés, ce n'est pas significatif, donc la valeur correspond toujours.

Remplacement du jeton de notre processus par celui du système

Dans le second appel, la valeur du drapeau est 1 car elle a été incrémentée à la fin du premier appel.

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

Là, nous voyons l'ordre dans lequel les valeurs sont stockées

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

L'adresse 0xFFFFFFFF avec la valeur que nous venons de trouver du Jeton du processus Système.

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

Et dans le HeapSpray se trouve la valeur de l'adresse du jeton de mon processus à laquelle je soustrais 8. Cette valeur plus huit sera utilisée comme cible. Rappelez-vous que nous écrivions à l'adresse pointée par RAX+8.

Graphical user interface, text Description automatically generated

Texto Descripción generada automáticamente

Dans l'adresse mémoire commençant à 0x5000000

Interfaz de usuario gráfica Descripción generada automáticamente

Nous voyons aussi qu'il utilise le nom d'un autre conteneur, car le précédent est utilisé par le processus système et ne peut pas être rouvert ou supprimé.

Ensuite, le bogue est déclenché une seconde fois de la même manière que la première.

Il arrive à nouveau à CLFS!ClfsEarlierLsn().

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

mettant RDX à 0xFFFFFFFF

Una captura de pantalla de un celular Descripción generada automáticamente

Ensuite, il arrive à nt!SeSetAccessStateGenericMapping()

Texto Descripción generada automáticamente

Lit l'adresse du jeton de mon processus moins 8 où il va écrire

Texto Descripción generada automáticamente

Ensuite, il lit le TOKEN SYSTEME

Texto Descripción generada automáticamente

Et écrit à l'adresse du jeton de mon processus (il ajoute 8), le Token Système

Et ainsi mon processus a le jeton système

Texto Descripción generada automáticamente

Une fois le jeton écrit, nous démarrons un processus pour vérifier les privilèges, dans ce cas, nous lançons Notepad.exe

Exécution d'un processus en tant que système :

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Aplicación, Word Descripción generada automáticamente

Texto Descripción generada automáticamente con confianza baja

Rappelez-vous que ce PoC ne fonctionne que sous Windows 11, sous Windows 10 il produira un BSOD, donc vous devez apporter quelques modifications pour qu'il fonctionne correctement, ce qui n'est pas expliqué dans cet article de blog.

Rétro-ingénierie du correctif :

Analyse des structures

Les structures et la plupart de la documentation sur le format de fichier CLFS ont été tirées de l'excellent travail d'IONESCU sur CLFS Internals.

Nous pouvons voir qu'une vérification a été ajoutée dans la fonction ClfsBaseFilePersisted::LoadContainerQ

Texto Descripción generada automáticamente con confianza media

Les valeurs qui effectuent une addition appartiennent à la structure _CLFS_BASE_RECORD_HEADER.

Escala de tiempo Descripción generada automáticamente con confianza media

Notez que le Bloc de base commence à l'offset 0x800 du fichier et se termine à l'offset 0x71FF, les 0x70 premiers octets correspondant à l'En-tête du bloc journal

En règle générale, nous pouvons ajouter la structure _CLF_LOG_BLOCK_HEADER dans IDA

struct _CLFS_LOG_BLOCK_HEADER

{

UCHAR MajorVersion;

UCHAR MinorVersion;

UCHAR Usn;

char ClientId;

USHORT TotalSectorCount;

USHORT ValidSectorCount;

ULONG Padding;

ULONG Checksum;

ULONG Flags;

CLFS_LSN CurrentLsn;

CLFS_LSN NextLsn;

ULONG RecordOffsets[16];

ULONG SignaturesOffset;

};

Ensuite, nous avons l'en-tête d'enregistrement de base (_CLFS_BASE_RECORD_HEADER) qui commence au décalage 0x870 à partir du début du fichier et fait 0x1338 octets de long.

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

Si vous souhaitez l'importer dans IDA, vous devez d'abord ajouter les types et structures manquants suivants

typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;

struct _CLFS_METADATA_RECORD_HEADER

{

ULONGLONG ullDumpCount;

};

Il est maintenant prêt à être ajouté :

typedef struct _CLFS_BASE_RECORD_HEADER

{

CLFS_METADATA_RECORD_HEADER hdrBaseRecord;

CLFS_LOG_ID cidLog;

ULONGLONG rgClientSymTbl[0x0b];

ULONGLONG rgContainerSymTbl[0x0b];

ULONGLONG rgSecuritySymTbl[0x0b];

ULONG cNextContainer;

CLFS_CLIENT_ID cNextClient;

ULONG cFreeContainers;

ULONG cActiveContainers;

ULONG cbFreeContainers;

ULONG cbBusyContainers;

ULONG rgClients[0x7c];

ULONG rgContainers[0x400];

ULONG cbSymbolZone;

ULONG cbSector;

USHORT bUnused;

CLFS_LOG_STATE eLogState;

UCHAR cUsn;

UCHAR cClients;

} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Après avoir inclus les structures, nous remarquons qu'il effectue une addition entre le cbSymbolZone et l'adresse où se termine le _CLFS_BASE_RECORD_HEADER. (début + 1338h)Texto, Aplicación Descripción generada automáticamente

Rappelez-vous que le cbSymbolZone a été modifié dans le fichier journal fabriqué de 0x000000F8 à 0x0001114B.

(décalage 0x1b98 du fichier)

0x800(décalage du début du bloc de base) + 0x70 (logBlockHeader) + 0x1328 (cbsymbolZone)

0x800+0x70+0x1328 = 0x1b98

cbsymbolZone fabriqué sur le fichier MyLog.blf :

Tabla Descripción generada automáticamente

Imagen que contiene Texto Descripción generada automáticamente

Comme le correctif se trouve dans la fonction CClfsBaseFilePersisted::LoadContainerQ, nous devons jeter un œil à l'objet CClfsBaseFilePersisted.

En plaçant un point d'arrêt dans CLFS!CClfsBaseFilePersisted::LoadContainerQ et lorsque CreateLogFile est appelée avec le handle du fichier fabriqué, il s'arrêtera.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Appelez la fonction CClfsBaseFile::GetBaseLogRecord pour obtenir l'adresse de l'enregistrement de base du journal (_CLFS_BASE_RECORD_HEADER)

Graphical user interface, application, timeline Description automatically generated

RAX pointera vers l'adresse de _CLFS_BASE_RECORD_HEADER

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

Notez la structure _CLFS_BASE_RECORD_HEADER en mémoire et le champ cbsymbolZone 0x1328

octets en avant

Imagen que contiene Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

r14 stocke la structure correspondant au « this », qui est CClfsBaseFilePersisted puisqu'il s'agit du this de la fonction CClfsBaseFilePersisted::LoadContainerQ.

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

La structure CClfsBaseFilePersisted en mémoire :

Texto Descripción generada automáticamente

Créons donc une structure de longueur 0x21c0 pour compléter ses champs pendant que nous la rétro-ingénierons (c'est une structure non documentée), nous l'appellerons struct_CClfsBaseFilePersisted

Tabla Descripción generada automáticamente con confianza media

À l'intérieur de la fonction CClfsBaseFile::GetBaseLogRecord(), on obtient le pointeur vers _CLFS_BASE_RECORD_HEADER. et nous savons que le « this » dans cette fonction est la structure : struct_CClfsBaseFilePersisted.

Escala de tiempo Descripción generada automáticamente con confianza media

Lire deux champs (décalage 0x28 et 0x30)

Interfaz de usuario gráfica, Aplicación, Tabla Descripción generada automáticamente

Le champ 0x28 est un mot et a la valeur 6, donc nous changeons le type en mot dans la structure.

Texto Descripción generada automáticamente

Texto Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Pour l'instant, nous le renommons en constante 6 (const_6)

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Selon la documentation, 6 serait le nombre de blocs CLFS_METADATA_BLOCK_COUNT. Le champ pourrait faire référence à cette valeur.

Et ce pointeur est au décalage 0x30.

Tabla Descripción generada automáticamente

Notez que la taille affichée inclut l'en-tête d'une longueur de 0x10

Texto Descripción generada automáticamente

Forma Descripción generada automáticamente con confianza media

Lorsque la fonction ExAllocatePoolWithTag est appelée, quelques octets sont demandés, mais l'en-tête n'est pas inclus, donc 0x90 octets (0xa0 – 0x10) seront demandés dans l'appel.

En recherchant par texte +30h], les instructions qui écrivent au décalage 0x30, nous avons trouvé une longue liste, mais en filtrant la liste par le type d'objet CClfsBaseFilePersisted, il ne nous reste que quelques résultats et nous trouvons immédiatement où cette taille est allouée, et la même balise. (Astuce : les noms de fonctions Create et Initialize sont toujours les premiers à regarder)

Texto Descripción generada automáticamente con confianza baja

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Comme nous ne connaissons pas encore le nom, nous le mettrons pool_0x90, qui est une autre structure non documentée, et nous créerons une structure de cette taille.

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Tabla Descripción generada automáticamente

Le pool_0x90 en mémoire a un autre pointeur à son propre décalage 0x30.

Aplicación, Tabla Descripción generada automáticamente con confianza media

Cet autre pointeur pointe vers le bloc de base dans le fichier (le bloc de base commence au décalage 0x800)

Forma Descripción generada automáticamente

A picture containing calendar Description automatically generated

Image tirée du blogpost de Zscaler:

Graphical user interface, application, email Description automatically generated

L'allocation est énorme, car elle contient tout le bloc de base.

Text Description automatically generated

Graphical user interface, text, application Description automatically generated

Nous allons donc créer une nouvelle structure de taille 0x7a00 et l'appeler BASE_BLOCK

Graphical user interface, text, application Description automatically generated

Les 70 premiers octets correspondent déjà à _CLFS_LOG_BLOCK_HEADER et les 0x1338 suivants à _CLFS_BASE_RECORD_HEADER.

Text Description automatically generated

Ainsi, en ajoutant le début du bloc de base avec le décalage vers l'enregistrement suivant (qui est 0x70), nous obtenons le _CLFS_BASE_RECORD_HEADER

Application Description automatically generated with low confidence

Le _CLFS_BASE_RECORD_HEADER en mémoire.

Calendar Description automatically generated

En regardant d'autres méthodes du même objet CClfsBaseFilePersisted, dans CClfsBaseFilePersisted::AddContainer, on obtient également l'adresse de _CLFS_BASE_RECORD_HEADER avec CClfsBaseFile::GetBaseLogRecord.

Text Description automatically generated

Ensuite, appelez CClfsBaseFile::OffsetToAddr en utilisant cbOffset, il obtient l'adresse de _CLFS_CONTAINER_CONTEXT, et stocke cboffset dans le tableau rgbcontainers qui se trouve au décalage 0x328 de _CLFS_BASE_RECORD_HEADER.

Graphical user interface, application Description automatically generated

La fonction CClfsBaseFile::OffsetToAddr est utilisée pour trouver les adresses des structures à partir du décalage

Graphical user interface, text, application Description automatically generated

À ce stade, le décalage du conteneur qui sera stocké à 0x328 est toujours 0, car nous n'avons pas encore ajouté de conteneur.

Background pattern Description automatically generated with low confidence

Le PoC appelle CreateLogFile deux fois, la première fois avec le fichier malformé MyLog.blf et la seconde fois avec le fichier normal MyLogxxx.blf, nous devons donc arrêter le débogage deux fois à tous les endroits ci-dessus et noter dans le bloc-notes les adresses des structures ci-dessus pour les deux fichiers.

Text Description automatically generated

Avançons un peu jusqu'à CLFS!CClfsLogFcbPhysical::AllocContainer en y plaçant un point d'arrêt et en y allant.

Lorsque AddLogContainer() est atteint dans le POC, nous nous arrêtons au point d'arrêt.

A picture containing application Description automatically generated

Définissons également un point d'arrêt sur CClfsBaseFilePersisted::AddContainer+176 où nous avons vu plus tôt qu'il trouvera le décalage et le pointeur vers la structure _CLFS_CONTAINER_CONTEXT.

A screenshot of a computer Description automatically generated with medium confidence

Graphical user interface, text, application Description automatically generated

Lorsque le débogueur s'arrête, nous pouvons voir que le décalage est 0x1468.

Text Description automatically generated

Dans RAX sera retournée l'adresse de la structure _CLFS_CONTAINER_CONTEXT.

Graphical user interface, text, application, table Description automatically generated

la structure est encore vide car le conteneur n'a pas encore été ajouté.

Text Description automatically generated

Notez que la valeur SignatureOffset=0x50 que nous avons écrite au décalage 0x868 du fichier malformé, en soustrayant 0x800 du début du bloc de base, se trouvera dans la structure _CLFS_LOG_BLOCK_HEADER au décalage 0x68.

Graphical user interface, text, application Description automatically generated

Text, letter Description automatically generated

Lorsque le PoC appelle la fonction AddLogContainer() en utilisant le fichier malformé, au décalage 0x68 de _CLFS_LOG_BLOCK_HEADER, au lieu de la valeur 0x50 que nous y avons écrite, on trouve actuellement un 0xFFFF0050 en mémoire.

A picture containing text Description automatically generated

À un moment donné, cette valeur a été modifiée par le programme ; pour voir quand cela s'est produit, lors de la prochaine exécution, nous définirons un point d'arrêt mémoire en écriture.

Le décalage est stocké à r15 + 0x328 (r15 pointe vers la structure _CLFS_BASE_RECORD_HEADER)

Text Description automatically generated with medium confidence

Graphical user interface Description automatically generated with low confidence

RBX stocke le décalage 0x1468.

A picture containing calendar Description automatically generated

Donc, dans l'adresse du bloc de base + 0x70 + le décalage 0x1468 que nous avons trouvé, se trouvera l'adresse du conteneur CLFS_CONTAINER_CONTEXT.

Text Description automatically generated

Dans la structure CLFS_CONTAINER_CONTEXT au décalage 0x18 se trouvera le pointeur pContainer qui y sera stocké, nous pouvons définir un point d'arrêt en écriture et voir quand il est écrit.

Text Description automatically generated

A picture containing text Description automatically generated

C'est le pointeur que nous devons corrompre car dans la fonction où se trouve la vulnérabilité, elle lit d'abord le CLFS_CONTAINER_CONTEXT, puis le déplace dans r15 et lit ensuite la valeur de r15+18, qui est ce pointeur sur lequel nous venons de définir le point d'arrêt en écriture.

Graphical user interface, application, table Description automatically generated

Graphical user interface, application, Word Description automatically generated

il stocke le pContainer au décalage 0x1c0 de la structure struct_CClfsBaseFilePersisted.

Text, application, whiteboard Description automatically generated

Après plusieurs arrêts, nous atteignons le moment où il est corrompu. Le haut de l'adresse du pointeur est passé de FFs à zéro.

Calendar Description automatically generated

Cela se produit lorsque le second AddLogContainer() du fichier malformé est appelé, le pointeur du MyLogxxx précédent est corrompu.

Le problème vient du fait que SignaturesOffset, qui devrait être 0x50, est maintenant 0xFFFF0050, ce qui permet d'écrire hors limites dans le memset qui suit.

Graphical user interface, text, application Description automatically generated

Graphical user interface, text Description automatically generated

Corrompre le pointeur « pContainer » :

La fonction memset() va corrompre la structure _CLFS_CONTAINER_CONTEXT qui se trouve en dessous, cette structure correspond au fichier MyLogxxx, car lors de la création, ils étaient situés à 0x11000 octets l'un de l'autre.

De cette façon, il calcule exactement où écrire sur la structure suivante et met à zéro le haut du pointeur, afin qu'il pointe vers le tas utilisateur où le HeapSspray a été créé.

la structure du bloc de base du fichier malformé est juste 0x11000 avant celle du fichier MyLogxxx.

Malformé :

Shape Description automatically generated

MyLogxxx

A picture containing text Description automatically generated

RCX est plus petit que RDX car 0xFFFF0050 a été ajouté, au lieu de 0x50 comme il se doit.

Graphical user interface, text, application Description automatically generated

et nous sommes arrivés à la fonction memset(), pour mettre la quantité de 0xb0 octets à zéro, avec RCX pointant vers la structure CLFS_CONTAINER_CONTEXT du fichier MyLogxxx, plus précisément vers les cinq octets de poids fort de pContainer.

Text Description automatically generated

Ce pointeur sera corrompu en écrasant les premiers octets :

Text Description automatically generated

restant pointant vers une adresse mémoire préalablement contrôlée par nous via HeapSpray

Text Description automatically generated

A picture containing text Description automatically generated

Ensuite, le handle du fichier MyLogxxx sera fermé, et on atteint CClfsBaseFilePersisted::RemoveContainer, la vulnérabilité est finalement déclenchée.

Text, application Description automatically generated with medium confidence

Réexaminer le correctif

Maintenant que nous avons plus d'informations, nous remarquons qu'il lit ici le Base_Block.LOG_BLOCK_HEADER.SignaturesOffset et le Base_Block. .LOG_BLOCK_HEADER.TotalSectorCount

Dans la première partie du correctif, SignaturesOffset ne doit pas être supérieur à 0x7a00, dans le nôtre il était initialement à 0x50, s'il arrivait avec une valeur supérieure à 0x7a00, cela nous éjecterait.

A picture containing diagram Description automatically generated

En exécutant le PoC sur la machine corrigée, il compare 0x50 avec 0x7a00 et comme il est plus petit, il continue.

Text Description automatically generated

Dans le bloc suivant, le cbSymbolZone malformé est ajouté à la valeur de l'adresse finale de _CLFS_BASE_RECORD_HEADER et cette somme est stockée dans result_1.

Text Description automatically generated

Ensuite, l'adresse du bloc de base est ajoutée à la valeur SignatureOffset, qui dans un fichier normal est 0x7980.

Text Description automatically generated

L'adresse maximale du base_block est 0x7a00, maintenant la SymbolZone est autorisée jusqu'à 0x80 avant la limite.

Il la stockera dans result_2, c'est-à-dire que ce serait la limite maximale pour la SymbolZone à l'intérieur du bloc de base, puis il compare les deux résultats si le premier est supérieur au second, cela signifie qu'il est sorti des limites.

Text Description automatically generated

Text Description automatically generated

Évidemment, le premier membre sera plus grand que le second et il ne continuera pas, puisque la première somme de cbSymbolZone + adresse finale de _CLFS_BASE_RECORD_HEADER dépasse la limite (qui est result_2) et conduit à un « out of bounds ».

Graphical user interface, text, application Description automatically generated

Corrompre le SignatureOffset

La dernière chose que nous devrions déterminer est l'endroit où la valeur SignatureOffset de 0x50 devient 0xFFFF0050.Alors, recommençons, redémarrons et arrêtons-nous à CLFS!CClfsBaseFilePersisted::LoadContainerQ là où la valeur n'a pas encore été modifiée en mémoire et est toujours 0x50.

Définissez un point d'arrêt d'accès à l'offset 0x68 dans SignatureOffset.

Description de calendrier générée automatiquement

Et après plusieurs arrêts, nous détectons le moment précis où il modifie la valeur, dans ClfsEncodeBlockPrivate.

Interface utilisateur graphique, description de texte générée automatiquement

Cette fonction n'est pas patchée, donc il pourrait s'agir d'un comportement causé par la faible valeur de 0x50 et le reste des valeurs étant manipulées.

Parmi les valeurs fabriquées, nous pouvons voir la valeur ccoffsetArray dont le nom dans la structure _CLFS_BASE_RECORD_HEADER est rgClients et représente le tableau d'offsets qui pointent vers l'objet Client Context.

Le champ rgClients est situé à l'offset 0x138 (0x9a8-0x800-0x70) de la structure _CLFS_BASE_RECORD_HEADER.

Interface utilisateur graphique, description de tableau générée automatiquement

Description de texte générée automatiquement avec une confiance moyenne

Dans le PoC, cette valeur est malformée pour pointer vers un faux objet client context, appelé FakeClientContextTexte, description de tableau blanc générée automatiquement

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

Ceci est la structure Client Context _CLFS_CLIENT_CONTEXT

struct _CLFS_CLIENT_CONTEXT

{

CLFS_NODE_ID cidNode;

CLFS_CLIENT_ID cidClient;

USHORT fAttributes;

ULONG cbFlushThreshold;

ULONG cShadowSectors;

ULONGLONG cbUndoCommitment;

LARGE_INTEGER llCreateTime;

LARGE_INTEGER llAccessTime;

LARGE_INTEGER llWriteTime;

CLFS_LSN lsnOwnerPage;

CLFS_LSN lsnArchiveTail;

CLFS_LSN lsnBase;

CLFS_LSN lsnLast;

CLFS_LSN lsnRestart;

CLFS_LSN lsnPhysicalBase;

CLFS_LSN lsnUnused1;

CLFS_LSN lsnUnused2;

CLFS_LOG_STATE eState;

union

{

HANDLE hSecurityContext;

ULONGLONG ullAlignment;

};

};

La valeur eState se trouve à l'offset 0x78 du début de la structure, dans le fichier fabriqué 0x23a0+0x78.

Description de texte générée automatiquement avec une confiance moyenne

Graphique, description de nuage de points générée automatiquement

Cette valeur montre l'état du journal.

typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;
const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20;
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;

cette valeur est définie à CLFS_LOG_STATE CLFS_LOG_SHUTDOWN =0x20

L'autre valeur malformée est fAttributes qui correspond à l'ensemble des indicateurs FILE_ATTRIBUTE associés au fichier journal de base (tels que System et Hidden).

Interface utilisateur graphique générée automatiquement avec une faible confiance

Texte, description de lettre générée automatiquement

Puisque le champ commence un octet plus tôt à 0xa et s'étend sur deux octets, la valeur de fAttributes est 0x100.

Une image contenant tableau générée automatiquement

Interface utilisateur graphique, texte, application générée automatiquement

Enfin, il y a la valeur blocknameoffset qui pointe vers l'offset 0x1bb8, je veux dire, en ajoutant 0x78 et 0x800 on pointe vers l'offset 0x2428 du fichier.

Texte généré automatiquement

Texte, description de lettre générée automatiquement

Notez que l'offset vers le Client Context est 0x1b30

Tableau généré automatiquement

Donc, le Client Context est à l'offset 0x23a0.

Texte généré automatiquement avec une confiance moyenne

Tableau généré automatiquement

Et juste 0x10 avant, se trouve la valeur correspondant à blocknameoffset.

Texte, description de lettre générée automatiquement

Tableau généré automatiquement avec une faible confiance

Ce qui pointerait vers la chaîne avec le nom

le dernier est blockattributeoffset qui se trouve 0xC avant le Client Context à 0x2394.

Tableau généré automatiquement

Ces deux dernières valeurs appartiennent à une structure précédant le Client Context de 0x30 octets, appelée**_CLFSHASHSYM**

typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;

Une image contenant diagramme générée automatiquement

Texte généré automatiquement

ils sont à 0x20 et 0x24 octets du début de la structure _CLFSHASHSYM, donc dans la structure _CLFSHASHSYM la valeur appelée blockNameOffset dans le POC est le champ cbSymName et blockAttributteoffset est le champ cbOffset.

Une image contenant texte générée automatiquement

Texte généré automatiquement avec une confiance moyenne

Ce sont les valeurs malformées, nous devons maintenant voir comment elles affectent le changement de notre SignaturesOffset de la valeur 0x50 à 0xFFFF0050.

Jetons un coup d'œil à la fonction CClfsBaseFile::AcquireClientContext(), qui devrait retourner le client context.

Interface utilisateur graphique, texte, application, email générée automatiquement

elle appelle CClfsBaseFile::GetSymbol avec le quatrième argument qui sera _CLFS_CLIENT_CONTEXT ** où il stockera le pointeur vers Client Context.

Interface utilisateur graphique, texte, application générée automatiquement

À l'intérieur de la fonction CClfsBaseFile::GetSymbol, nous passons l'offset malformé ccoffsetArray à CClfsBaseFile::OffsetToAddr et obtenons l'adresse du client context, définissons un point d'arrêt là pour qu'il s'arrête lors de l'appel du fichier créé avec CreatelogFile.

Interface utilisateur graphique, application générée automatiquement avec une confiance moyenne

Là, il s'arrête avec l'argument fabriqué ccoffsetArray.

Interface utilisateur graphique, application générée automatiquement

Tableau généré automatiquement

La fonction CClfsBaseFile::OffsetToAddr retourne le faux Client Context

Une image contenant interface utilisateur graphique générée automatiquement

Et vérifie que la valeur de cbOffset n'est pas nulle puisque 0xC se trouve avant la structure _CLFS_CLIENT_CONTEXT qui est dans RAX.

Une capture d'écran d'ordinateur générée automatiquement

Une image contenant texte générée automatiquement

Ensuite, il compare cbOffset avec ccoffsetArray (qui est dans RSI), ils doivent être égaux, sinon nous obtiendrons une erreur.

Une capture d'écran d'ordinateur générée automatiquement

Il vérifie également que cbSymName soit égal à cbOffset+0x88, sinon nous obtiendrons aussi une erreur.

Interface utilisateur graphique, texte, application générée automatiquement avec une confiance moyenne

Et enfin, il compare l'octet cidClient avec zéro

Une image contenant diagramme générée automatiquement

Si toutes ces vérifications réussissent, le client context sera sauvegardé.

Une capture d'écran d'ordinateur générée automatiquement

La sortie de la fonction r14 pointe vers le Client Context

Interface utilisateur graphique, texte, application générée automatiquement

En sortant de CClfsLogFcbPhysical::Initialize, nous aurons l'adresse de CLFS_CLIENT_CONTEXT.

Texte généré automatiquement

Maintenant, il lit la valeur de fAttributes (0x100)

Interface utilisateur graphique, texte, application générée automatiquement

cette fonction appartient à la classe CClfsLogFcbPhysical

Texte généré automatiquement

Interface utilisateur graphique, texte, application générée automatiquement

Qui a été allouée ici, et sa taille est 0x15d0 et son tag est “ClfC”

Texte généré automatiquement avec une confiance moyenne

Créons une structure pour stocker ce que nous reversons, nous l'appellerons : struct_CClfsLogFcbPhysical.

Une image contenant tableau générée automatiquement

Notez qu'à 0x2b0, il sauvegarde l'adresse de la structure CClfsBaseFilePersisted.

Une image contenant application générée automatiquement

Après avoir sauvegardé de nombreuses valeurs dans la structure, il passe à une partie importante, il teste eState avec 0x20.

Interface utilisateur graphique, texte, application générée automatiquement

Interface utilisateur graphique, texte, application, tableau générée automatiquement

Puisque la valeur fabriquée était 0x20, le test retournera 1.

Tableau généré automatiquement

Interface utilisateur graphique, texte, application générée automatiquement

Nous voyons que dans le constructeur dans la vtable se trouve

Texte généré automatiquement

Il vérifiera si le fichier est multiplexé.

Interface utilisateur graphique, application générée automatiquement

Ainsi, il emprunte le chemin souhaité, atteignant CClfsLogFcbPhysical::ResetLog.

Interface utilisateur graphique, application générée automatiquement

Texte, application, tableau généré automatiquement avec une confiance moyenne

Plusieurs champs sont initialisés à zéro sauf un qui est initialisé à 0xFFFFFFFF00000000.

Interface utilisateur graphique générée automatiquement avec une faible confiance

Ici, il récupère le Client Context

Interface utilisateur graphique, application générée automatiquement avec une confiance moyenne

il stocke la valeur 0xFFFFFFFF00000000.

Interface utilisateur graphique, application générée automatiquement

Interface utilisateur graphique, application générée automatiquement

Une image contenant calendrier générée automatiquement

Il écrit 0xFFFFFFFF à l'offset 0x5c qui est la partie haute de CLFS_LSN lsnRestart.ullOffset

Texte généré automatiquement avec une confiance moyenne

Interface utilisateur graphique, texte, lettre générée automatiquement

Maintenant, nous exécutons la fonction ClfsEncodeBlockPrivate(), qui est responsable de l'écrasement du 0x50 par 0xFFFF0050 comme nous l'avons vu précédemment.

Là, il lit la valeur de SignatureOffset = 0x50 qui est toujours telle que nous l'avons mise dans le fichier malformé et l'ajoute au début de CLFS_LOG_BLOCK_HEADER.

Texte généré automatiquement

c'est une boucle qui écrit 2 octets, comme le SignatureOffset au lieu de pointer vers une valeur correcte qui dans un fichier normal est une valeur élevée, par exemple 0x3f8 qui lui fait écrire plus loin, ici il écrira dans le même CLFS_LOG_BLOCK_HEADER

L'idée est de changer la destination d'écriture pour tenter de corrompre la valeur SignatureOffset.

Fichier normal

Tableau généré automatiquement

À ce stade, il commencera à boucler et à écrire deux octets.

Interface utilisateur graphique, application générée automatiquement

Le compteur doit atteindre la valeur 0x3d pour sortir de la boucle.

Interface utilisateur graphique, application générée automatiquement

RCX augmente à partir de 0x200, nous sommes déjà dans le troisième cycle, et sa valeur est 0x600

Interface utilisateur graphique, texte, application générée automatiquement

dans l'itération 0xe, RCX est 0x1a00

Interface utilisateur graphique, texte généré automatiquement

Une image contenant calendrier générée automatiquement

C'était là où il avait écrit le 0xFFFFFFFF000000.

Interface utilisateur graphique, texte, application générée automatiquement

Tableau généré automatiquement avec une confiance moyenne

Il lit les deux derniers octets FFFF

Texte généré automatiquement

Et il les copiera ensuite dans R8

Une image contenant calendrier générée automatiquement

Une image contenant calendrier générée automatiquement

Comme nous l'avons vu, cette valeur est critique car elle permet de contourner la vérification et d'écrire hors limites pour corrompre le pointeur pContainer du fichier qui suit le memset() et d'écrire des zéros en haut et de le laisser pointer vers notre mémoire contrôlée (HeapSpray).

Dans le CClfsBaseFilePersisted::AllocSymbol, la même somme qui va obtenir la destination du memset qui est cbSymbolZone + adresse finale de CLFS_BASE_RECORD_HEADER la compare avant contre Base_block + 0xFFFF0050, donc elle a des valeurs corrompues des deux côtés de l'équation.

CbSymbolZone= 0x1114B

C'est la valeur malformée qui, ajoutée à l'adresse finale de CLFS_BASE_RECORD_HEADER, lui fera écrire hors limites et l'autre membre de la comparaison qui devrait être l'adresse du Base Block + SignatureOffset reste SignatureOffset =0xFFFF0050 ce qui permet à cette vérification de passer et d'écrire hors limites dans le memset() et de mettre à zéro le haut du pointeur qui restera pointant vers notre HeapSpray.

Interface utilisateur graphique, application, tableau générée automatiquement

Puisque RCX est plus petit que RDX.

Interface utilisateur graphique, application générée automatiquement

Comme nous l'avons vu précédemment. (Les valeurs peuvent différer car elles appartiennent à une exécution précédente)

Il corrompra le pointeur, en mettant les octets les plus élevés à 0

Tableau généré automatiquement

Le laissant pointer vers une zone mémoire que nous contrôlons via HeapSpray

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

Tableau généré automatiquement

Ainsi, lorsque la vulnérabilité est déclenchée, nous arrivons à CClfsBaseFilePersisted::RemoveContainer

Une image contenant interface utilisateur graphique générée automatiquement

Là, se trouvera le pointeur déjà corrompu et il peut être exploité comme nous l'avons vu précédemment.

Interface utilisateur graphique, application générée automatiquement

À ce stade, nous avons exploité le bogue, il mène au contrôle des fonctions qui permettent de lire le jeton SYSTEM et d'écrire dans notre propre processus pour réaliser l'élévation de privilèges locale.

Nous espérons que cela vous sera utile, si vous avez des doutes, vous pouvez nous contacter à [email protected] et [email protected]

Profitez-en !

Télécharger l’outil