
Technical analysis and proof-of-concept for CVE-2020-0796 (SMBGhost), an integer overflow vulnerability in SMBv3 compression leading to local privilege escalation on Windows 10/Server.
The compression feature added to SMBv3 starting from Windows 10/Server version 1903 contains an integer overflow vulnerability confirmed by Microsoft on 12/03/2020. It allows an attacker to perform Local Privilege Escalation (LPE) and Remote Code Execution (RCE). Here we will only discuss the LPE vulnerability.
Affected versions:
Analyzing the file srv2.sys, it is observed that the functions related to Decompress are called as follows:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
First function `Srv2ReceiveHandler` is called to receive an smb data packet and calls a function corresponding to the `ProtocolId` protocol. If `PrococolId` = 0x424D53FC, it will call the function `Srv2DecompressMessageAsync`, which will proceed to call the function `Srv2DecompressData` to decompress the data packet. The function `Srv2DecompressData` will call the function `SrvNetAllocateBuffer` to allocate an `Alloc` used to store the data after decompression, then it calls the function `SmbCompressionDecompress` to decompress the data packet, finally it calls the function `memcpy`. Thus the entire Decompress process has the following main steps:
- 1. Allocate
- 2. Decompress
- 3. Copy
According to documentation provided by Microsoft, the `COMPRESSION_TRANSFORM_HEADER` structure is used to send and receive compressed data from client and server. It has the following structure:``` c
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} ;
Here we only focus on the 2 main fields above:
OriginalCompressedSegmentSize is the size of the uncompressed data segment, in bytes.Offset is the offset in bytes between the start of the compressed data and the end of the _COMPRESSION_TRANSFORM_HEADER structure.Thus the compressed data packet will have the following 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;
}
Analysis of the `Srv2DecompressData` function reveals that it receives a compressed packet data `COMPRESSION_TRANSFORM_HEADER` (Header), allocates a memory region (Alloc) using the `SrvNetAllocateBuffer` function with the sum of `Header->OriginalCompressedSegmentSize` + `Header->Offset` as the parameter, then decompresses the compressed data and copies the uncompressed data into `Alloc->Buffer`.

An integer overflow error occurs when `Srv2DecompressData` calls the `SrvNetAllocateBuffer` function. The `SrvNetAllocateBuffer` function actually accepts two 64-bit values, but when calling `SrvNetAllocateBuffer`, `Srv2DecompressData` only passes two 32-bit values (ULONG) to it. Meanwhile, both `OriginalCompressedSegmentSize` and `Offset` are ULONG, and when they are added together, the result can exceed 32 bits. This causes an integer overflow (Simply put, adding 0xffffffff (`OriginalCompressedSegmentSize`) to 0x10 (`Offset`) yields 0xf0000000f but `SrvNetAllocateBuffer` only receives 0x0000000f).

The integer overflow leads to incorrect allocation of the Alloc memory region (the size to be allocated is smaller than the actual required size), which can cause a buffer overflow:
