
Análisis de la vulnerabilidad
Acerca de esta vulnerabilidad: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-20698
En ntoskrnl.exe tenemos dos funciones parcheadas: sub_1406AE224 y WbAddLookupEntryEx. La función vulnerable es WbAddLookupEntryEx. Rutas a esta función:

Para llamar a esta función necesitamos llamar a NtQuerySystemInformation con el primer argumento 0xb9. El primer byte en el búfer, que el usuario proporciona como segundo argumento, es la opción para este 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;
}
Para cada proceso que usa NtQuerySystemInformation con el argumento 0xb9, se creará una estructura que se almacenará en la memoria hasta que el proceso termine. Llamé a esta estructura item_instance. Primero, la función sub_1406AF294 intenta encontrar la estructura del proceso entre otras estructuras similares. Los punteros a estas estructuras se almacenan en la memoria en orden de ID de proceso. Si no se encuentra la estructura, se creará mediante WbCreateWarbirdProcess. A continuación, se añadirá un puntero a esta estructura al array usando WbAddLookupEntryEx, que es vulnerable.
El código de WbAddLookupEntryEx antes del parche:
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;
}
Reasigna el array de punteros e inserta un nuevo puntero.
El parche añade estas comprobaciones de seguridad:
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;
Esto hace que las operaciones de suma y multiplicación sean seguras contra desbordamiento. Por lo tanto, en la versión sin parche podemos obtener desbordamiento de enteros en estos casos:
Después del desbordamiento de enteros, el array de punteros se reasignará, pero con un tamaño menor. Y luego, memmove intentará desplazar elementos para hacer espacio para el nuevo elemento. Por lo tanto, corromperá los fragmentos que se encuentran después del nuevo fragmento asignado.
Para desencadenar el desbordamiento de enteros necesitamos crear 0xffffffff / size_of_item + 1 estructuras. size_of_item siempre es 8. Por lo tanto, necesitamos crear 0x20000001 estructuras. Cada estructura representa un proceso. Así que necesitamos tener 0x20000001 (536870913) procesos concurrentes. En mi máquina virtual solo pude crear 12492 procesos concurrentes y 39652 en mi máquina anfitriona.