
Technische Analyse und Proof-of-Concept für CVE-2020-0796 (SMBGhost), eine Integer-Überlauf-Schwachstelle in der SMBv3-Kompression, die zu lokaler Privilegienausweitung unter Windows 10/Server führt.
Die Komprimierungsfunktion, die SMBv3 ab der Windows 10/Server-Version 1903 hinzugefügt wurde, enthält eine Integer-Überlauf-Schwachstelle, die von Microsoft am 12.03.2020 bestätigt wurde. Sie ermöglicht es einem Angreifer, eine lokale Rechteausweitung (LPE) und Remote-Code-Ausführung (RCE) durchzuführen. Hier wird nur die LPE-Schwachstelle behandelt.
Betroffene Versionen:
Analyse der Datei srv2.sys: Es wird festgestellt, dass die folgenden Funktionen im Zusammenhang mit der Dekomprimierung aufgerufen werden:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
Zuerst wird die Funktion `Srv2ReceiveHandler` aufgerufen, um ein SMB-Datenpaket zu empfangen und eine Funktion entsprechend dem Protokoll `ProtocolId` aufzurufen. Wenn `PrococolId` = 0x424D53FC, ruft es die Funktion `Srv2DecompressMessageAsync` auf, die dann die Funktion `Srv2DecompressData` aufruft, um das Datenpaket zu dekomprimieren. Die Funktion `Srv2DecompressData` ruft die Funktion `SrvNetAllocateBuffer` auf, um einen `Alloc` zuzuweisen, der zum Speichern der dekomprimierten Daten dient, danach ruft sie die Funktion `SmbCompressionDecompress` auf, um das Datenpaket zu dekomprimieren, und schließlich wird die Funktion `memcpy` aufgerufen. Somit besteht der gesamte Dekomprimierungsprozess aus den folgenden Hauptschritten:
- 1. Allocate
- 2. Decompress
- 3. Copy
Laut der von Microsoft bereitgestellten Dokumentation wird die Struktur `COMPRESSION_TRANSFORM_HEADER` verwendet, um komprimierte Daten zwischen Client und Server zu senden und zu empfangen. Sie hat folgende Struktur:``` c
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} ;
Hier konzentrieren wir uns nur auf die beiden Hauptfelder oben:
OriginalCompressedSegmentSize ist die Größe des unkomprimierten Datensegments in Byte.Offset ist die Abweichung in Byte zwischen dem Startpunkt der komprimierten Daten und dem Endpunkt der Struktur _COMPRESSION_TRANSFORM_HEADER.Somit hat das komprimierte Datenpaket die folgende Form:
``` 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;
}
Analysieren wir die Funktion `Srv2DecompressData`: Sie erhält ein komprimiertes Datenpaket `COMPRESSION_TRANSFORM_HEADER` (Header), reserviert Speicher (Alloc) mit der Funktion `SrvNetAllocateBuffer` unter Verwendung der Summe `Header->OriginalCompressedSegmentSize` + `Header->Offset`, dekomprimiert dann die komprimierten Daten und kopiert die nicht komprimierten Daten in `Alloc->Buffer`.

Der Integer-Überlauffehler tritt auf, wenn `Srv2DecompressData` die Funktion `SrvNetAllocateBuffer` aufruft. Die Funktion `SrvNetAllocateBuffer` akzeptiert eigentlich zwei 64-Bit-Werte, aber beim Aufruf von `SrvNetAllocateBuffer` übergibt `Srv2DecompressData` ihr nur zwei 32-Bit-Werte (ULONG). Da sowohl `OriginalCompressedSegmentSize` als auch `Offset` ULONG sind, kann ihre Addition einen Wert ergeben, der größer als 32 Bit ist. Daher kommt es zu einem Integer-Überlauffehler (Einfach ausgedrückt: Addiert man 0xffffffff (`OriginalCompressedSegmentSize`) zu 0x10 (`Offset`), ergibt sich der Wert 0xf0000000f, aber die Funktion `SrvNetAllocateBuffer` erhält nur den Wert 0x0000000f).

Der Integer-Überlauffehler führt zu einer falschen Speicherzuweisung von Alloc (die benötigte Größe ist kleiner als die tatsächliche Größe), was einen Pufferüberlauf verursachen kann:
