
CVE-2017-8570 Exp und Exploit-Sample-Analyse
Ursache: Beim Öffnen eines Office-Dokuments wird FLTLDR.EXE verwendet, um eine eingebettete EPS-Datei zu rendern, die die Schwachstelle enthält. Die Datei ist in der PostScript-Sprache verfasst und kann vom Angreifer durch eine "save-restore"-Operation ausgenutzt werden. Im Kern handelt es sich um eine UAF-Schwachstelle (Use-After-Free). Wenn ein Benutzer eine Datei mit einem fehlerhaften Grafikbild öffnet oder wenn ein Benutzer ein fehlerhaftes Grafikbild in eine Office-Datei einfügt, kann die Schwachstelle ausgenutzt werden.
Betroffene Versionen: Microsoft Office 2010 Service Pack 2, Microsoft Office 2013 Service Pack 1, Microsoft Office 2016
POC: kcufId's Github
Der Autor suchte lange im Internet, fand jedoch kein Office-Installationspaket, das EPSIMP32.FLT enthielt. Glücklicherweise stellte Meister kcufId ein LoadEps.exe zur Verfügung, um EPS-Dateien zu laden. Vielen Dank an Meister kcufId.
LoadEps.exe lädt zunächst EPSIMP32.FLT:

Anschließend wird ImportGr aufgerufen, um die EPS-Datei zu laden:

Hier direkt mit F7 folgen, dann kann man erfolgreich an den in EPSIMP32.FLT gesetzten Breakpoints anhalten.
Bevor wir zum eigentlichen Thema kommen, zuerst eine Erläuterung der PostScript-Objektstruktur.
// PostScript Object
struct PostScript object
{
dword type;
dword attr;
dword value1;
dword value2; // wenn Array, Zeiger auf userdict, wo das Array-Objekt gespeichert ist
}ps_obj;
Die entsprechenden Werte für verschiedene type sind wie folgt:
0x0 nulltype
0x3 integertype
0x5 realtype
0x8 booleantype
0x10 operatortype
0x20 marktype
0x40 savetype
0x300 nametype
0x500 stringtype
0x900 filetype
0x30000 arraytype
0x0B0000 packedarraytype
0x70000 packedarraytype
0x110000 dicttype
0x210000 gstatetype
Am Beispiel einer Zeichenkette wird deren Speicherstruktur erläutert. Wenn man einen Breakpoint auf die forall-Funktion setzt, kann man weiter sehen, wie sie mit Zeichenketten umgeht (wie man die forall-Funktion lokalisiert, siehe https://paper.seebug.org/368/).

Bild 1 entspricht ps_obj, dessen value2-Element auf den entsprechenden Eintrag in der Indexliste zeigt (Bild 2). Der Index-Eintrag zeigt auf eine Struktur der Größe 0x30, deren Offset 0x24 einen Zeiger auf einen Zeiger auf eine Struktur der Größe 0x28 speichert (Bild 5), und Offset 0x2C speichert die Größe der Zeichenkette (Bild 3). In der Struktur in Bild 5 speichert Offset 0x4 die Adresse des entsprechenden Eintrags in der Indexliste dieser Struktur (d.h. 0x01DB5E94 aus Bild 4), Offset 0x20 zeigt auf die endgültige Speicherposition der Zeichenkette (Bild 6), Offset 0x24 ist die tatsächlich belegte Speichergröße – Zeichenkettengröße + 1.
Struktur der Größe 0x30:
+0x0 dword
+0x4 dword
+0x8 dword
+0xc dword
+0x10 dword
+0x14 dword
+0x18 dword
+0x1c dword
+0x20 dword
+0x24 dword pp_struct // Zeiger auf Zeiger auf Struktur der Größe 0x28
+0x28 dword
+0x2c dword size // tatsächliche Größe der Zeichenkette
Struktur der Größe 0x28 (bei einem Array hat diese Struktur die Größe 0x2C, und Offset 0x28 zeigt auf die Array-Elemente, wobei jedes Element ein ps_obj ist):
+0x0 dword
+0x4 dword // speichert die Adresse des entsprechenden Eintrags in der Indexliste dieser Struktur
+0x8 dword
+0xc dword
+0x10 dword
+0x14 dword
+0x18 dword
+0x1c dword
+0x20 dword ptr_object // Zeiger auf die endgültige Speicherposition der Zeichenkette
+0x24 dword size // tatsächlich belegte Speichergröße, tatsächliche Größe der Zeichenkette + 1
Erstes Auslösen der Schwachstelle:

Zuerst wird der VM-Status in der Variablen l62 gespeichert. Danach wird für jedes Zeichen in der Variablen l63 der Verarbeitungsprozess l61 –>> l59 –>> l56 aufgerufen. l62 restore stellt den vorherigen Zustand wieder her. Dadurch wird der von l63 nach der Anweisung /l62 save def angeforderte Speicherplatz freigegeben und wird zu einem hängenden Zeiger.

Die Variablen l95-l99 bestimmen den weiteren Ablauf, ihre Werte sind alle 0 (d.h. 32-Bit):

Zweites Auslösen der Schwachstelle: Zuerst wird ein Speicherbereich der Größe 0x27 (tatsächlich belegt 0x28) angefordert, um l63 zu speichern:

Danach stellt l62 restore den vorherigen Zustand wieder her, was dazu führt, dass der für l63 angeforderte Speicher freigegeben wird und ein hängender Zeiger entsteht. Anschließend wird l100 ausgeführt. Der zuvor von l63 belegte Speicher wird nun verwendet, um die 0x28-Struktur der Zeichenkette l102 (d.h. l136) zu speichern (dies erklärt, warum l63 Speicher der Größe 0x27 anfordert):

Die Werte an den Offsets 0x4, 0x20 und 0x24 dieser Struktur werden abgerufen:

Schließlich wird der Inhalt der Zeichenkette l136 geändert (das Bild zeigt nur einen Teil der Änderungen):

Diese Änderungen sind sorgfältig konstruiert und werden beim dritten Auslösen der Schwachstelle verwendet.
Drittes Auslösen der Schwachstelle: Ein Array mit 0x37 Elementen wird angefordert. Danach wird beim Durchlauf bis zum 0x34. Element l62 restore ausgeführt:

Nach der Ausführung von restore wird die 0x30-Struktur des Arrays mit dem Inhalt der Zeichenkette l193 überschrieben:

Dadurch wird das Objekt, das beim letzten (0x36) forall-Prozess ausgeführt wird, zu der im obigen Bild gezeigten 0x30-Struktur. Und das Abrufen des 0x36. Elements führt zu der beim zweiten Auslösen sorgfältig konstruierten Zeichenkette:

Das abgerufene Array-Element ist ein Array der Größe 4, dessen erstes Element eine Zeichenkette mit Startadresse 0 und Größe 0x7FFFFFFF ist: