
Análise técnica da vulnerabilidade de divulgação de informações do kernel CVE-2020-1206 (SMBleed) no Windows SMBv3, incluindo o oráculo de vazamento de memória não autenticado e técnicas de exploração combinadas com o SMBGhost para RCE.
Na vulnerabilidade SMBGhost (CVE-2020-0796) falei sobre uma técnica de write-what-where primitive por meio do uso de um bug de overflow de inteiro para alterar o ponteiro Alloc.Userbuffer para apontar para um endereço que desejamos e gravar dados arbitrários nele. Semelhante ao SMB Ghost, essa vulnerabilidade também existe na função Srv2DecompressData em srv2.sys. Vamos revisar a função Srv2DecompressData relacionada à vulnerabilidade SMBGhost (CVE-2020-0796), que foi simplificada por 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;
}
A função Srv2DecompressData recebe uma mensagem comprimida enviada pelo cliente e aloca uma região de memória necessária, descomprimindo a mensagem nela. Em seguida, se o campo Offset for diferente de zero, ela copia os dados (RawData) que estão antes dos dados comprimidos para o início da região de memória alocada.

O bug SMBGhost está no fato de a função não verificar integer overflow, levando a uma alocação de tamanho incorreto que causa buffer overflow. Três meses após a Microsoft corrigir o SMBGhost, a vulnerabilidade CVE-2020-1206 (SMBleed - como é chamada pelo [Zecops Blog](https://blog.zecops.com/)) foi encontrada. Essa vulnerabilidade nos permite vazar o endereço de outra máquina e, se combinada com o SMBGhost, podemos obter RCE. Para termos uma visão mais simples da função Srv2DecompressData, usaremos essa função sem a correção do SMBGhost e assumiremos que ela já foi corrigida.
# Falsificando OriginalCompressedSegmentSize
Como no SMBGhost, desta vez continuaremos falsificando o OriginalCompressedSegmentSize com um valor um pouco maior do que os dados descomprimidos que enviamos. Por exemplo, se comprimirmos um dado com tamanho x bytes, em vez de colocar x no campo OriginalCompressedSegmentSize, colocaremos x + 0x1000; veja a imagem a seguir para ficar mais claro:

Dados não inicializados do kernel serão considerados como parte da mensagem.
Como eu disse na análise do [CVE-2020-0796](https://github.com/datntsec/CVE-2020-0796), o Srv2DecompressData ainda ignora a etapa de verificação após a função SmbCompressionDecompress se a descompressão for bem-sucedida:``` c
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
Embora o campo OriginalCompressedSegmentSize seja definido como x + 0x1000 em vez de x, após a descompressão bem-sucedida, a variável FinalCompressedSize não contém o valor x, mas sim o valor 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;
}
Porque após a descompressão bem-sucedida, `FinalCompressedSize` é atualizado para manter o valor `CompressedBufferSize` (correspondente ao `OriginalCompressedSegmentSize` passado para a função `SmbCompressionDecompress`). A atualização e a verificação subsequente são quase desnecessárias, podendo levar a alguns erros inesperados.
# Exploração básica
A estrutura de mensagem que a Zecops usa para demonstrar a falha é a [mensagem SMB2 WRITE](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e7046961-3318-4350-be2a-a8d69bb59ce8). Essa estrutura contém campos como o número de bytes graváveis, flags, ..., seguidos por um buffer de comprimento arbitrário. Isso é bastante perfeito para explorar a falha, pois podemos criar uma mensagem e especificar o cabeçalho, com um buffer contendo dados não inicializados.
Baseado no POC da [Zecops](https://blog.zecops.com/) no repositório WindowsProtocolTestSuites da Microsoft, para ter uma visão mais clara disso, adicionaremos este pequeno complemento à função de compressão:``` 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.