
Analyse de la vulnérabilité
À propos de cette vulnérabilité : https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-20698
Dans ntoskrnl.exe, nous avons deux fonctions corrigées : sub_1406AE224 et WbAddLookupEntryEx. La fonction vulnérable est WbAddLookupEntryEx. Chemins vers cette fonction :

Pour appeler cette fonction, nous devons appeler NtQuerySystemInformation avec le premier argument 0xb9. Le premier octet du tampon fourni par l'utilisateur comme deuxième argument est une option pour ce commutateur :
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;
}
Pour chaque processus qui utilise NtQuerySystemInformation avec l'argument 0xb9, une structure sera créée et stockée en mémoire jusqu'à la fin du processus. J'ai appelé cette structure item_instance. D'abord, la fonction sub_1406AF294 essaie de trouver la structure du processus parmi d'autres structures similaires. Les pointeurs vers ces structures sont stockés en mémoire dans l'ordre des ID de processus. Si la structure n'est pas trouvée, elle sera créée par WbCreateWarbirdProcess. Ensuite, un pointeur vers cette structure sera ajouté au tableau en utilisant WbAddLookupEntryEx qui est vulnérable.
Le code de WbAddLookupEntryEx avant le correctif :
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;
}
Il réalloue le tableau de pointeurs et insère un nouveau pointeur.
Le correctif ajoute ces vérifications de sécurité :
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;
Cela sécurise les opérations d'addition et de multiplication contre les débordements. Ainsi, dans la version non corrigée, nous pouvons obtenir un débordement d'entier dans ces cas :
Après le débordement d'entier, le tableau de pointeurs sera réalloué, mais avec une taille plus petite. Ensuite, memmove tentera de décaler les éléments pour faire de la place au nouvel élément. Ainsi, il corrompra les blocs situés après le nouveau bloc alloué.
Pour déclencher un débordement d'entier, nous devons créer 0xffffffff / size_of_item + 1 structures. size_of_item est toujours 8. Donc, nous devons créer 0x20000001 structures. Chaque structure représente un processus. Nous devons donc avoir 0x20000001 (536870913) processus concurrents. Sur ma machine virtuelle, je n'ai pu créer que 12492 processus concurrents et 39652 sur ma machine hôte.