2022年8月更新中出现了SMB的RCE, 微软介绍说对于server端是认证后的, 所以应该不会是历史漏洞重新出现的问题, 毕竟曾经也分析过一阵子SMB, 出现了新鲜漏洞还是需要分析分析的, 以下是一个简单的分析介绍
Bug 首先官方介绍是压缩相关的bug, 对比srv2.sys
和srvnet.sys
后, 发现srvnet.sys
的SmbCompressionDecompress
有改动, 所以大概率是它了.
以下是相关改动:
从图中看到, 改动其实很小, 就改了最后一个传入参数.
查看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, 0x2532534C u, v3, 0x2532534C u);
可以看到, 它应该是放入了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, 0x2532534C u)) != 0 i64 ) { v10 = RtlDecompressBufferEx2(v13, a4, (unsigned int )a5, a2, a3, 0 , a6, PoolWithTag); if ( PoolWithTag ) ExFreePoolWithTag(PoolWithTag, 0x2532534C u);
可见没有任何问题, 在windows 10 2004里, 开始变了模样, 但是参数传递是修补后的样子. 之前保存过windows 20211(发布于2020年9月)的srvnet.sys
文件, 发现它是传递了P
, 所以可以看到, 这个bug应该某个开发版引入的, 一直延续到了windows 11.
有意思的是, 它的变量类型是PVOID
, 而P
也是PVOID
, 所以编译器不会有任何警告. 理论上只要有任何测试触发这个位置, 这块代码都有可能会崩溃, 但是直到如今, 才被爆出来, 也是出人意料的, 可见微软内部的安全审计依然不到位(也许就没有).
我想微软之所以那么改, 可能是为了提高内存利用效率, 避免频繁的申请内存吧, 没想到搞出这等幺蛾子, 哈哈哈
影响 好在, 自从windows 爆出过压缩部分的bug后, 它已经要求server端先验证, 后压缩, 使得bug只能是认证后才能触发. 但是Client端还是会受影响的, 毕竟client主动连的server, 也就不存在认证限制了.