
Analyse der Schwachstelle
Über diese Schwachstelle: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-20698
In der ntoskrnl.exe haben wir zwei gepatchte Funktionen: sub_1406AE224 und WbAddLookupEntryEx. Die anfällige Funktion ist WbAddLookupEntryEx. Pfade zu dieser Funktion:

Um diese Funktion aufzurufen, müssen wir NtQuerySystemInformation mit dem ersten Argument 0xb9 aufrufen. Das erste Byte im Puffer, das der Benutzer als zweites Argument bereitstellt, ist die Option für diesen Switch:
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;
}
Für jeden Prozess, der NtQuerySystemInformation mit dem Argument 0xb9 verwendet, wird eine Struktur erstellt, die im Speicher gespeichert bleibt, bis der Prozess beendet wird. Ich habe diese Struktur item_instance genannt. Zuerst versucht die Funktion sub_1406AF294, die Struktur des Prozesses unter anderen solchen Strukturen zu finden. Zeiger auf diese Strukturen werden im Speicher in der Reihenfolge der Prozess-ID gespeichert. Wenn die Struktur nicht gefunden wird, wird sie von WbCreateWarbirdProcess erstellt. Anschließend wird ein Zeiger auf diese Struktur mittels der anfälligen Funktion WbAddLookupEntryEx zum Array hinzugefügt.
Der Code von WbAddLookupEntryEx vor dem Patch:
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;
}
Es allokiert das Array von Zeigern neu und fügt einen neuen Zeiger ein.
Der Patch fügt diese Sicherheitsüberprüfungen hinzu:
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;
Dies macht Additions- und Multiplikationsoperationen sicher gegen Überlauf. In der ungepatchten Version kann es also in diesen Fällen zu einem Integer-Überlauf kommen:
Nach dem Integer-Überlauf wird das Array von Zeigern neu allokiert, jedoch mit kleinerer Größe. Dann wird memmove versuchen, Elemente zu verschieben, um Platz für das neue Element zu schaffen. Dadurch werden Chunks beschädigt, die nach dem neu allokierten Chunk liegen.
Um einen Integer-Überlauf auszulösen, müssen wir 0xffffffff / size_of_item + 1 Strukturen erstellen. size_of_item ist immer 8. Also müssen wir 0x20000001 Strukturen erstellen. Jede Struktur repräsentiert einen Prozess. Wir benötigen also 0x20000001 (536870913) gleichzeitige Prozesse. Auf meiner virtuellen Maschine konnte ich nur 12492 gleichzeitige Prozesse erstellen, auf meinem Host-Rechner 39652.