
Análisis técnico y prueba de concepto para CVE-2020-0796 (SMBGhost), una vulnerabilidad de desbordamiento de enteros en la compresión SMBv3 que conduce a la escalada local de privilegios en Windows 10/Server.
La función de compresión añadida a SMBv3 a partir de la versión del sistema operativo Windows 10/Server version 1903 contiene una vulnerabilidad de desbordamiento de enteros (integer overflow) confirmada por Microsoft el 12/03/2020. Permite que un atacante realice una escalada de privilegios local (LPE) y ejecución remota de código (RCE). Aquí solo se hablará de la vulnerabilidad LPE.
Versiones afectadas:
Al analizar el archivo srv2.sys, se observa que las funciones relacionadas con la descompresión se llaman de la siguiente manera:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
Primero se llama a la función `Srv2ReceiveHandler` para recibir un paquete de datos SMB y se llama a una función correspondiente al protocolo `ProtocolId`. Si `PrococolId` = 0x424D53FC, se llamará a la función `Srv2DecompressMessageAsync`, que a su vez llamará a la función `Srv2DecompressData` para descomprimir el paquete de datos. La función `Srv2DecompressData` llamará a la función `SrvNetAllocateBuffer` para asignar un `Alloc` que se usará para almacenar los datos después de la descompresión; a continuación, llamará a la función `SmbCompressionDecompress` para descomprimir el paquete de datos y, por último, llamará a la función `memcpy`. Así, todo el proceso de descompresión tendrá los siguientes pasos principales:
- 1. Asignar
- 2. Descomprimir
- 3. Copiar
Según la documentación proporcionada por Microsoft, la estructura `COMPRESSION_TRANSFORM_HEADER` se utiliza para enviar y recibir datos comprimidos desde el cliente y el servidor. Su estructura es la siguiente:``` c
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} ;
Aquí solo nos centramos en los 2 campos principales anteriores:
OriginalCompressedSegmentSize es el tamaño del segmento de datos sin comprimir, calculado en bytes.Offset es el desplazamiento en bytes entre el punto de inicio de los datos comprimidos y el punto final de la estructura _COMPRESSION_TRANSFORM_HEADER.Así, el paquete de datos comprimido tendrá la siguiente 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;
}
Al analizar la función `Srv2DecompressData`, se observa que recibe un paquete de datos comprimidos `COMPRESSION_TRANSFORM_HEADER` (Header), procede a asignar una región de memoria (Alloc) mediante la función `SrvNetAllocateBuffer` con el parámetro de la suma de `Header->OriginalCompressedSegmentSize` + `Header->Offset`, y luego descomprime los datos comprimidos y copia los datos no comprimidos en `Alloc->Buffer`.

El error de desbordamiento de enteros (integer overflow) ocurre cuando `Srv2DecompressData` llama a la función `SrvNetAllocateBuffer`; la función `SrvNetAllocateBuffer` en realidad recibe dos valores de 64 bits, pero al llamar a `SrvNetAllocateBuffer`, `Srv2DecompressData` solo le pasa dos valores de 32 bits (ULONG). Mientras tanto, tanto `OriginalCompressedSegmentSize` como `Offset` son ULONG, y al sumarlos pueden dar como resultado un número mayor de 32 bits. Por lo tanto, se produce el desbordamiento de enteros (en términos simples, al sumar 0xffffffff (`OriginalCompressedSegmentSize`) con 0x10 (`Offset`) se obtiene el valor 0xf0000000f, pero `SrvNetAllocateBuffer` solo recibe el valor 0x0000000f).

El desbordamiento de enteros conduce a una asignación incorrecta de la región de memoria Alloc (el tamaño que se debe asignar es menor que el tamaño real), lo que puede provocar un error de desbordamiento de búfer (buffer overflow):
