
Analyse technique et preuve de concept pour CVE-2020-0796 (SMBGhost), une vulnérabilité de dépassement d'entier dans la compression SMBv3 menant à une élévation de privilèges locale sur Windows 10/Server.
La fonctionnalité de compression ajoutée à SMBv3 depuis la version 1903 du système d'exploitation Windows 10/Server contient une vulnérabilité de débordement d'entier confirmée par Microsoft le 12/03/2020. Elle permet à un attaquant d'effectuer une élévation de privilèges locale (LPE) et une exécution de code à distance (RCE). Ici, nous ne parlerons que de la vulnérabilité LPE.
Versions affectées:
L'analyse du fichier srv2.sys montre que les fonctions liées à la décompression sont appelées comme suit:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
Premièrement, la fonction `Srv2ReceiveHandler` est appelée pour recevoir un paquet de données SMB et appeler une fonction correspondant au protocole `ProtocolId`. Si `PrococolId` = 0x424D53FC, elle appelle la fonction `Srv2DecompressMessageAsync`, qui appelle ensuite la fonction `Srv2DecompressData` pour décompresser le paquet de données. La fonction `Srv2DecompressData` appelle la fonction `SrvNetAllocateBuffer` pour allouer un `Alloc` destiné à stocker les données après décompression, puis elle appelle la fonction `SmbCompressionDecompress` pour décompresser le paquet de données, et enfin elle appelle la fonction `memcpy`. Ainsi, l'ensemble du processus de décompression comporte les étapes principales suivantes :
- 1. Allocate
- 2. Decompress
- 3. Copy
Selon la documentation fournie par Microsoft, la structure `COMPRESSION_TRANSFORM_HEADER` est utilisée pour envoyer et recevoir des données compressées entre le client et le serveur. Elle a la structure suivante :``` c
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} ;
Ici, nous nous concentrons uniquement sur les 2 champs principaux ci-dessus :
OriginalCompressedSegmentSize est la taille du segment de données non compressé, en octets.Offset est le décalage, en octets, entre le point de départ des données compressées et le point de fin de la structure _COMPRESSION_TRANSFORM_HEADER.Ainsi, le paquet de données compressé se présente comme suit :
``` 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;
}
Analyse de la fonction `Srv2DecompressData` : on constate que la fonction reçoit un paquet de données compressées `COMPRESSION_TRANSFORM_HEADER` (Header), puis alloue une zone mémoire (Alloc) via la fonction `SrvNetAllocateBuffer` avec comme paramètre la somme `Header->OriginalCompressedSegmentSize` + `Header->Offset`, puis décompresse les données compressées et copie les données non compressées dans `Alloc->Buffer`.

L'erreur d'integer overflow se produit lorsque `Srv2DecompressData` appelle la fonction `SrvNetAllocateBuffer` ; cette dernière reçoit en réalité deux valeurs 64 bits, mais lors de l'appel à `SrvNetAllocateBuffer`, `Srv2DecompressData` ne lui transmet que deux valeurs 32 bits (ULONG). Or `OriginalCompressedSegmentSize` et `Offset` sont tous deux des ULONG ; lorsque on les additionne, le résultat peut dépasser 32 bits. C'est ainsi que se produit l'erreur d'integer overflow (pour faire simple, additionner 0xffffffff (`OriginalCompressedSegmentSize`) avec 0x10 (`Offset`) donne 0xf0000000f, mais la fonction `SrvNetAllocateBuffer` ne reçoit que la valeur 0x0000000f).

L'erreur d'integer overflow conduit à une allocation incorrecte de la zone mémoire Alloc (la taille à allouer est inférieure à la taille réelle), ce qui peut provoquer une erreur de buffer overflow :
