
Analyse technique de CVE-2020-1206 (SMBleed) vulnérabilité de divulgation d'informations du noyau dans Windows SMBv3, y compris l'oracle de fuite de mémoire non authentifié et les techniques d'exploitation combinées avec SMBGhost pour RCE.
Dans la vulnérabilité SMBGhost (CVE-2020-0796), j'ai parlé d'une technique de primitive write-what-where via l'utilisation d'un bug d'overflow d'entier pour modifier le pointeur Alloc.Userbuffer afin qu'il pointe vers une adresse de notre choix et d'y écrire des données arbitraires. Tout comme SMB Ghost, cette vulnérabilité existe également dans la fonction Srv2DecompressData de srv2.sys. Revenons sur la fonction Srv2DecompressData liée à la vulnérabilité SMBGhost (CVE-2020-0796), qui a été simplifiée par Zecops.``` c typedef struct _COMPRESSION_TRANSFORM_HEADER { ULONG ProtocolId; ULONG OriginalCompressedSegmentSize; USHORT CompressionAlgorithm; USHORT Flags; ULONG Offset; } COMPRESSION_TRANSFORM_HEADER, *PCOMPRESSION_TRANSFORM_HEADER;
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;
}
La fonction Srv2DecompressData reçoit un message compressé envoyé par le client et alloue une zone mémoire nécessaire, puis décompresse le message dans cette zone. Ensuite, si le champ Offset est non nul, elle copie les données (RawData) précédant les données compressées au début de la zone mémoire allouée.

Le bug SMBGhost réside dans le fait que la fonction ne vérifie pas les dépassements d'entiers (integer overflow), ce qui conduit à une allocation de taille incorrecte et provoque un débordement de tampon (buffer overflow). Trois mois après que Microsoft a corrigé SMBGhost, la faille CVE-2020-1206 (SMBleed, selon la dénomination du [Zecops Blog](https://blog.zecops.com/)) a été découverte. Cette faille nous permet de fuiter l'adresse d'une autre machine et, combinée à SMBGhost, elle peut permettre d'obtenir une RCE. Pour avoir une vision plus simple de la fonction Srv2DecompressData, nous réutiliserons cette fonction telle qu'elle était avant le correctif de SMBGhost, tout en supposant qu'elle a été corrigée.
# Falsifier OriginalCompressedSegmentSize
Comme pour SMBGhost, cette fois nous allons également falsifier OriginalCompressedSegmentSize avec une valeur légèrement supérieure aux données décompressées que nous envoyons. Par exemple, si nous compressons des données d'une taille de x octets, au lieu de placer x dans le champ OriginalCompressedSegmentSize, nous y placerons x + 0x1000 ; l'image suivante sera plus claire :

Les données du noyau non initialisées seront considérées comme faisant partie du message.
Comme je l'ai dit dans l'analyse [CVE-2020-0796](https://github.com/datntsec/CVE-2020-0796), Srv2DecompressData ignore toujours la phase de vérification après la fonction SmbCompressionDecompress si la décompression réussit :``` c
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
Bien que le champ OriginalCompressedSegmentSize soit défini à x + 0x1000 au lieu de x, après une décompression réussie, la variable FinalCompressedSize ne contient pas la valeur x, mais contiendra la valeur x + 0x1000 :```c NTSTATUS SmbCompressionDecompress( USHORT CompressionAlgorithm, PUCHAR UncompressedBuffer, ULONG UncompressedBufferSize, PUCHAR CompressedBuffer, ULONG CompressedBufferSize, PULONG FinalCompressedSize) { // ...
NTSTATUS Status = RtlDecompressBufferEx2(
...,
FinalUncompressedSize,
...);
if (status >= 0) {
*FinalCompressedSize = CompressedBufferSize;
}
// ...
return Status;
}
Parce qu'après une décompression réussie, FinalCompressedSize est mis à jour pour contenir la valeur CompressedBufferSize (correspondant à OriginalCompressedSegmentSize passé à la fonction SmbCompressionDecompress). La mise à jour et la vérification ultérieures sont presque superflues, ce qui peut entraîner des erreurs inattendues.
# Exploitation de base
La structure de message utilisée par Zecops pour démontrer la vulnérabilité est le [message SMB2 WRITE](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e7046961-3318-4350-be2a-a8d69bb59ce8). Cette structure contient des champs tels que le nombre d'octets pouvant être écrits, le flag, ..., suivis d'un tampon de longueur arbitraire. C'est plutôt parfait pour exploiter la vulnérabilité, car on peut créer un message et spécifier l'en-tête, avec un tampon contenant des données non initialisées.
Basé sur le POC de [Zecops](https://blog.zecops.com/) sur le dépôt WindowsProtocolTestSuites de Microsoft, pour avoir une vision plus claire de cela, nous allons ajouter cette petite extension à la fonction de compression :``` c
// HACK: fake size
if (((Smb2SinglePacket)packet).Header.Command == Smb2Command.WRITE)
{
((Smb2WriteRequestPacket)packet).PayLoad.Length += 0x1000;
compressedPacket.Header.OriginalCompressedSegmentSize += 0x1000;
}
Lưu ý rằng POC này yêu cầu thông tin xác thực và chia sẻ quyền write, thường có sẵn trong nhiều trường hợp. Tuy nhiên, lỗi trả về sẽ áp dụng cho mọi message (bao gồm cả message có hoặc không có thông tin xác thực), nên có khả năng ta sẽ có thể khai thác mà không cần phải xác thực. Một điều nữa, là bộ nhớ mà chúng ta sẽ leak là từ các lần phân bổ trước trong NonPagedPoolNx và vì chúng ta có thể kiểm soát kích thước phân bổ, chúng ta sẽ kiểm soát được dữ liệu mà chúng ta sẽ leak ở một mức độ nào đó.
Vậy nếu không có thông tin xác thực thì có thể leak được địa chỉ kernel không? Để trả lời cho câu hỏi này, ta hãy phân tích SMB sâu hơn nữa.
Khi xác thực thông tin, client sẽ gửi các message sau:
SMB2 NEGOTIATE → SMB2 SESSION_SETUP → SMB2 SESSION_SETUP
Nếu thông tin xác thực sai, phiên kết nối sẽ bị hủy sau gói tin SMB2 SESSION_SETUP thứ 2:

Giả sử rằng ta không có thông tin xác thực, chúng ta sẽ kiểm tra xem liệu có một lệnh nào có thể gửi mà không cần phải xác thực hay không. Qua việc tìm kiếm, ta nhận thấy:
SMB2 NEGOTIATE và nó cũng là lệnh SMB2 NEGOTIATE duy nhất trong suốt một phiên.SMB2 SESSION_SETUP.Trong đó SMB2 NEGOTIATE message sẽ không được nén. Bug nằm ở hàm giải nén, vì vậy ta sẽ không xem xét nó mà chỉ xét các SMB2 SESSION_SETUP message.