
Análise técnica e prova de conceito para CVE-2020-0796 (SMBGhost), uma vulnerabilidade de estouro de inteiro na compressão SMBv3 que leva a escalada de privilégio local no Windows 10/Server.
A funcionalidade de compressão adicionada ao SMBv3 a partir da versão 1903 dos sistemas operacionais Windows 10/Server contém uma vulnerabilidade de estouro de inteiro confirmada pela Microsoft em 12/03/2020. Permite que um atacante execute escalonamento de privilégio local (LPE) e execução remota de código (RCE). Aqui, abordaremos apenas a vulnerabilidade LPE.
Versões afetadas:
Análise do arquivo srv2.sys, observa-se que as funções relacionadas à descompressão são chamadas da seguinte forma:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
Primeiro, a função `Srv2ReceiveHandler` é chamada para receber um pacote de dados smb e chamar uma função correspondente ao protocolo `ProtocolId`. Se `PrococolId` = 0x424D53FC, ela chamará a função `Srv2DecompressMessageAsync`, que por sua vez chamará a função `Srv2DecompressData` para descomprimir o pacote de dados. A função `Srv2DecompressData` chamará a função `SrvNetAllocateBuffer` para alocar um `Alloc` usado para armazenar os dados após a descompressão, em seguida chamará a função `SmbCompressionDecompress` para realizar a descompressão do pacote de dados e, por fim, chamará a função `memcpy`. Assim, todo o processo de Decompress terá as seguintes etapas principais:
- 1. Allocate
- 2. Decompress
- 3. Copy
De acordo com a documentação fornecida pela Microsoft, a estrutura `COMPRESSION_TRANSFORM_HEADER` é usada para enviar e receber dados comprimidos entre cliente e servidor. Ela possui a seguinte estrutura:``` c
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} ;
Aqui nós focamos apenas nos 2 campos principais acima:
OriginalCompressedSegmentSize é o tamanho do segmento de dados descomprimidos, em bytes.Offset é o deslocamento em bytes entre o início dos dados comprimidos e o fim da estrutura _COMPRESSION_TRANSFORM_HEADER.Assim, o pacote de dados comprimidos terá a seguinte forma:
``` c
typedef struct _ALLOCATION_HEADER
{
// ...
PVOID UserBuffer;
// ...
} ALLOCATION_HEADER, *PALLOCATION_HEADER;
NTSTATUS Srv2DecompressData(PCOMPRESSION_TRANSFORM_HEADER Header, SIZE_T TotalSize) { PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer( (ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset), NULL); If (!Alloc) { return STATUS_INSUFFICIENT_RESOURCES; }
ULONG FinalCompressedSize = 0;
NTSTATUS Status = SmbCompressionDecompress(
Header->CompressionAlgorithm,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset,
(ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset),
(PUCHAR)Alloc->UserBuffer + Header->Offset,
Header->OriginalCompressedSegmentSize,
&FinalCompressedSize);
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
if (Header->Offset > 0) {
memcpy(
Alloc->UserBuffer,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
Header->Offset);
}
Srv2ReplaceReceiveBuffer(some_session_handle, Alloc);
return STATUS_SUCCESS;
}
Análise da função `Srv2DecompressData`, nota-se que a função recebe um pacote de dados comprimidos `COMPRESSION_TRANSFORM_HEADER` (Header), realiza a alocação de uma região de memória (Alloc) usando a função `SrvNetAllocateBuffer` com parâmetros sendo a soma de `Header->OriginalCompressedSegmentSize` + `Header->Offset`, em seguida descomprime os dados comprimidos e copia os dados não comprimidos para `Alloc->Buffer`.

O erro de integer overflow ocorre quando `Srv2DecompressData` chama a função `SrvNetAllocateBuffer`. A função `SrvNetAllocateBuffer` na verdade recebe dois valores de 64 bits, mas ao chamar `SrvNetAllocateBuffer`, `Srv2DecompressData` apenas passa para ela dois valores de 32 bits (ULONG). Enquanto isso, tanto `OriginalCompressedSegmentSize` quanto `Offset` são ULONG, e ao somá-los, pode resultar em um número maior que 32 bits. Por isso, ocorre o erro de integer overflow (entendido de forma simples: ao somar 0xffffffff (`OriginalCompressedSegmentSize`) com 0x10 (`Offset`), resulta no valor 0xf0000000f, mas a função `SrvNetAllocateBuffer` recebe apenas o valor 0x0000000f).

O erro de integer overflow levará a uma alocação incorreta da região de memória Alloc (o tamanho necessário para alocação é menor que o tamanho real), podendo causar um erro de buffer overflow:
