Couper le son : enchaîner les vulnérabilités pour obtenir une RCE sur Outlook : partie 2
Introduction
En utilisant la vulnérabilité décrite dans la partie 1 de cette série de blogs,nous avons une fois de plus la possibilité de jouer un fichier son personnalisé sur la cible, en exploitant la fonctionnalité de rappel sonore d'Outlook. Pour exploiter cette capacité et la transformer en une exécution de code à distance (RCE), nous avons commencé à rechercher des vulnérabilités dans l'analyse des fichiers sonores sous Windows.
Surfaces d'attaque
Le fichier son à lire par Outlook est au format de fichier audio Waveform (WAV). Il est lu à l'aide de la fonction PlaySound , qui reçoit le chemin du fichier son. PlaySound va charger le fichier, l'analyser, puis appeler soundOpen qui appellera les différentes fonctions d'onde, telles que waveOutOpen.
Les fichiers WAV servent de conteneur (ou de wrapper) pour plusieurs codecs audio. Un codec est un programme ou un code qui code ou décode un flux de données (image, vidéo ou audio). En général, le codec est un codage à modulation par code d'impulsion (PCM), qui est un moyen simple de représenter des signaux analogiques échantillonnés.
Il existe trois principales surfaces d'attaque où nous pouvons rechercher des vulnérabilités :
Analyse du format WAV
Gestionnaire de compression audio
Différents codecs audio
Analyse du format WAV
L'analyse du format WAV est implémentée dans la fonction soundInitWavHdr dans winmm.dll (la bibliothèque qui implémente l'API Windows Multimedia). La surface d'attaque présentée ici n'est pas grande et semble avoir été examinée ; nous n'avons pas trouvé de vulnérabilités.
Qu'est-ce que le gestionnaire de compression audio (ACM) ?
Le gestionnaire de compression audio (ACM) est le code responsable de la gestion des cas dans lesquels le codec utilisé dans le fichier WAV n'utilise pas un encodage PCM simple et doit donc être décodé par un décodeur personnalisé. Ces décodeurs sont implémentés dans des fichiers portant l'extension .acm. Un exemple courant est le codec MP3, qui est implémenté dans le fichier l3codeca.acm. Chaque codec est géré par un pilote (qui n'est pas le même qu'un pilote en mode noyau, mais dont la fonction est similaire), qui est enregistré par l'intermédiaire de l'ACM.
Chaque fois qu'une transformation est nécessaire, comme la transformation de MP3 en PCM ou vice versa, l'ACM entre en action et gère cette transformation. Lorsque nous utilisons un fichier WAV dans lequel le PCM n'est pas utilisé, l'ACM est interrogé pour savoir si le codec spécifié dans le fichier existe et s'il peut gérer la transformation (Figure 1).
Chaque pilote ACM doit implémenter des fonctions telles que acmdStreamSize et acmdStreamOpen. La première renvoie la taille (en octets) nécessaire pour la sortie de la transformation ; la seconde crée une structure de flux et définit les champs appropriés, tels que la fonction de rappel de décodage.
La surface d'attaque d'ACM n'est pas si grande. Pourtant, nous avons réussi à trouver une vulnérabilité dans ce code, comme nous le montrerons plus loin dans ce billet.
Différents codecs audio
Pour la dernière surface d'attaque, nous disposons de différents codecs audio installés par défaut. Le codec est spécifié dans le fichier WAV de deux manières différentes :
L'étiquette wFormatTag dans le bloc FORMAT
Si l'étiquette wFormatTag est WAVE_FORMAT_EXTENSIBLE, SubFormat contiendra le GUID du codec audio.
La liste des codecs disponibles est décrite dans l'annexe.
Notions de base sur le traitement du signal audio
Avant de nous plonger dans le code, nous allons nous familiariser avec les bases du traitement du signal audio. Si vous êtes déjà familiarisé avec ces concepts, n'hésitez pas à passer à la section suivante.
Lorsque nous entendons un son, il s'agit en fait d'une vibration qui se propage à travers un support de transmission. Le son est la réception de ces ondes vibratoires telles qu'elles sont captées par nos oreilles et perçues par notre cerveau.
Un signal audio est une forme d'onde continue. Pour le traiter numériquement, nous devons le convertir d'un signal analogique en un signal numérique (par exemple, à l'aide d'un convertisseur ADC). Cette conversion est la discrétisation du signal analogique. Pour ce faire, nous échantillonnons le signal analogique plusieurs fois en points de données régulièrement espacés (appelés échantillons). Le taux d'échantillonnage (également appelé fréquence d'échantillonnage) détermine le nombre d'échantillons prélevés par seconde. Des taux d'échantillonnage plus élevés permettent d'enregistrer plus de détails, mais ils nécessitent également plus de stockage et de traitement
Outre le taux d'échantillonnage, nous avons la taille de l'échantillon,qui correspond au nombre de bits utilisés pour chaque échantillon. Une fois encore, plus la taille de l'échantillon est élevée, plus l'échantillon est de bonne qualité (ou plus proche du son original). Les tailles d'échantillon sont généralement de 16 ou 24 bits par échantillon.
Les codecs audio sont basés sur des modèles psycho-acoustiques. La psycho-acoustique est l'étude scientifique de la perception des sons et de l'audiologie, c'est-à-dire de la manière dont le système auditif humain perçoit les différents sons. Par exemple, la plage d'audition humaine se situe entre 20 Hz et 20 000 Hz. Ainsi, pour réduire davantage la taille du fichier, les codecs audio peuvent se débarrasser des fréquences qui ne peuvent pas être entendues par les humains. En outre, le codec audio peut se débarrasser des signaux si leur volume n'est pas assez fort pour que l'oreille humaine l'entende. Par exemple, un son de 20 Hz ne sera pas entendu s'il est inférieur à 60 décibels.
Il existe de nombreux autres exemples de modèles psycho-acoustiques, par exemple le masquage de signaux calmes lorsqu'ils sont proches en fréquence ou en temps d'un signal fort.
L'analyse, l'interprétation et la modification des signaux sont effectuées à l'aide de banques de filtres, qui divisent le signal en sous-bandes. Il s'agit de bandes distinctes de composants qui permettent un examen plus détaillé et la manipulation de parties spécifiques du signal. Les banques de filtres couramment utilisées sont les banques de filtres DCT et polyphase.
Avec ces connaissances de base en tête, nous pouvons nous plonger dans la recherche de différents codecs.
Première tentative : écriture hors limites dans le tampon d'échantillons
Nous avons d'abord essayé d'étudier le MP3, car comparé aux autres codecs, il est beaucoup plus complexe. La plupart des autres codecs n'effectuent que des conversions assez simples, alors que le MP3 comporte plusieurs étapes au cours de son processus de décodage (Figure 2).
Les données audio du MP3 sont organisées en une série de trames, chaque trame représentant un petit segment audio. Une trame se compose d'un en-tête et de données audio. Les données audio sont compressées à l'aide du codage de Huffman. Chaque trame représente exactement 1 152 échantillons du domaine fréquentiel par canal (mono/stéréo). Elle est divisée en deux parties appelées granules, chacune contenant 576 échantillons. Chaque trame contient également des informations relatives à son décodage, appelées informations annexes (Figure 3).
La plupart des opérations effectuées dans le cadre du processus de décodage des MP3 sont complexes et, bien qu'elles puissent théoriquement constituer un endroit intéressant (et cool) pour rechercher des vulnérabilités subtiles, dans la pratique, de nombreuses opérations (telles que la DCT modifiée, la banque de filtres polyphasés et la réduction des alias) sont effectuées sur une mémoire tampon qui contient en permanence des valeurs pour 576 échantillons. Il n'est donc pas plausible de trouver ici une vulnérabilité d'écriture hors limites. Le décodage Huffman est intéressant, car il fonctionne naturellement sur des données plus dynamiques (par opposition au tampon de 576 échantillons).
Décodage de Huffman du MP3
Le codage de Huffman d'un granule (576 échantillons) est implémenté à l'aide de tables de codes (au lieu d'un arbre binaire). La plage de fréquences totale de 0 à 22 050 (fréquence de Nyquist) est divisée en cinq régions (Figure 4) :
1., 2. et 3. Trois régions de « grandes valeurs » : échantillons dont la valeur est comprise entre -8 206 et 8 206
4. « région count1 » : quadruples des valeurs -1, 0 ou 1
5. « région rzero » : les valeurs de haute fréquence sont supposées avoir de faibles amplitudes et n'ont donc pas besoin d'être codées ; ces valeurs sont égales à 0.
Chaque région possède ses propres tables de Huffman (à l'exception de la région 0), et les échantillons de fréquences différentes sont donc codés différemment.
La classification des échantillons dans les régions repose sur les variables suivantes :
big_values : spécifie le nombre total d'échantillons dans la région de grandes valeurs
region0_count et region1_count : partitionne les valeurs big_values en sous-régions ; en soustrayant leur somme de big_values, on obtient le nombre d'échantillons dans region2.
part2_3length : spécifie le nombre de bits utilisés pour les facteurs d'échelle (partie 2) et le nombre de bits utilisés pour le codage de Huffman (partie 3).
Le processus de décodage des 576 échantillons se déroule comme suit :
Décodage des échantillons dans la région big_values
Décodage des échantillons dans la région count1
Si le nombre de bits traités est supérieur à part2_3length, alors nous avons pratiquement décodé toutes les données d'entrée, et même celles qui ont été lues en trop. Par conséquent, soustrayez 4 de total_samples_read (c'est-à-dire ignorez ces bits d'entrée)
S'il reste des échantillons, remplissez-les avec le 0 (cela forme la région 0)
Cette logique est présentée sous forme de pseudo-code dans le tableau 1.
total_samples_read = 0;
// Decode big_values region
[redacted for brevity]
// Decode count1 region [1]
for (int i = 0; i < count1; i++) {
samples[total_samples_read++] = huff_decode(bitstream, count1_huff_table);
}
if (bits_processed > part2_3length) [2] {
// Overread. Throw last 4 samples
total_samples_read -= 4;
}
// Fill rzero region with zeros
for (int i = total_samples_read; i < 576; i++) {
samples[total_samples_read++] = 0; [3]
}
Tableau 1 : Pseudo-code de la logique de décodage de Huffman
Malheureusement, le code ne tient pas compte d'un cas particulier, qui conduit à un sous-flux d'entier.
La taille de la région des grandes valeurs est de 0
La taille de la région count1 est 0
Part2_3length est 0
Bits_processed est supérieur à 0
Dans ce cas précis, bits_processed va être plus grand que part2_3length (il atteint une valeur non nulle avant le processus de décodage pendant le décodage des facteurs d'échelle). Le code va donc « jeter » les quatre derniers échantillons. Comme le code n'a traité aucun échantillon, total_samples_read est 0. Ici, nous allons avoir un sous-flux et le code pense que nous avons traité -4 échantillons. Maintenant, il va remplir la région 0 comme suit :
Définissez le pointeur de tampon sur &samples[total_samples_read]. Cela indique 16 octets avant le tampon d'échantillons.
Définissez la taille d'écriture sur 576 - total_samples_read = 576 - (-4) = 580 entiers.
Ainsi, nous avons une écriture hors limites directement avant le tampon d'échantillons avec la valeur zéro. Parfait !
Alors pourquoi cette vulnérabilité n'a-t-elle pas reçu de CVE ? Le tampon d'échantillons fait partie d'une structure, et le champ juste avant le tampon d'échantillons est le tableau facteurs d'échelle. Il s'agit d'un tableau avec des champs que nous contrôlons déjà, et nous n'avons donc pas vraiment d'impact intéressant ici.
La même vulnérabilité se produit également après que le code a déquantifié les échantillons. Il remplit à nouveau la région 0, cette fois avec le tampon déquantifié. Vous voulez deviner ce qui se trouve avant le tampon déquantifié ? Le tampon d'échantillonnage décodé de Huffman. Il s'agit du même tampon d'échantillons que nous avons vu plus tôt, que nous contrôlons également. Donc, une fois de plus, nous n'avons pas d'impact réel.
Ces écritures hors limites existent toujours dans le décodeur MP3 (accessible à la fois via WAV et via les fichiers .mp3) et, selon Microsoft, elles pourraient être corrigées à l'avenir. Bien qu'aucune vulnérabilité n'ait été trouvée lors de l'inversion du codec, nous pensons qu'il peut y avoir des vulnérabilités qui se cachent dans les différentes opérations complexes effectuées par le décodeur.
Deuxième tentative : dépassement d'entier dans le codec IMA ADPCM
Notre prochaine tentative sera le codec IMA ADPCM, qui est implémenté dans imaadp32.acm. Comme nous le savons maintenant, l'ACM va gérer les transformations depuis et entre les différents codecs. Pour enregistrer un codec, le code doit implémenter les fonctions de l'ACM. L'une d'elles est la fonction acmStreamSize, qui renvoie le nombre d'octets nécessaires pour le tampon de destination.
Le codec IMA ADPCM calcule la taille du tampon de destination en fonction de la taille de la charge utile d'entrée (cbSrcLength), de l'alignement (nBlockAlign) et des échantillons par bloc (wSamplesPerBlock; tableau 2).
(cbSrcLength / pwfxSrc->nBlockAlign) *(pwfxSrc->wSamplesPerBlock * pwfxDst->nBlockAlign)
Tableau 2 : Calcul de la taille du tampon
Avant de multiplier, le code s'assure que le calcul n'aboutira pas à un dépassement de capacité (tableau 3).
SrcNumberOfBlocks = cbSrcLength / pwfxSrc->WaveFormat.nBlockAlign;
v14 = pwfxSrc->wSamplesPerBlock * pwfxDst->nBlockAlign;
if ( 0xFFFFFFFF / v14 < SrcNumberOfBlocks )
return ERROR_OVERFLOW;
IsThereRemainder = cbSrcLength % pwfxSrc->WaveFormat.nBlockAlign;
if ( IsThereRemainder )
++SrcNumberOfBlocks;
DstBufferLengthInBytes = v14 * SrcNumberOfBlocks;
Tableau 3 : Vérification du calcul visant à empêcher le dépassement d'entier
Cette vérification ne suffit apparemment pas à empêcher un dépassement. S'il y a un reste dans la division cbSrcLength / pwfxSrc->nBlockAlign, le code incrémente le résultat de (cbSrcLength / pwfxSrc->nBlockAlign), qui est utilisé dans la multiplication. Le contrôle de dépassement ne couvre pas cette incrémentation. Par conséquent, nous pouvons toujours dépasser la longueur du tampon de destination en spécifiant des valeurs personnalisées.
Nous devons fournir cbSrcLength qui a un reste lorsqu'il est divisé par pwfxSrc->nBlockAlign.
Le tableau 4 présente un exemple de valeurs conduisant à un dépassement d'entier.
cbSrcLength = 0x71c71c72
pwfxSrc->nBlockAlign = 8
pwfxSrc->wSamplesPerBlock = 9
pwfxDst->nBlockAlign = 2
Tableau 4 : Exemple de valeurs conduisant à un dépassement d'entier
Il en résulte un tampon de destination de 0xE octets, alors que le tampon de destination devrait être beaucoup plus grand.
Bien que cela ressemble à un dépassement d'entier qui peut mener à une écriture hors limites, la fonction de décodage s'assure correctement qu'aucune écriture n'a lieu après le tampon alloué et ne suppose pas que le tampon a été alloué avec la taille correcte.
Ainsi, bien que nous fournissions plusieurs échantillons, en pratique, lorsque le tampon de destination est plein, le code s'arrête. Le même comportement se produit dans le codec AD PCM (implémenté dans msadp32.acm).
Troisième tentative : dépassement d'entier dans ACM (CVE-2023-36710)
Enfin, nous avons trouvé une jolie vulnérabilité dans le code ACM. Dans le cadre du processus de lecture d'un fichier WAV, la fonction mapWavePrepareHeader dans le gestionnaire ACM (implémentée dans msacm32.drv) est appelée.
Cette fonction présente une vulnérabilité de dépassement d'entier. Elle appelle acmStreamSize, ce qui appelle le rappel du pilote. Rappelez-vous que cette fonction renvoie la taille nécessaire pour le tampon de destination. Après réception de cette taille, mapWavePrepareHeader ajoute 176 octets (la taille de l'en-tête du flux qui précédera le tampon de destination) sans vérification des dépassements. Le résultat de cet ajout est transmis à GlobalAlloc (Figure 6).
Il s'agit d'un problème exploitable. Nous pouvons faire en sorte que GlobalAlloc alloue un très petit tampon au lieu d'un grand en faisant en sorte que acmStreamSize renvoie une valeur comprise entre 0xffffff50 et 0xffffffff. Après cette allocation, nous pouvons provoquer deux écritures hors limites :
Les valeurs de l'en-tête du flux, telles que la taille de la structure, les pointeurs et les tailles des tampons source et destination. Ces valeurs sont partiellement contrôlables.
Les valeurs décodées du codec. Ces valeurs sont entièrement contrôlées.
Pour déclencher la vulnérabilité, nous devons fournir un échantillon WAV dont la taille, une fois décodée, sera supérieure ou égale à 0xffffff50. Bien que cela semble facile à réaliser, nous avons constaté tout au long de nos tentatives qu'il n'était pas possible de le faire avec certains codecs. Par exemple, avec le codec MP3, dans le cadre du calcul, il y a une multiplication par 1 152 ou 576 (qui sont le nombre d'échantillons par image). Le résultat de ce calcul ne se situera jamais dans la fourchette souhaitée.
Enfin, nous avons réussi à déclencher la vulnérabilité en utilisant le codec ADP d'IMA. La taille du fichier est d'environ 1,8 Go. En effectuant l'opération de limite mathématique sur le calcul, nous pouvons conclure que la plus petite taille de fichier possible avec le codec ADP d'IMA est de 1 Go.
L'exploitation d'une telle vulnérabilité est facilitée par l'existence d'un moteur de script permettant de créer dynamiquement un exploit. Comme Windows Media Player n'en possède pas, l'exploitation peut s'avérer plus difficile. Cela reste possible (comme l'a démontré Chris Evans dans son article de blog « Advancing exploitation: a scriptless 0day exploit against Linux desktops »). Cependant, il y a plus de chances que cette vulnérabilité soit exploitée avec succès dans le contexte de l'application Outlook (ou d'autres applications de messagerie instantanée).
Synthèse
Cette série de blogs a couvert des recherches qui ont commencé par une vulnérabilité exploitée dans la nature. (Lire Partie 1, si vous ne l'avez pas déjà fait.) La recherche s'est ensuite poursuivie par la recherche de contournements et a finalement abouti à la découverte d'une vulnérabilité complémentaire à laquelle l'enchaîner afin d'obtenir une chaîne RCE Zero Click. Bien que ces vulnérabilités soient corrigées, les attaquants continuent de rechercher des surfaces d'attaque similaires et des vulnérabilités qui peuvent être exploitées à distance.
À ce jour, la surface d'attaque dans Outlook que nous avons étudiée existe toujours, et de nouvelles vulnérabilités peuvent être trouvées et exploitées. Bien que Microsoft ait corrigé Exchange pour supprimer les courriers contenant la propriété PidLidReminderFileParameter, nous ne pouvons pas exclure la possibilité de contourner cette mesure d'atténuation.
Annexe
Ce site dresse la liste de tous les types de médias et de codecs disponibles dans Media Foundation de Microsoft.
Nos tests indiquent que seuls les codecs suivants sont disponibles via WAV en pratique :
1 : PCM
2 : ADPCM
6 : A-LAW
7 : U-LAW
11 : IMA ADPCM
31 : GSM 6.10
55 : MPEG-1 Audio Layer III (MP3)
00000003_0000_0010_8000_00aa00389b71 — IEEE Float
00000008-0000-0010-8000-00aa00389b71 — DTS Audio
00000092-0000-0010-8000-00aa00389b71 — Dolby Digital
00000164-0000-0010-8000-00aa00389b71 — Microsoft WMA