Уязвимость нулевого дня в диспетчере окон рабочего стола (CVE-2021-28310)

Анализируя эксплойт к CVE-2021-1732, первоначально обнаруженный центром аналитики угроз DBAPPSecurity и используемый APT-группой BITTER, мы обнаружили еще один эксплойт нулевого дня, который, как мы полагаем, связан с этой же группой. Мы сообщили компании Microsoft о новом эксплойте в феврале. После того как подтвердилось, что эксплойт действительно использует уязвимость нулевого дня, ей присвоили обозначение CVE-2021-28310. Microsoft выпустила исправление для этой уязвимости в составе апрельских обновлений безопасности.

По нашей информации, эксплойт CVE-2021-28310 уже применяют злоумышленники — возможно, несколько групп. Он предназначен для эскалации привилегий (EoP) и, вероятно, используется совместно с другими браузерными эксплойтами в целях «побега из песочницы» или получения системных привилегий для дальнейшего доступа к ресурсам. К сожалению, нам не удалось зафиксировать полную цепочку заражения, поэтому мы не знаем, используется ли эксплойт с другими браузерными уязвимостями нулевого дня или с известными уязвимостями, для которых уже выпущены исправления.

Первоначально эксплойт был выявлен нашей передовой технологией защиты от эксплойтов, и связанными с ней детектирующими логиками. Стоит отметить, что за последние несколько лет мы внедрили в наши продукты множество технологий защиты от эксплойтов. С их помощью удалось обнаружить несколько уязвимостей нулевого дня, что раз за разом подтверждает эффективность этих разработок. Мы будем и дальше укреплять защиту наших пользователей, делая интернет безопаснее для всех. Для этого мы продолжим совершенствовать свои технологии и сотрудничать со сторонними разработчиками для исправления уязвимостей. В этой статье мы приводим технический анализ уязвимости CVE-2021-28310 и описываем способ ее эксплуатации злоумышленниками. Дополнительная информация об APT-группе BITTER и индикаторах компрометации доступна клиентам сервиса Kaspersky Intelligence Reporting. Чтобы ее получить, обращайтесь по адресу intelreports@kaspersky.com.

Технические подробности​

Уязвимость CVE-2021-28310 связана с библиотекой dwmcore.dll, входящей в диспетчер окон рабочего стола (dwm.exe). Уязвимость относится к типу out-of-bounds (OOB) write, то есть дает возможность записывать данные за границами предполагаемого буфера. Ввиду отсутствия контроля границ злоумышленники могут создать ситуацию, позволяющую им записывать нужные данные по нужному смещению посредством API-интерфейса DirectComposition. DirectComposition — это компонент Windows, который впервые появился в Windows 8 и служит для создания растровых композиций с преобразованием изображений, спецэффектами и анимацией. Он поддерживает работу с растровыми изображениями из различных источников (GDI, DirectX и т. д.). В нашем блоге мы уже публиковали заметку об обнаруженных в реальной среде эксплойтах нулевого дня, связанных со злонамеренным использованием этого API DirectComposition. Этот интерфейс реализуется драйвером win32kbase.sys. Имена всех связанных системных вызовов начинаются со строки NtDComposition.



Системные вызовы DirectComposition в драйвере win32kbase.sys

Для применения эксплойта требуется только три системных вызова: NtDCompositionCreateChannel, NtDCompositionProcessChannelBatchBuffer и NtDCompositionCommitChannel. Системный вызов NtDCompositionCreateChannel инициирует канал, который может использоваться совместно с системным вызовом NtDCompositionProcessChannelBatchBuffer с целью отправки по несколько команд DirectComposition за раз для их обработки ядром в пакетном режиме. Чтобы этот механизм работал, команды должны быть последовательно записаны в специальный буфер, выделенный системным вызовом NtDCompositionCreateChannel. Каждая команда имеет собственный формат с переменной длиной и списком параметров.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum DCOMPOSITION_COMMAND_ID
{
ProcessCommandBufferIterator,
CreateResource,
OpenSharedResource,
ReleaseResource,
GetAnimationTime,
CapturePointer,
OpenSharedResourceHandle,
SetResourceCallbackId,
SetResourceIntegerProperty,
SetResourceFloatProperty,
SetResourceHandleProperty,
SetResourceHandleArrayProperty,
SetResourceBufferProperty,
SetResourceReferenceProperty,
SetResourceReferenceArrayProperty,
SetResourceAnimationProperty,
SetResourceDeletedNotificationTag,
AddVisualChild,
RedirectMouseToHwnd,
SetVisualInputSink,
RemoveVisualChild
};


Список идентификаторов команд, поддерживаемых функцией DirectComposition::CApplicationChannel::processCommandBufferIterator

Хотя эти команды обрабатываются ядром, они также сериализуются в другой формат и передаются через протокол локального вызова процедур (LPC) в процесс диспетчера окон рабочего стола (dwm.exe) для рендеринга данных на экран. Эта процедура может быть инициирована третьим системным вызовом — NtDCompositionCommitChannel.

Для реализации уязвимости обнаруженный эксплойт использует три типа команд: CreateResource, ReleaseResource и SetResourceBufferProperty.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void CreateResourceCmd(int resourceId)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = CreateResource;
buf[1] = resourceId;
buf[2] = PropertySet; // MIL_RESOURCE_TYPE
buf[3] = FALSE;
BatchLength += 16;
}

void ReleaseResourceCmd(int resourceId)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = ReleaseResource;
buf[1] = resourceId;
BatchLength += 8;
}

void SetPropertyCmd(int resourceId, bool update, int propertyId, int storageOffset, int hidword, int lodword)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = SetResourceBufferProperty;
buf[1] = resourceId;
buf[2] = update;
buf[3] = 20;
buf[4] = propertyId;
buf[5] = storageOffset;
buf[6] = _D2DVector2; // DCOMPOSITION_EXPRESSION_TYPE
buf[7] = hidword;
buf[8] = lodword;
BatchLength += 36;
}


Формат команд, используемых в эксплойте

Давайте взглянем на функцию CPropertySet::processSetPropertyValue в dwmcore.dll. Эта функция отвечает за обработку команды SetResourceBufferProperty. Нас больше всего интересует код, отвечающий за обработку типа выражения D2DVector2 (DCOMPOSITION_EXPRESSION_TYPE = D2DVector2).


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
int CPropertySet::processSetPropertyValue(CPropertySet *this, ...)
{
...

if (expression_type == _D2DVector2)
{
if (!update)
{
CPropertySet::AddProperty<D2DVector2>(this, propertyId, storageOffset, _D2DVector2, value);
}
else
{
if ( storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF )
{
goto fail;
}

CPropertySet::UpdateProperty<D2DVector2>(this, propertyId, _D2DVector2, value);
}
}

...
}

int CPropertySet::AddProperty<D2DVector2>(CResource *this, unsigned int propertyId, int storageOffset, int type, _QWORD *value)
{
int propertyIdAdded;

int result = PropertySetStorage<DynArrayNoZero,PropertySetUserModeAllocator>::AddProperty<D2DVector2>(
this->propertiesData,
type,
value,
&propertyIdAdded);
if ( result < 0 )
{
return result;
}

if ( propertyId != propertyIdAdded || storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF )
{
return 0x88980403;
}

result = CPropertySet::propertyUpdated<D2DMatrix>(this, propertyId);
if ( result < 0 )
{
return result;
}

return 0;
}

int CPropertySet::UpdateProperty<D2DVector2>(CResource *this, unsigned int propertyId, int type, _QWORD *value)
{
if ( this->properties[propertyId]->type == type )
{
*(_QWORD *)(this->propertiesData + (this->properties[propertyId]->offset & 0x1FFFFFFF)) = *value;

int result = CPropertySet::propertyUpdated<D2DMatrix>(this, propertyId);
if ( result < 0 )
{
return result;
}

return 0;
}
else
{
return 0x80070057;
}
}


Обработка команды SetResourceBufferProperty (D2DVector2) в dwmcore.dll

Если в качестве типа выражения задано D2DVector2, то для команды SetResourceBufferProperty функция CPropertySet::processSetPropertyValue(…) вызовет либо CPropertySet::AddProperty<D2DVector2>(…), либо CPropertySet::UpdateProperty<D2DVector2>(…) в зависимости от того, установлен ли в команде флаг update. Первое, что бросается в глаза — это способ добавления нового свойства, реализованный в функции CPropertySet::AddProperty<D2DVector2>(…). Можно заметить, что функция добавляет новое свойство к ресурсу и лишь после этого проверяет, равны ли значения propertyId и storageOffset нового свойства предоставленным значениям (и возвращает ошибку, если это не так). Проверка чего-либо после выполнения операции — признак плохого кода, она чревата уязвимостями. Однако гораздо более серьезную проблему можно обнаружить в функции CPropertySet::UpdateProperty<D2DVector2>(…). Здесь не выполняется проверка, которая гарантировала бы, что предоставленный идентификатор свойства (propertyId) меньше количества свойств, добавленных к ресурсу. В результате злоумышленник сможет использовать эту функцию для выполнения OOB write за границами буфера propertiesData, если удастся обойти две дополнительные проверки данных внутри массива properties.


1
2
(1) storageOffset == this->properties[propertyId]->offset & 0x1FFFFFFF
(2) this->properties[propertyId]->type == type


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

Злоумышленник сможет обойти эти проверки, если найдет способ выделять и освобождать объекты в процессе dwm.exe таким образом, чтобы приводить кучу в желаемое состояние и заполнять память поддельными свойствами по определенным адресам, применяя технику heap spraying. Обнаруженный эксплойт справляется с этой задачей при помощи команд CreateResource, ReleaseResource и SetResourceBufferProperty.

На момент написания статьи мы еще не проанализировали обновленные бинарные файлы, исправляющие эту уязвимость, но чтобы исключить возможность возникновения других ее вариантов, разработчикам Microsoft следует проверять количество свойств и для других типов выражений.

Даже если принять во внимание вышеперечисленные проблемы в dwmcore.dll и предположить, что достигнуто желаемое состояние памяти для обхода ранее упомянутых проверок и выпущен пакет команд для реализации уязвимости, фактически она все же не будет реализована, потому что есть еще одно препятствие.

Как упоминалось выше, команды сначала обрабатываются ядром и только после этого отправляются в диспетчер окон рабочего стола (dwm.exe). Это означает, что если попытаться отправить команду с недопустимым propertyId, системный вызов NtDCompositionProcessChannelBatchBuffer вернет ошибку, и команда не будет передана процессу dwm.exe. Когда в качестве типа выражения задано D2DVector2, команды SetResourceBufferProperty обрабатываются в драйвере win32kbase.sys с помощью функций DirectComposition::CPropertySetMarshaler::AddProperty<D2DVector2>(…) и DirectComposition::CPropertySetMarshaler::UpdateProperty<D2DVector2>(…), которые очень похожи на те, что присутствуют в dwmcore.dll (вполне вероятно, что они были скопированы). Однако версия функции UpdateProperty<D2DVector2>, предназначенная для работы в режиме ядра, имеет одно заметное отличие: она проверяет количество свойств, добавленных к ресурсу.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int DirectComposition::CPropertySetMarshaler::UpdateProperty<D2DVector2>(DirectComposition::CPropertySetMarshaler *this, unsigned int *commandParams, _QWORD *value)
{
unsigned int propertyId = commandParams[0];
unsigned int storageOffset = commandParams[1];
unsigned int type = commandParams[2];

if ( propertyId >= this->propertiesCount
|| storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF)
|| type != this->properties[propertyId]->type )
{
return 0xC000000D;
}
else
{
*(_QWORD *)(this->propertiesData + (this->properties[propertyId]->offset & 0x1FFFFFFF)) = *value;
...
}
return 0;
}


DirectComposition::CPropertySetMarshaler::UpdateProperty<D2DVector2>(…) в win32kbase.sys

Версия функции UpdateProperty<D2DVector2>, работающая в режиме ядра, выполняет проверку propertiesCount и тем самым предотвращает дальнейшую обработку вредоносной команды своим аналогом, работающим в пользовательском режиме. Таким образом, уязвимость частично устраняется, однако здесь в игру вступает DirectComposition::CPropertySetMarshaler::AddProperty<D2DVector2>(…). Предназначенная для режима ядра версия функции AddProperty<D2DVector2> работает точно так же, как ее вариант для пользовательского режима, и проверяет свойства тем же способом, то есть лишь после добавления, и если propertyId и storageOffset созданного свойства не соответствуют предоставленным значениям, возвращается ошибка. Вследствие этого можно добавить новое свойство посредством функции AddProperty<D2DVector2> и вынудить функцию возвратить ошибку, вызвав несоответствие между количеством свойств, назначенных одному и тому же ресурсу в режиме ядра и в пользовательском режиме. Таким образом можно обойти проверку propertiesCount в ядре и передать вредоносные команды в диспетчер окон рабочего стола (dwm.exe).

Несоответствие между количеством свойств, назначенных одному и тому же ресурсу в режиме ядра и в пользовательском режиме, может быть источником других уязвимостей. Поэтому мы рекомендуем компании Microsoft изменить поведение функции AddProperty так, чтобы свойства проверялись перед их добавлением.

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

  • Создается большое количество ресурсов со свойствами определенного размера, чтобы привести кучу в предсказуемое состояние.
  • Создаются дополнительные ресурсы со свойствами, имеющими определенный размер и содержимое, для того чтобы заполнить память поддельными свойствами по определенным адресам посредством техники heap spraying.
  • Ресурсы, созданные на этапе 2, освобождаются.
  • Создаются дополнительные ресурсы со свойствами. Эти ресурсы будут использоваться для выполнения операций OOB write.
  • Создаются промежутки между ресурсами, созданными на этапе 1.
  • Создаются дополнительные свойства для ресурсов, созданных на этапе 4. Предполагается, что буферы для них будут выделены по определенным адресам.
  • Создаются «специальные» свойства, чтобы для ресурсов, созданных на этапе 4, вызвать несоответствие между количеством свойств, назначенных одному и тому же ресурсу в режиме ядра и пользовательском режиме.
  • Используется уязвимость OOB write для записи шелл-кода, создания объекта и выполнения кода.
  • Внедряется дополнительный шелл-код в другой системный процесс.
Продукты «Лаборатории Касперского» обнаруживают этот эксплойт со следующими вердиктами:

  • HEUR:Exploit.Win32.Generic
  • HEUR:Trojan.Win32.Generic
  • PDM:Exploit.Win32.Generic
 
Сверху Снизу