
Análise da vulnerabilidade
Sobre esta vulnerabilidade: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-20698
No ntoskrnl.exe temos duas funções corrigidas: sub_1406AE224 e WbAddLookupEntryEx. A função vulnerável é WbAddLookupEntryEx. Caminhos para esta função:

Para chamar esta função precisamos chamar NtQuerySystemInformation com o primeiro argumento 0xb9. O primeiro byte no buffer, que o usuário fornece como segundo argumento, é a opção 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 processo que usa NtQuerySystemInformation com o argumento 0xb9, uma estrutura será criada e armazenada na memória até que o processo termine. Chamei esta estrutura de item_instance. Primeiro, a função sub_1406AF294 tenta encontrar a estrutura do processo entre outras estruturas semelhantes. Ponteiros para essas estruturas são armazenados na memória em ordem de ID do processo. Se a estrutura não for encontrada, ela será criada por WbCreateWarbirdProcess. Em seguida, um ponteiro para esta estrutura será adicionado ao array usando WbAddLookupEntryEx, que é vulnerável.
O código de WbAddLookupEntryEx antes da correção:
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;
}
Ele realoca o array de ponteiros e insere um novo ponteiro.
A correção adiciona estas verificações de segurança:
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;
Isto torna as operações de adição e multiplicação seguras contra estouro. Portanto, na versão não corrigida podemos obter estouro de inteiro nestes casos:
Após o estouro de inteiro, o array de ponteiros será realocado, mas com um tamanho menor. E então, memmove tentará deslocar elementos para abrir espaço para o novo elemento. Assim, corromperá os chunks que estão após o novo chunk alocado.
Para desencadear o estouro de inteiro precisamos criar 0xffffffff / size_of_item + 1 estruturas. size_of_item é sempre 8. Portanto, precisamos criar 0x20000001 estruturas. Cada estrutura representa um processo. Então precisamos ter 0x20000001 (536870913) processos concorrentes. Na minha máquina virtual só consegui criar 12492 processos concorrentes e 39652 na minha máquina host.