2022年8月更新中出现了SMB的RCE, 微软介绍说对于server端是认证后的, 所以应该不会是历史漏洞重新出现的问题, 毕竟曾经也分析过一阵子SMB, 出现了新鲜漏洞还是需要分析分析的, 以下是一个简单的分析介绍

Bug

首先官方介绍是压缩相关的bug, 对比srv2.syssrvnet.sys后, 发现srvnet.sysSmbCompressionDecompress有改动, 所以大概率是它了.

以下是相关改动:

1660184030021

从图中看到, 改动其实很小, 就改了最后一个传入参数.

查看MSDN对于该函数的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NT_RTL_COMPRESS_API NTSTATUS RtlDecompressBufferEx2(
[in] USHORT CompressionFormat,
[out] PUCHAR UncompressedBuffer,
[in] ULONG UncompressedBufferSize,
[in] PUCHAR CompressedBuffer,
[in] ULONG CompressedBufferSize,
[in] ULONG UncompressedChunkSize,
[out] PULONG FinalUncompressedSize,
[in, optional] PVOID WorkSpace
);

[in, optional] WorkSpace

A pointer to a caller-allocated work space buffer used by the RtlDecompressBufferEx2 function during decompression. Use the RtlGetCompressionWorkSpaceSize function to determine the correct work space buffer size.

所以可以了解到, workspace理应是caller申请的一块内存.

一般来说, 应该是如下的形式调用:

1
2
3
4
5
6
7
8
if(NT_SUCCESS(RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, &CompressBufferWorkSpaceSize, &CompressFragmentWorkSpaceSize)))
{
if(WorkSpace = LocalAlloc(LPTR, CompressBufferWorkSpaceSize))
{
status = NT_SUCCESS(RtlDecompressBufferEx2(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, (PUCHAR) data, size, (PUCHAR) (*compressedData), size, 4096, compressedSize, WorkSpace));
LocalFree(WorkSpace);
}
}

先调用RtlGetCompressionWorkSpaceSize获取需要的workspace的size, 然后申请对应大小的内存, 之后再调用解压缩函数.

接下来, 我们看看P到底是个什么东西.
SmbCompressionInitialize函数内, 它初始化了全局变量P:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CompressionWorkSpaceSize = RtlGetCompressionWorkSpaceSize(
2u,
&CompressBufferWorkSpaceSize,
&CompressFragmentWorkSpaceSize);
if ( CompressionWorkSpaceSize < 0 )
goto LABEL_12;
v1 = 0;
if ( CompressBufferWorkSpaceSize )
v1 = CompressBufferWorkSpaceSize;
CompressionWorkSpaceSize = RtlGetCompressionWorkSpaceSize(
3u,
&CompressBufferWorkSpaceSize,
&CompressFragmentWorkSpaceSize);
if ( CompressionWorkSpaceSize < 0 )
goto LABEL_12;
if ( CompressBufferWorkSpaceSize > v1 )
v1 = CompressBufferWorkSpaceSize;
CompressionWorkSpaceSize = RtlGetCompressionWorkSpaceSize(
4u,
&CompressBufferWorkSpaceSize,
&CompressFragmentWorkSpaceSize);
if ( CompressionWorkSpaceSize < 0 )
goto LABEL_12;
if ( CompressBufferWorkSpaceSize > v1 )
v1 = CompressBufferWorkSpaceSize;
P = (PVOID)PplCreateLookasideList(0, 0, v1, 0x2532534Cu, v3, 0x2532534Cu);

可以看到, 它应该是放入了3种压缩类型所需要的size中最大的那一个到lookaside list里, P就是指向lookaside list结构体的指针. 可见它并不是RtlDecompressBufferEx2想要的workspace结构体.

正常情况下, 在第一个图的修补后的第58行, 它会调用P的allocate函数, 去申请最大的那个size的workspace.

然而, 由于程序员手抖, 不小心传入了P, 导致了类型混淆, 从而造成了对P的写入问题.

History

在windows 10 1909的代码中, SmbCompressionDecompress是这样实现的:

1
2
3
4
5
6
if ( RtlGetCompressionWorkSpaceSize(v13, &CompressBufferWorkSpaceSize, (PULONG)CompressFragmentWorkSpaceSize) < 0
|| (PoolWithTag = ExAllocatePoolWithTag((POOL_TYPE)512, CompressBufferWorkSpaceSize, 0x2532534Cu)) != 0i64 )
{
v10 = RtlDecompressBufferEx2(v13, a4, (unsigned int)a5, a2, a3, 0, a6, PoolWithTag);
if ( PoolWithTag )
ExFreePoolWithTag(PoolWithTag, 0x2532534Cu);

可见没有任何问题, 在windows 10 2004里, 开始变了模样, 但是参数传递是修补后的样子. 之前保存过windows 20211(发布于2020年9月)的srvnet.sys文件, 发现它是传递了P, 所以可以看到, 这个bug应该某个开发版引入的, 一直延续到了windows 11.

有意思的是, 它的变量类型是PVOID, 而P也是PVOID, 所以编译器不会有任何警告. 理论上只要有任何测试触发这个位置, 这块代码都有可能会崩溃, 但是直到如今, 才被爆出来, 也是出人意料的, 可见微软内部的安全审计依然不到位(也许就没有).

我想微软之所以那么改, 可能是为了提高内存利用效率, 避免频繁的申请内存吧, 没想到搞出这等幺蛾子, 哈哈哈

影响

好在, 自从windows 爆出过压缩部分的bug后, 它已经要求server端先验证, 后压缩, 使得bug只能是认证后才能触发. 但是Client端还是会受影响的, 毕竟client主动连的server, 也就不存在认证限制了.