
Технический анализ и proof-of-concept для CVE-2020-0796 (SMBGhost), уязвимость целочисленного переполнения в сжатии SMBv3, приводящая к локальному повышению привилегий на Windows 10/Server.
Функция сжатия, добавленная в SMBv3 начиная с версии ОС Windows 10/Server 1903, содержит уязвимость целочисленного переполнения, подтвержденную Microsoft 12 марта 2020 года. Позволяет атакующему выполнять локальное повышение привилегий (LPE) и удаленное выполнение кода (RCE). Здесь будет рассмотрена только уязвимость LPE.
Затронутые версии:
Анализ файла srv2.sys показывает, что функции, связанные с декомпрессией, вызываются следующим образом:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
Сначала вызывается функция `Srv2ReceiveHandler` для приема пакета данных smb и вызова функции, соответствующей протоколу `ProtocolId`. Если `PrococolId` = 0x424D53FC, то вызывается функция `Srv2DecompressMessageAsync`, которая, в свою очередь, вызывает функцию `Srv2DecompressData` для распаковки пакета данных. Функция `Srv2DecompressData` вызывает `SrvNetAllocateBuffer` для выделения `Alloc`, используемого для хранения данных после распаковки, затем вызывает `SmbCompressionDecompress` для выполнения распаковки пакета данных и, наконец, вызывает `memcpy`. Таким образом, весь процесс распаковки включает следующие основные этапы:
- 1. Allocate
- 2. Decompress
- 3. Copy
Согласно документации Microsoft, структура `COMPRESSION_TRANSFORM_HEADER` используется для отправки и получения сжатых данных между клиентом и сервером. Она имеет следующую структуру:``` c
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} ;
Здесь мы сосредоточимся только на двух основных полях:
OriginalCompressedSegmentSize — это размер несжатого сегмента данных в байтах.Offset — это смещение в байтах между началом сжатых данных и концом структуры _COMPRESSION_TRANSFORM_HEADER.Таким образом, сжатый пакет данных будет иметь следующий вид:
``` 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;
}
Анализ функции `Srv2DecompressData`: функция принимает сжатый пакет данных `COMPRESSION_TRANSFORM_HEADER` (Header), выделяет область памяти (Alloc) с помощью функции `SrvNetAllocateBuffer` с параметром, равным сумме `Header->OriginalCompressedSegmentSize` + `Header->Offset`, затем распаковывает сжатые данные и копирует несжатые данные в `Alloc->Buffer`.

Ошибка целочисленного переполнения происходит, когда `Srv2DecompressData` вызывает функцию `SrvNetAllocateBuffer`. Функция `SrvNetAllocateBuffer` фактически принимает два 64-битных значения, но при вызове `SrvNetAllocateBuffer` `Srv2DecompressData` передает ей только два 32-битных значения (ULONG). При этом и `OriginalCompressedSegmentSize`, и `Offset` являются ULONG, и при их сложении может получиться число больше 32 бит. Поэтому возникает ошибка целочисленного переполнения (проще говоря, при сложении 0xffffffff (`OriginalCompressedSegmentSize`) с 0x10 (`Offset`) получится значение 0xf0000000f, но функция `SrvNetAllocateBuffer` получает только значение 0x0000000f).

Ошибка целочисленного переполнения приведет к неправильному выделению памяти Alloc (необходимый размер выделения меньше фактического), что может вызвать переполнение буфера:
