Погружение в VBScript

Анализ эксплуатации CVE-2018-8174

В конце апреля 2018 года мы описали новую уязвимость нулевого дня CVE-2018-8174 для Internet Explorer, найденную благодаря нашей «песочнице». Уязвимость использует известную из эксплойта Proof of Concept для CVE-2014-6332 технику, суть которой в «повреждении» двух объектов памяти и изменении типа одного объекта на Array для возможности чтения и записи адресного пространства, а другого объекта — на Integer для получения адреса произвольного объекта.

В то время как эксплойт для CVE-2014-6332 был нацелен на эксплуатацию целочисленного переполнения, которое позволяло производить запись в произвольные участки памяти, меня заинтересовало, как эта техника была адаптирована для использования уязвимости Use-After-Free. Для ответа на этот вопрос рассмотрим внутреннее устройство VBScript-интерпретатора.

Недокументированная платформа

Отладка исполняемого скрипта VBScript — сложная задача. Перед исполнением он компилируется в p-code, который интерпретируется виртуальной машиной. В открытых источниках отсутствует какая-либо информация о внутреннем устройстве этой виртуальной машины и ее инструкциях. Тем не менее, мне удалось найти две веб-страницы с сообщениями инженеров Microsoft, датированные 1999 и 2004 годами и дающие некоторое представление о p-code. Этой информации оказалось достаточно для того, чтобы полностью разобрать все инструкции VM и написать дизассемблер! Созданные в ходе исследования скрипты для дизассемблирования VBScript p-code в памяти отладчиков IDA Pro и WinDBG доступны в нашем репозитории GitHub.

Способность понять интерпретируемый код дает нам возможность очень точно следить за исполнением скрипта: мы владеем полной информацией о том, где код исполняется в данный момент, и можем наблюдать все объекты, которые он создает и использует. Все это значительно помогает в анализе.

Лучшее место для запуска дизассемблирующего скрипта — функция CScriptRuntime::RunNoEH, ответственная непосредственно за интерпретирование p-code.

Важные поля класса CScriptRuntime

Класс CScriptRuntime содержит всю информацию о состоянии интерпретатора: локальные переменные, аргументы функции, указатель на вершину стека и текущую инструкцию, а также адрес скомпилированного скрипта.

Виртуальная машина VBScript стек-ориентирована и состоит из чуть более 100 инструкций.

Все переменные (локальные аргументы и на стеке) представлены в виде структуры VARIANT, занимающей 16 байт, верхнее слово которой обозначает тип данных. Часть значений типов представлена на соответствующей странице в MSDN.

Эксплуатация CVE-2018-8174

Ниже представлен код и дизассемблированный p-code класса Class1:

Функция 34 — это конструктор класса Class1.

Инструкция OP_CreateClass вызовет функцию VBScriptClass::Create для создания объекта VBScriptClass.

Инструкции OP_FnBindEx и OP_CreateVar попытаются получить переменные, переданные в аргументах. Так как они еще не существуют, функция VBScriptClass::CreateVar создаст их.

Эта диаграмма показывает, как переменные могут быть получены из объекта VBScriptClass. Значение переменной хранится в структуре VVAL:

Для понимания эксплуатации важно знать, как переменные представлены в структуре VBScriptClass.

Когда инструкция OP_NamedSt ‘mem’ выполняется в функции 36 (SetProp), она вызовет Default Property Getter экземпляра класса, который перед этим был сохранен на стеке, и затем сохранит возвращенное значение в переменную mem.

***BOS(8292,8301)*** mem=Value *****
0000OP_Bos1 0
0002OP_LocalAdr -1 <-------- сохраняет аргумент на стеке
0005OP_NamedSt ‘mem’ <-------- если это диспетчер класса с Default Property Getter вызвать и сохранить возвращенное значение в 'mem'

Ниже код и дизассемблированный p-code функции 30 (p), которая будет вызвана во время исполнения инструкции OP_NamedSt:

Первый базовый блок этой функции:

***BOS(8626,8656)*** P=CDbl(«174088534690791e-324») *****
0000OP_Bos1 0
0002OP_StrConst ‘174088534690791e-324’
0007OP_CallNmdAdr’CDbl’ 1
000EOP_LocalSt 0

Этот блок конвертирует строку 174088534690791e-324 в VARIANT и сохраняет в локальную переменную 0, зарезервированную для возвращаемого значения функции.

VARIANT, полученный после конвертирования 174088534690791e-324 в double

После установки возвращаемого значения, но перед непосредственно возвратом эта функция выполняет:

For IIIl=0 To 6
IIIlI(IIIl)=0
Next

Это вызывает сборщик мусора для экземпляра Class1 и приводит к использованию висячего указателя из-за Use-After-Free уязвимости в Class_Terminate(), о которой мы писали ранее.

В строке

***BOS(8855,8874)*** Set llII=New Class2 *****
0047OP_Bos1 4
0049OP_InitClass ‘Class2’
004EOP_LocalSet 1

инструкция OP_InitClass ‘Class2’ создает экземпляр «злого близнеца» класса Class1 на месте предварительно освобожденного VBScriptClass, который все еще используется инструкцией OP_NamedSt ‘mem’ в функции 36 (SetProp).

Класс Class2 — «злой близнец» класса Class1:

Расположение переменных в памяти предсказуемо. Размер данных, занимаемых структурой VVAL, рассчитывается по формуле 0x32 + длина имени переменной в UTF-16.

Ниже представлена диаграмма, которая показывает расположение переменных Class1 относительно переменных Class2, когда Class2 выделен на месте освобожденного Class1.

В конце исполнения инструкции OP_NamedSt ‘mem’ в функции 36 (SetProp) значение, возвращенное функцией 30 (p), будет записано по висячему указателю VVAL ‘mem’ в Class1, переписав тип VARIANT у VVAL ‘mem’ в Class2.

VARIANT с типом Double переписывает тип VARIANT с String на Array

Таким образом, объект с типом String превращается в объект с типом Array, и данные, которые раньше считались строкой, третируются как контрольная структура Array, позволяющая получить доступ ко всему адресному пространству процесса.

Заключение

Наши скрипты для дизассемблирования VBScript, скомпилированного p-code, дают возможность осуществлять отладку VBScript на уровне байт-кода, что значительно помогает в анализе эксплойтов и понимании принципов работы VBScript. Они доступны в нашем публичном репозитории Github.

Случай с CVE-2018-8174 демонстрирует, что когда выделения памяти очень предсказуемы, эксплуатация Use-After-Free уязвимостей не составляет труда. Эксплойт, найденный в «дикой природе», нацелен на более старые версии Windows. Для Windows 7 и Windows 8.1 необходимое для его эксплуатации расположение объектов в памяти наиболее вероятно.

Продукты «Лаборатории Касперского» блокируют компонентом Automatic Exploit Protection (AEP) все стадии эксплойта со следующими вердиктами:

  • HEUR:Exploit.MSOffice.Generic
  • HEUR:Exploit.Script.CVE-2018-8174.a
  • HEUR:Exploit.Script.Generic
  • HEUR:Trojan.Win32.Generic
  • PDM:Exploit.Win32.Generic

Публикации на схожие темы

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *