Análisis técnico de la vulnerabilidad de ejecución remota de código cliente / servidor SMBv3 de Microsoft (CVE-2020-0796)


El Centro de seguridad de Microsoft lanzó un parche de vulnerabilidad de ejecución remota de código SMBv3 que afecta a los usuarios de Windows 10 y otros sistemas a las 23:00 el 12 de marzo, hora de Beijing. Recomendamos que los usuarios afectados instalen el parche tan pronto como sigan la Guía de información de Microsoft Update: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0796. Al mismo tiempo, el equipo de 360Vulcan realizó un análisis rápido de la vulnerabilidad, que es una vulnerabilidad de ejecución remota de código de toque cero de alto riesgo. El análisis técnico es el siguiente.

Análisis de vulnerabilidad de SMB

Entorno de depuración

Causa raíz

La vulnerabilidad se produjo en srv2.sys. Debido a que SMB no manejó adecuadamente los paquetes de datos comprimidos, al usar la longitud que pasó el cliente para la descompresión al descomprimir el paquete de datos, no verificó si la longitud era legal. Eventualmente causó un desbordamiento de enteros.

Análisis detallado

SMB v3 admite la compresión de datos. Si el ProtocolId en el encabezado SMB es 0x424D53FC, es decir, 0xFC, 'S', 'M', 'B', entonces los datos se comprimen y smb llamará a la función de compresión y descompresión.
Primero, SMB llamará a la función srv2! Srv2ReceiveHandler para recibir paquetes de datos smb y establecerá la función de procesamiento correspondiente de acuerdo con ProtocoIId.
  • __int64 __fastcall Srv2ReceiveHandler(__int64 a1, void *Src, __int64 a3, unsigned int a4, unsigned int *a5, char *Srca, struct _SLIST_ENTRY *a7, _QWORD *a8)
  • {
  • ...
  • //
  • // 这里判断头部ProtocolId
  • //
  • if ( **((_DWORD **)&v20[15].Next[1].Next + 1) == 'BMS\xFC' )
  • {
  • if ( KeGetCurrentIrql() > 1u )
  • {
  • v20[14].Next = (_SLIST_ENTRY *)v11;
  • v20[2].Next = (_SLIST_ENTRY *)Srv2DecompressMessageAsync;
  • v43 = HIDWORD(v20->Next) == 5;
  • *((_DWORD *)&v20[3].Next + 2) = 0;
  • if ( v43 )
  • {
  • LOBYTE(v71) = 1;
  • LOBYTE(v35) = 1;
  • SRV2_PERF_ENTER_EX(&v20[32].Next + 1, v35, 307i64, "Srv2PostToThreadPool", (_DWORD)v71);
  • }
  • v44 = *((_QWORD *)&v20[3].Next[8].Next + 1);
  • v45 = *(_QWORD *)(v44 + 8i64 * KeGetCurrentNodeNumber() + 8);
  • if ( !ExpInterlockedPushEntrySList((PSLIST_HEADER)(v45 + 16), v20 + 1) && *(_WORD *)(v45 + 66) )
  • RfspThreadPoolNodeWakeIdleWorker(v45);
  • goto LABEL_168;
  • }
  • }
  • }
Del código, podemos ver que si se trata de datos comprimidos, llama a la función Srv2DecompressMessageAsync para descomprimirlo. Srv2! Srv2DecompressMessageAsync llamará a la función Srv2DecompressData.
La estructura es la siguiente:
  • typedef struct _COMPRESSION_TRANSFORM_HEADER
  • {
  • ULONG ProtocolId;
  • ULONG OriginalCompressedSegmentSize;
  • USHORT CompressionAlgorithm;
  • USHORT Flags;
  • ULONG Length;
  • }COMPRESSION_TRANSFORM_HEADER, *PCOMPRESSION_TRANSFORM_HEADER;
El código que genera la vulnerabilidad de desbordamiento de enteros es el siguiente:
  • __int64 __fastcall Srv2DecompressData(__int64 pData)
  • {
  • __int64 v2; // rax
  • COMPRESSION_TRANSFORM_HEADER Header; // xmm0 MAPDST
  • __m128i v4; // xmm0
  • unsigned int CompressionAlgorithm; // ebp
  • __int64 UnComparessBuffer; // rax MAPDST
  • int v9; // eax
  • int v11; // [rsp+60h] [rbp+8h]
  • v11 = 0;
  • v2 = *(_QWORD *)(pData + 0xF0);
  • if ( *(_DWORD *)(v2 + 0x24) < 0x10u ) // 这里判断数据包长度的最小值
  • return 0xC000090Bi64;
  • Header = *(COMPRESSION_TRANSFORM_HEADER *)*(_QWORD *)(v2 + 0x18);// [v2+0x18]中为客户端传进来的Buffer
  • // [v2+0x24]为数据包长度
  • v4 = _mm_srli_si128((__m128i)Header, 8);
  • CompressionAlgorithm = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(pData + 0x50) + 0x1F0i64) + 0x8Ci64);
  • if ( CompressionAlgorithm != v4.m128i_u16[0] )
  • return 0xC00000BBi64;
  • UnCompressBuffer = SrvNetAllocateBuffer((unsigned int)(Header.OriginalCompressedSegmentSize + v4.m128i_i32[1]), 0i64);// OriginalCompressedSegmentSize + CompressedSegmentSize,这里没有检查相加的值,导致整数溢出,分配一个较小的UnCompressBuffer
  • if ( !UnComparessBuffer )
  • return 0xC000009Ai64;
  • if ( (int)SmbCompressionDecompress(
  • CompressionAlgorithm, // CompressionAlgorithm
  • *(_QWORD *)(*(_QWORD *)(pData + 0xF0) + 0x18i64) + (unsigned int)Header.Length + 0x10i64,// CompressedBuffer
  • (unsigned int)(*(_DWORD *)(*(_QWORD *)(pData + 0xF0) + 0x24i64) - Header.Length - 0x10),// CompressedBufferSize
  • (unsigned int)Header.Length + *(_QWORD *)(UnComparessBuffer + 0x18),// UncompressedBuffer,会传入SmbCompressionDecompress函数进行Decompress处理。
  • Header.OriginalCompressedSegmentSize,
  • &v11) < 0
  • || (v9 = v11, v11 != Header.OriginalCompressedSegmentSize) )
  • {
  • SrvNetFreeBuffer(UnComparessBuffer);
  • return 0xC000090Bi64;
  • }
  • if ( Header.Length )
  • {
  • memmove(
  • *(void **)(UnComparessBuffer + 24),
  • (const void *)(*(_QWORD *)(*(_QWORD *)(pData + 240) + 24i64) + 16i64),
  • (unsigned int)Header.Length);
  • v9 = v11;
  • }
  • *(_DWORD *)(UnComparessBuffer + 36) = Header.Length + v9;
  • Srv2ReplaceReceiveBuffer(pData, UnComparessBuffer);
  • return 0i64;
  • }
A través del código anterior podemos ver que ambas longitudes en este encabezado no están marcadas. La siguiente es una descripción de estas dos longitudes en la documentación de MS:
  1. OriginalCompressedSegmentSize (4 bytes) El tamaño, en bytes, del segmento de datos sin comprimir.
  2. Offset / Length (4 bytes) Si SMB2_COMPRESSION_FLAG_CHAINED está configurado en el campo Flags, este campo DEBE interpretarse como Longitud. La longitud, en bytes, de la carga útil comprimida. De lo contrario, este campo DEBE interpretarse como Offset. El desplazamiento, en bytes, desde el final de esta estructura hasta el inicio del segmento de datos comprimido.
Luego, en la función srv2! Srv2DecompressData, se llama a SmbCompressionDecompress y finalmente se llama a nt! RtlDecompressBufferXpressLz para descomprimir los datos. La ruta de llamada es la siguiente:
  • ffff9480`20e2ad98 nt!RtlDecompressBufferXpressLz+0x2d0
  • ffff9480`20e2adb0 nt!RtlDecompressBufferEx2+0x66
  • ffff9480`20e2ae00 srvnet!SmbCompressionDecompress+0xd8
  • ffff9480`20e2ae70 srv2!Srv2DecompressData+0xdc
nt! RtlDecompressBufferXpressLz analizará los campos en el paquete de datos del protocolo de compresión smb, que contiene el tamaño del búfer de descompresión, y lo comparará con el OriginalCompressedSegmentSize del búfer asignado a través de SrvNetAllocateBuffer antes de confirmar que su tamaño no es mayor que OriginalCompressedSegmentSize, y luego realizará memcpy.
  • signed __int64 __fastcall RtlDecompressBufferXpressLz(_BYTE *a1, unsigned int a2, _BYTE *a3, unsigned int a4, __int64 a5, _DWORD *a6)
  • {
  • v9 = &a1[a2];
  • ....
  • if ( &a1[v21] > v9 )
  • return 0xC0000242i64;
  • ...
  • v33 = a1;
  • a1 += v21;
  • qmemcpy(v33, v23, v21);
  • }
Como se muestra en el pseudocódigo anterior, a1 apunta al UncompressBuffer asignado por SrvNetAllocateBuffer, el valor de a2 es OriginalCompressedSegmentSize y el valor de v21 es el tamaño de los datos descomprimidos analizados desde el paquete smb. Este valor puede ser controlado por el atacante. Si el tamaño es mayor que OriginalCompressedSegmentSize, entonces el tamaño es mayor que OriginalCompressedSegmentSize Se devolverá el error 0xC0000242. Debido a que no hubo una verificación previa de la longitud, si pasamos un OriginalCompressedSegmentSize grande para desencadenar un desbordamiento de entero, v21 puede establecer un valor máximo, y aún puede juzgar el tamaño de descompresión y finalmente llamar a qmemcpy para copiar un Tamaños extremadamente grandes causan desbordamientos del búfer.

Parche

El parche de Microsoft es verificar las dos longitudes anteriores en Srv2DecompressData.
  • __int64 __fastcall Srv2DecompressData(__int64 a1)
  • {
  • //
  • //添加了对Header中Length的检查
  • //
  • if ( RtlULongAdd(Header.OriginalCompressedSegmentSize, Header2.Length, &pulResult) < 0 )
  • {
  • ...
  • }
  • if ( pulResult > (unsigned __int64)(unsigned int)(*(_DWORD *)(v9 + 0x24) + 0x100) + 0x34 )
  • {
  • ...
  • }
  • if ( RtlULongSub(pulResult, Header.Length, &pulResult) < 0 )
  • {
  • ...
  • }
  • }
Para ver el documento del protocolo, consulte: SMB2
Fuente: http://blogs.360.cn/post/CVE-2020-0796.html#4-L1

Grupo de Telegram: https://t.me/hackingteamelrinconoscuro

Canal de Youtube: https://www.youtube.com/channel/UCXy8Lg28OuGuI5Z-2EWJaNA?view_as=subscriber

Canal Vimeo: https://vimeo.com/403136547?activityReferer=1

Red Social Twitter: https://twitter.com/HackingTeam1?s=09


Pagina Web: https://elrincondehackingteam.blogspot.com/

Comentarios