
Анализ уязвимости
Об этой уязвимости: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-20698
В ntoskrnl.exe есть две исправленные функции: sub_1406AE224 и WbAddLookupEntryEx. Уязвимая функция — WbAddLookupEntryEx. Пути к этой функции:

Чтобы вызвать эту функцию, нужно вызвать NtQuerySystemInformation с первым аргументом 0xb9. Первый байт в буфере, который пользователь передает вторым аргументом, является опцией для этого переключателя:
switch ( first_byte_in_usermod_buf )
{
case 1:
status_1 = WbDecryptEncryptionSegment(item, copy_of_usermod_buf, NumberOfBytes_1);
goto set_status_and_return;
case 2:
status_1 = WbReEncryptEncryptionSegment(item, copy_of_usermod_buf, NumberOfBytes_1);
goto set_status_and_return;
case 3:
status_1 = WbHeapExecuteCall(item, copy_of_usermod_buf, usermod_buffer, NumberOfBytes_1);
goto set_status_and_return;
case 4:
if ( !copy_of_usermod_buf )
{
status_1 = patched_case4_no_user_buffer(item, another_item);
set_status_and_return:
status = status_1;
goto return;
}
break;
case 5:
case 6:
status = usermod_buffer != 0i64 ? STATUS_NOT_IMPLEMENTED : STATUS_INVALID_PARAMETER;
goto return;
case 7:
status_1 = WbRemoveWarbirdProcess(item->proc_handle);
goto set_status_and_return;
case 8:
status_1 = WbProcessStartup(item, copy_of_usermod_buf, NumberOfBytes_1);
goto set_status_and_return;
case 9:
status_1 = WbProcessModuleUnload(item, copy_of_usermod_buf, NumberOfBytes_1);
goto set_status_and_return;
}
Для каждого процесса, который использует NtQuerySystemInformation с аргументом 0xb9, будет создана структура, которая будет храниться в памяти до завершения процесса. Я назвал эту структуру item_instance. Сначала функция sub_1406AF294 пытается найти структуру процесса среди других подобных структур. Указатели на эти структуры хранятся в памяти в порядке идентификаторов процессов. Если структура не найдена, она создается функцией WbCreateWarbirdProcess. Затем указатель на эту структуру добавляется в массив с помощью уязвимой функции WbAddLookupEntryEx.
Код функции WbAddLookupEntryEx до патча:
NTSTATUS WbAddLookupEntryEx(
items_info *items_info,
__int64 new_item_pointer,
__int64 not_used,
unsigned int new_item_index)
{
unsigned int last_item_index;
NTSTATUS status;
unsigned int old_items_count;
unsigned int size_of_item;
last_item_index = items_info->last_item_index;
status = 0;
old_items_count = items_info->old_items_count;
if ( last_item_index + 1 >= old_items_count )
{
status = WbReAlloc(
items_info->vuln_buffer,
old_items_count * items_info->size_of_item,
items_info->size_of_item * (old_items_count + items_info->count_of_new_items),
&items_info->vuln_buffer);
if ( status < 0 )
return status;
items_info->old_items_count += items_info->count_of_new_items;
old_items_count = items_info->old_items_count;
last_item_index = items_info->last_item_index;
}
if ( new_item_index > last_item_index || !old_items_count )
return STATUS_INVALID_PARAMETER;
memmove(
(char *)items_info->vuln_buffer + (new_item_index + 1) * items_info->size_of_item,
(char *)items_info->vuln_buffer + new_item_index * items_info->size_of_item,
items_info->size_of_item * (last_item_index - new_item_index));
size_of_item = items_info->size_of_item;
++items_info->last_item_index;
*(_QWORD *)((char *)items_info->vuln_buffer + new_item_index * size_of_item) = new_item_pointer;
return status;
}
Он перераспределяет массив указателей и вставляет новый указатель.
Патч добавляет следующие проверки безопасности:
status = RtlULongMult(old_items_count, items_info->size_of_item, &size_of_old_items);
if ( status < 0 )
return status;
status = RtlULongAdd(old_items_count, items_info->count_of_new_items, &sum_result);
if ( status < 0 )
return status;
status = RtlULongMult(sum_result, mb_item_size, result_size_8byte);
if ( status < 0 )
return status;
Это делает операции сложения и умножения безопасными от переполнения. Таким образом, в неисправленной версии можно получить целочисленное переполнение в следующих случаях:
После целочисленного переполнения массив указателей будет перераспределен, но с меньшим размером. Затем memmove попытается сдвинуть элементы, чтобы освободить место для нового элемента. Таким образом, он повредит блоки, расположенные после нового выделенного блока.
Чтобы вызвать целочисленное переполнение, нам нужно создать 0xffffffff / size_of_item + 1 структур. size_of_item всегда равен 8. Таким образом, нам нужно создать 0x20000001 структур. Каждая структура представляет процесс. Следовательно, нам нужно иметь 0x20000001 (536870913) одновременных процессов. На моей виртуальной машине я смог создать только 12492 одновременных процессов, а на хост-машине — 39652.