Ton stummschalten: Verkettung von Schwachstellen für RCE in Outlook – Teil 2
Einführung
Wenn wir die in Teil 1 dieser Blogreihe beschriebene Schwachstelle ausnutzen, haben wir erneut die Möglichkeit, eine nutzerdefinierte Audiodatei auf dem Ziel wiederzugeben, und dabei den Erinnerungston von Outlook auszunutzen. Um diese Fähigkeit zu nutzen und in eine vollständige Remote-Code-Ausführung (RCE) umzuwandeln, begannen wir, beim Parsen von Audiodateien unter Windows nach Schwachstellen zu suchen.
Angriffsflächen
Die Audiodatei, die von Outlook abgespielt werden soll, hat das Dateiformat Waveform Audio (WAV). Sie wird über die Funktion PlaySound wiedergegeben, die den Audiodateipfad empfängt. PlaySound lädt die Datei, analysiert sie und ruft dann soundOpen an. Dadurch werden die verschiedenen Wave-Funktionen aufgerufen, wie waveOutOpen.
WAV-Dateien fungieren als Container (oder Wrapper) für mehrere Audio-Codecs. Ein Codec ist ein Programm oder Code, mit dem ein Datenstrom kodiert oder decodiert wird (z. B. Bild, Video oder Audio). Normalerweise ist dieser Codec Pulscode-Modulation (PCM), eine einfache Möglichkeit zur Darstellung von abgetasteten analogen Signalen.
Es gibt drei große Angriffsflächen, bei denen wir nach Schwachstellen suchen können:
WAV-Formatanalyse
Audio Compression Manager
Verschiedene Audio-Codecs
WAV-Formatanalyse
Die WAV-Formatanalyse ist in der Funktion soundInitWavHdr in winmm.dll eingebettet (die Bibliothek, die die Windows Multimedia API implementiert). Die dort vorgestellte Angriffsfläche ist nicht groß und scheint überprüft worden zu sein. Wir konnten dort keine Schwachstellen finden.
Was ist Audio Compression Manager?
Der Audio Compression Manager (ACM) ist der Code, der für die Verarbeitung von Fällen verantwortlich ist, in denen der in der WAV-Datei verwendete Codec keine einfache PCM-Codierung verwendet und daher von einem nutzerdefinierten Decoder decodiert werden muss. Diese Decoder werden in Dateien mit der Erweiterung .acm bereitgestellt. Ein beliebtes Beispiel ist der MP3-Codec, der in l3codeca.acm implementiert ist. Jeder Codec wird von einem Treiber verarbeitet (der nicht mit einem Kernelmodus-Treiber identisch, aber in seiner Funktion ähnlich ist), der über ACM registriert wird.
Wann immer eine Transformation erforderlich ist, wie z. B. die Umwandlung von MP3 in PCM oder umgekehrt, wird ACM aktiv und verwaltet diese Transformation. Wenn wir eine WAV-Datei verwenden, bei der PCM nicht verwendet wird, wird ACM abgefragt, ob der in der Datei angegebene Codec vorhanden ist und die Transformation verarbeiten kann (Abbildung 1).
Jeder ACM-Treiber muss Funktionen wie acmdStreamSize und acmdStreamOpen ausführen. Die erste gibt die Größe (in Byte) zurück, die für die Ausgabe der Transformation erforderlich ist. Die zweite erstellt eine Stream-Struktur und legt die entsprechenden Felder fest, wie z. B. die Decodierungs-Callback-Funktion.
Die Angriffsfläche von ACM ist nicht sehr umfangreich. Dennoch konnten wir eine Schwachstelle in diesem Code finden, die wir später in diesem Blogbeitrag beleuchten werden.
Verschiedene Audio-Codecs
Für die letzte Angriffsfläche sind verschiedene Audio-Codecs standardmäßig installiert. Der Codec wird in der WAV-Datei auf zwei verschiedene Arten angegeben:
wFormatTag im FORMAT-Chunk
Wenn wFormatTag WAVE_FORMAT_EXTENSIBLE ist, dann enthält das Unterformat die GUID für den Audio-Codec.
Die Liste der verfügbaren Codecs wird im Anhang beschrieben.
Grundlagen der Verarbeitung von Audiosignalen
Bevor wir in den Code eintauchen, sollten wir uns mit einigen Grundlagen der Audiosignalverarbeitung vertraut machen. Wenn Sie bereits mit diesen Konzepten vertraut sind, können Sie auch direkt mit dem nächsten Abschnitt fortfahren.
Wenn wir Töne hören, nehmen wir tatsächlich Vibrationen wahr, die sich durch ein Übertragungsmedium ausbreiten. Klang ist der Empfang dieser Schwingungswellen, die von unseren Ohren aufgenommen und von unserem Gehirn verarbeitet werden.
Ein Audiosignal ist eine kontinuierliche Wellenform. Um es digital zu verarbeiten, müssen wir es von einem analogen Signal in ein digitales Signal umwandeln (z. B. mithilfe eines ADC-Wandlers). Diese Umwandlung ist die Diskretisierung des analogen Signals. Dazu erfassen wir das analoge Signal mehrfach in gleichmäßig verteilten Datenpunkten (genannt Proben (englisch: Samples)). Die Abtastrate (auch als Abtastfrequenz bezeichnet) bestimmt, wie viele Samples pro Sekunde entnommen werden. Höhere Abtastraten erfassen mehr Details, erfordern aber auch mehr Speicher und Verarbeitung.
Neben der Abtastrate ist auch die Größe des Samples, die Anzahl der Bits, die für jede Probe verwendet werden, von Belang. Auch hier gilt: Je größer das Sample ist, desto besser (oder näher am ursprünglichen Klang) wird die Qualität. Die Größe des Samples beträgt in der Regel 16 Bit oder 24 Bit pro Sample.
Audio-Codecs basieren auf psychoakustischen Modellen. Psychoakustik ist die wissenschaftliche Untersuchung der Wahrnehmung von Tönen und Audiologie – wie das menschliche Hörsystem verschiedene Klänge wahrnimmt. Der menschliche Hörbereich liegt beispielsweise zwischen 20 Hz und 20.000 Hz. Um die Dateigröße weiter zu reduzieren, werden Audio-Codecs möglicherweise bestimmte Frequenzen entfernen, die von Menschen nicht wahrgenommen werden können. Darüber hinaus kann der Audio-Codec Signale entfernen, wenn die Lautstärke nicht stark genug ist, um vom menschlichen Ohr gehört zu werden. Ein 20-Hz-Ton ist beispielsweise nicht zu hören, wenn die Lautstärke weniger als 60 Dezibel beträgt.
Es gibt viele weitere Beispiele für psychoakustische Modelle, z. B. die Maskierung leiser Signale, wenn sie sich in Bezug auf Frequenz oder Zeitpunkt in der Nähe eines lauten Signals befinden.
Die Analyse, Interpretation und Modifikation der Signale erfolgt mithilfe von Filterbänken, die das Signal in Teilbänder aufteilen – verschiedene Komponentenbänder, die eine detailliertere Untersuchung und Manipulation bestimmter Teile des Signals ermöglichen. Häufig verwendete Filterbänke sind die DCT- und Polyphasenfilterbänke.
Mit diesem Grundwissen können wir uns nun der Untersuchung verschiedener Codecs zuwenden.
Erster Versuch: Out-of-Bound-Schreibvorgang im Sample-Puffer
Wir haben zunächst versucht, MP3 zu betrachten, da MP3 im Vergleich zu den anderen Codecs viel komplexer ist. Die meisten anderen Codecs führen nur relativ einfache Umwandlungen durch. Bei MP3 umfasst der Decodierungsprozess jedoch mehrere Schritte (Abbildung 2).
MP3-Audiodaten sind in einer Reihe von Frames organisiert, wobei jeder Frame ein kleines Audiosegment darstellt. Ein Frame besteht aus einem Header und den Audiodaten. Die Audiodaten werden mithilfe der Huffman-Codierung komprimiert. Jeder Frame stellt genau 1.152 Frequenzdomain-Samples pro Kanal (Mono/Stereo) dar. Dieser ist in zwei Teile partitioniert, die als Granulat bezeichnet werden, jeweils 576 Samples. Jeder Frame enthält auch Informationen zur Decodierung, die als Side Info bezeichnet werden. (Abbildung 3).
Die meisten Vorgänge, die im Rahmen des MP3-Decodierungsprozesses durchgeführt werden, sind komplex. Obwohl sie theoretisch ein interessanter Ort sein könnten, um nach subtilen Schwachstellen zu suchen, werden in der Praxis viele der Operationen (z. B. modifizierte DCT- oder Polyphasenfilterbänke und Aliasreduktion) auf einem Puffer ausgeführt, der ständig Werte für 576 Samples enthält. Daher ist es hier nicht plausibel, eine Schwachstelle bei einem Out-of-Bounds-Schreibvorgang zu finden. Interessanter wird es allerdings bei der Huffman-Dekodierung, da sie naturgemäß an dynamischeren Daten arbeitet (im Gegensatz zum Puffer mit 576 Samples).
Huffman-Dekodierung für MP3
Die Huffman-Codierung eines Granulats (576 Samples) wird über Codetabellen (anstatt über einen Binärbaum) implementiert. Der Gesamtfrequenzbereich von 0 bis 22.050 (Nyquist-Frequenz) ist in fünf Bereiche unterteilt (Abbildung 4):
1., 2. und 3. Drei Bereiche von „big values“ (großen Werten) – Samples, deren Wert zwischen -8.206 und 8.206 liegt
4. Bereich „count1“ – Vierfache der Werte -1, 0 oder 1
5. Bereich „rzero“ – bei höheren Frequenzwerten wird davon ausgegangen, dass sie niedrige Amplituden haben und daher nicht codiert werden müssen; diese Werte sind gleich 0.
Jeder Bereich verfügt über eigene Huffman-Tabellen (mit Ausnahme des 0-Bereichs), sodass Samples mit unterschiedlichen Frequenzen unterschiedlich codiert werden.
Die Klassifizierung von Samples nach Bereichen basiert auf den folgenden Variablen:
big_values – gibt an, wie viele Samples sich insgesamt im Bereich „big values“ befinden
region0_Count und region1_count – teilt big_values in Unterbereiche auf; durch Subtraktion ihrer Summe von big_values ergibt sich die Anzahl der Samples in region2.
part2_3length – gibt an, wie viele Bits für Skalierungsfaktoren verwendet werden (Teil 2) und wie viele für die Huffman-Codierung (Teil 3)
Die 576 Samples werden wie folgt decodiert:
Decodieren der Samples im Bereich „big_values“
Decodieren der Samples Bereich „count1“
Wenn die Anzahl der verarbeiteten Bits größer als part2_3length ist, haben wir praktisch alle Eingabedaten decodiert und sogar überlesen. Daher müssen wir von total_samples_read 4 subtrahieren (d. h. diese Eingabebits verwerfen).
Wenn dann noch Samples übrig sind, füllen wir diese mit 0 (dies bildet den 0-Bereich).
Diese Logik wird in der Tabelle 1 als Pseudo-Code dargestellt.
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]
}
Tabelle 1: Pseudo-Code der Dekodierungslogik nach Huffman
Leider fehlt der Code für einen bestimmten Edge Case, was zu einem Integer-Unterlauf führt.
Größe „big_values“-Bereichs ist 0
count1-Bereichsgröße ist 0
part2_3length ist 0
bits_processed ist größer als 0
In diesem speziellen Fall ist bits_processed größer als part2_3length (es erreicht einen Wert ungleich Null vor dem Decodierungsprozess während der Decodierung der Skalierungsfaktoren). Der Code „verwirft“ also die letzten vier Samples. Da das Coding keine Samples verarbeitet hat, beträgt total_samples_read 0. Hier wird es zu einem Unterlauf kommen. Der Code geht dann davon aus, dass wir -4 Samples verarbeitet haben. Nun wird die 0-Region wie folgt gefüllt:
Der Pufferzeiger wird auf &samples[total_samples_read] gesetzt. Dies weist auf 16 Byte hin, bevor die Samples puffern.
Die Schreibgröße wird auf 576 festgelegt – total_samples_read = 576 - (-4) = 580 Ganzzahlen.
So erreichen wir einen Out-of-Bound-Schreibvorgang, bevor die Samples mit dem Wert Null puffern. Sehr schön!
Warum wurde dieser Schwachstelle also keine CVE zugewiesen? Der Sample-Puffer ist Teil einer Struktur. Das Feld direkt vor dem Sample-Puffer ist das Skalierungsfaktor-Array. Das ist ein Array mit Feldern, die wir bereits kontrollieren. Daher ist das für uns nicht wirklich interessant.
Die gleiche Schwachstelle tritt auch dann auf, wenn der Code die Samples dequantisiert hat. Der 0-Bereich wird erneut mit dem dequantisierten Puffer gefüllt. Möchten Sie raten, was vor dem dequantisierten Puffer steht? Der decodierte Sample-Puffer nach Huffman. Dies ist der gleiche Sample-Puffer, den wir zuvor gesehen haben und den wir ebenfalls kontrollieren. Auch das ist also für uns nicht von Interesse.
Diese Out-of-Bound-Schreibvorgänge sind weiterhin im MP3-Decoder vorhanden (beide sind über WAV und über .mp3-Dateien zugänglich). Den Angaben von Microsoft nach können sie in Zukunft behoben werden. Obwohl bei der Umkehrung des Codec keine wirkungsvolle Schwachstelle gefunden wurde, glauben wir, dass es Schwachstellen gibt, die sich in den verschiedenen komplexen Vorgängen des Decoders verbergen.
Zweiter Versuch: Integer-Überlauf im IMA ADPCM Codec
Unser nächster Versuch ist der IMA ADPCM Codec, der in imaadp32.acm implementiert ist. Wie wir jetzt wissen, verwaltet ACM Transformationen von und zwischen verschiedenen Codecs. Um einen Codec zu registrieren, muss der Code ACM-Funktionen implementieren. Eine dieser Funktionen ist acmStreamSize. Diese gibt die Anzahl der Bytes zurück, die für den Zielpuffer benötigt werden.
Der IMA ADPCM Codec berechnet die Größe des Zielpuffers basierend auf der Größe der Eingabe-Payload (cbSrcLength), Ausrichtung (nBlockAlign) und Samples pro Block (wSamplesPerBlock; Tabelle 2).
(cbSrcLength / pwfxSrc->nBlockAlign) *(pwfxSrc->wSamplesPerBlock * pwfxDst->nBlockAlign)
Tabelle 2: Berechnung der Puffergröße
Vor der Multiplikation stellt der Code sicher, dass die Berechnung nicht zu einem Integer-Überlauf führt (Tabelle 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;
Tabelle 3: Berechnungsprüfung zum Vermeidung von Integer-Überlauf
Diese Prüfung reicht anscheinend nicht aus, um einen Überlauf zu verhindern. Wenn es einen Rest in der Division von cbSrcLength / pwfxSrc->nBlockAlign gibt, erhöht der Code erhöht das Ergebnis von (cbSrcLength / pwfxSrc->nBlockAlign), die bei der Multiplikation verwendet wird. Die Überlaufprüfung deckt diese Erhöhung nicht ab. Daher können wir die Länge des Zielpuffers immer noch zum Überlaufen bringen, indem wir nutzerdefinierte Werte angeben.
Wir müssen für eine cbSrcLength mit einem Rest sorgen (bei Division durch pwfxSrc->nBlockAlign).
Tabelle 4 zeigt ein Beispiel für Werte, die zu einem Integer-Überlauf führen.
cbSrcLength = 0x71c71c72
pwfxSrc->nBlockAlign = 8
pwfxSrc->wSamplesPerBlock = 9
pwfxDst->nBlockAlign = 2
Tabelle 4: Beispiel für Werte, die zu einem Integer-Überlauf führen
Dies führt zu einem Zielpuffer von 0xE Byte, dabei sollte der Zielpuffer viel größer sein.
Obwohl dies wie ein Integer-Überlauf scheint, der zu einem Out-of-Bounds-Schreibvorgang führen kann, stellt die Decodierungsfunktion korrekt sicher, dass nach dem zugewiesenen Puffer keine Schreibvorgänge erfolgen. Sie geht nicht davon aus, dass der Puffer mit der richtigen Größe zugewiesen wurde.
Obwohl wir also mehrere Samples zur Verfügung stellen, stoppt der Code in der Praxis, wenn der Zielpuffer voll ist. Das gleiche Verhalten tritt im AD PCM Codec (implementiert in msadp32.acm auf).
Dritter Versuch: Integer-Überlauf in ACM (CVE-2023-36710)
Schließlich haben wir eine interessante Schwachstelle im ACM-Code gefunden. Im Rahmen der Wiedergabe einer WAV-Datei wird die Funktion mapWavePrepareHeader im ACM-Manager (implementiert in msacm32.drv) aufgerufen.
Diese Funktion hat eine Schwachstelle in Bezug auf Integer-Überlauf. Sie ruft acmStreamSize auf, die wiederum den Callback des Treibers aufruft. Wie wir wissen, gibt diese Funktion die erforderliche Größe für den Zielpuffer zurück. Nachdem diese Größe bekannt ist, fügt mapWavePrepareHeader 176 Byte (die Größe des Stream-Headers, der den Zielpuffer weiterführt) ohne Überlaufprüfungen hinzu. Das Ergebnis dieser Addition wird an GlobalAlloc weitergeleitet (Abbildung 6).
Dieses Problem lässt sich ausnutzen. Wir können dafür sorgen, dass GlobalAlloc einen wirklich kleinen Puffer zuweist (anstatt einen großen Puffer), indem acmStreamSize einen Wert zwischen 0xffffff50 und 0xffffffff zurückgibt. Nach dieser Zuordnung können zwei Out-of-Bounds-Schreibvorgänge verursacht werden:
Die Stream-Header-Werte, wie Strukturgröße, Quell- und Ziel-Pufferzeiger und -Größen. Diese Werte sind zum Teil steuerbar.
Die dekodierten Werte des Codec. Diese Werte werden vollständig gesteuert.
Um die Schwachstelle auszulösen, müssen wir ein WAV-Sample mit einer Größe bereitstellen, die bei der Decodierung größer oder gleich 0xffffff50 ist. Obwohl dies einfach zu erreichen scheint, haben wir bei unseren Versuchen festgestellt, dass es in einigen Codecs eventuell nicht möglich ist, dies zu erreichen. Beispielsweise erfolgt beim MP3-Codec als Teil der Berechnung eine Multiplikation mit entweder 1.152 oder 576 (die Anzahl der Samples pro Frame). Das Ergebnis dieser Berechnung wird niemals in dem von uns gewünschten Bereich liegen.
Schließlich konnten wir die Schwachstelle über den IMA ADP Codec auslösen. Die Dateigröße beträgt ca. 1,8 GB. Durch die Ausführung der mathematischen Begrenzung bei der Berechnung können wir feststellen, dass die kleinstmögliche Dateigröße mit dem IMA ADP Codec 1 GB beträgt.
Die Ausnutzung einer solchen Schwachstelle wird vereinfacht, wenn eine Scripting-Engine zur dynamischen Erstellung eines Exploits verfügbar ist. Da Windows Media Player nicht über eine solche verfügt, kann die Ausnutzung schwieriger sein. Das könnte dennoch möglich sein (wie Chris Evans in seinem Blogbeitrag „Fortschrittliche Ausnutzung: Ein skriptloser Zero-Day-Exploit gegen Linux-Desktops“ ausführte). Dennoch besteht eine größere Wahrscheinlichkeit, dass diese Schwachstelle erfolgreich im Kontext der Outlook-Anwendung (oder anderen Instant-Messaging-Anwendungen) ausgenutzt werden kann.
Zusammenfassung
Diese Blogserie befasste sich mit der Erforschung einer Schwachstelle, die in im freien Internet ausgenutzt wurde. (Lesen Sie Teil 1, falls Sie das noch nicht getan haben.) Die Suche nach Umgehungen setzte sich dann fort. Schließlich fanden wir eine Companion-Sicherheitslücke, die kombiniert werden konnte, um so eine RCE-Kette zu erreichen, bei der keine Interaktion mit dem Nutzer notwendig war (Zero-Click). Obwohl diese Schwachstellen behoben sind, suchen Angreifer weiterhin nach ähnlichen Angriffsflächen und Sicherheitslücken, die remote ausgenutzt werden können.
Derzeit besteht die Angriffsfläche in Outlook, die wir untersucht haben, noch immer. Es könnten also neue Schwachstellen gefunden und ausgenutzt werden. Obwohl Microsoft einen Patch für Exchange bereitgestellt hat, um E-Mails zu löschen, die die Eigenschaft PidLidReminderFileParameter enthalten, können wir die Möglichkeit nicht ausschließen, dass diese Abwehr umgangen wird.
Anhang
Diese Website listet alle Medientypen und Codecs auf, die in der Microsoft Media Foundation verfügbar sind.
Unsere Tests haben ergeben, dass in der Praxis nur die folgenden Codecs über WAV verfügbar sind:
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