
Technical analysis of CVE-2020-1206 (SMBleed) kernel information disclosure vulnerability in Windows SMBv3, including unauthenticated memory leak oracle and exploitation techniques combining with SMBGhost for RCE.
In the SMBGhost vulnerability (CVE-2020-0796) I talked about a write-what-where primitive technique through the use of an integer overflow bug to change the pointer Alloc.Userbuffer to point to an address we desire and write arbitrary data into it. Similar to SMB Ghost, this vulnerability also exists in the Srv2DecompressData function in srv2.sys. Let's review the Srv2DecompressData function related to the SMBGhost vulnerability (CVE-2020-0796) simplified by 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;
}
The Srv2DecompressData function takes a compressed message sent by the client and proceeds to allocate a necessary memory region, decompress the message into it. Then, if the Offset field is non-zero, it copies the data (RawData) before the compressed data to the beginning of the allocated memory region.

The SMBGhost bug lies in the function not checking for integer overflow, leading to incorrect allocation size and causing a buffer overflow. Three months after Microsoft patched SMBGhost, the vulnerability CVE-2020-1206 (SMBleed - as named by [Zecops Blog](https://blog.zecops.com/)) was discovered. This vulnerability allows us to leak the address of another machine, and if combined with SMBGhost, we can achieve RCE. To have a simpler view of the Srv2DecompressData function, we will reuse this function as it was before the SMBGhost patch, assuming it has been patched.
# Faking OriginalCompressedSegmentSize
Like SMBGhost, this time we will still fake the OriginalCompressedSegmentSize with a number slightly larger than the decompressed data we send. For example, we compress data of size x bytes; instead of setting x into the OriginalCompressedSegmentSize field, we set it to x + 0x1000. See the following image for clarity:

Uninitialized kernel data will be treated as part of the message.
As I mentioned in the analysis of [CVE-2020-0796](https://github.com/datntsec/CVE-2020-0796), Srv2DecompressData will still skip the checking stage after the SmbCompressionDecompress function if the decompression is successful:``` c
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
Mặc dù trường OriginalCompressedSegmentSize được đặt thành x + 0x1000 thay vì x, nhưng sau khi giải nén thành công, biến FinalCompressedSize không chứa giá trị x, mà sẽ chứa giá trị 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;
}
Because after successful decompression, FinalCompressedSize is updated to hold the CompressedBufferSize value (corresponding to the OriginalCompressedSegmentSize passed to the SmbCompressionDecompress function). This subsequent update and check is almost unnecessary and may lead to some unexpected errors.
## Basic Exploitation
The message structure that Zecops uses to demonstrate the vulnerability is the [SMB2 WRITE message](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e7046961-3318-4350-be2a-a8d69bb59ce8). This structure contains fields such as the number of writable bytes, flags, etc., followed by a buffer of arbitrary length. This is quite perfect for exploiting the vulnerability, since we can create a message and specify the header, with a buffer containing uninitialized data.
Based on [Zecops'](https://blog.zecops.com/) POC in Microsoft's WindowsProtocolTestSuites repository, to get a clearer view of this, we will add this small addition to the compression function:``` c
// HACK: fake size
if (((Smb2SinglePacket)packet).Header.Command == Smb2Command.WRITE)
{
((Smb2WriteRequestPacket)packet).PayLoad.Length += 0x1000;
compressedPacket.Header.OriginalCompressedSegmentSize += 0x1000;
}
Note that this POC requires authentication and write share permissions, which are often available in many cases. However, the error return applies to every message (including messages with or without authentication), so it's possible we can exploit without authentication. Another thing is that the memory we will leak comes from previous allocations in NonPagedPoolNx, and since we can control the allocation size, we can control the data we leak to some extent.
So if there is no authentication, can we still leak a kernel address? To answer this question, let's analyze SMB deeper.
When authenticating, the client sends the following messages:
SMB2 NEGOTIATE → SMB2 SESSION_SETUP → SMB2 SESSION_SETUP
If authentication fails, the connection is terminated after the second SMB2 SESSION_SETUP packet:

Assuming we don't have authentication, we will check if there is any command that can be sent without needing authentication. Through searching, we notice:
SMB2 NEGOTIATE and it is also the only SMB2 NEGOTIATE command during a session.SMB2 SESSION_SETUP.Among them, the SMB2 NEGOTIATE message will not be compressed. The bug lies in the decompression function, so we will not consider it and only examine the SMB2 SESSION_SETUP messages.