本文来自启明星辰订阅号。
2020年3月10日,微软在其官方SRC发布了CVE-2020-0796的安全公告(ADV200005,Microsoft Guidance for Disabling SMBv3 Compression),公告表示在Windows SMBv3版本的客户端和服务端存在远程代码执行漏洞。同时指出该漏洞存在于MicroSoft Server Message Block 3.1.1协议处理特定请求包的功能中,攻击者利用该漏洞可在目标SMB Server或者Client中执行任意代码。
漏洞复现
漏洞基本原理
图2 带压缩数据的SMB数据报文结构
-
ProtocolId:4字节,固定为0x424D53FC
-
OriginalComressedSegmentSize:4字节,原始的未压缩数据大小
-
CompressionAlgorithm:2字节,压缩算法
-
Flags :2字节,详见协议文档
-
Offset/Length:根据Flags的取值为Offset或者Length,Offset表示数据包中压缩数据相对于当前结构的偏移
提权利用过程
(2)验证程序获取自身token数据结构中privilege成员在内核中的地址(记tokenAddr)。
(3)验证程序通过session发送畸形压缩数据(记为evilData)给SMB server触发漏洞。其中,evilData包含tokenAddr、权限数据、溢出占位数据。
(4)SMS server收到evilData后触发漏洞,并修改tokenAddr地址处的权限数据,从而提升验证程序的权限。
(5)验证程序获取权限后对winlogon进行控制,来创建system用户shell。
漏洞内存分配分析
-
OriginalSize:0xffffffff
-
Offset:0x10
-
Real compressed data:13字节的压缩数据,解压后应为1108字节’A’加8字节的token地址。
-
SMB3 raw data:实际上是由2个8字节的0x1FF2FFFFBC(总长0x10)加上0x13字节的压缩数据组成。
图6 SrvNetAllocateBuffer内存分配过程
图7 SrvDisableNetBufferLookAsideList变量初始化过程
SrvNetBufferLookasideAllocate函数实际是调用SrvNetAllocateBufferFromPool来分配内存,如图9所示。
图9 SrvNetBufferLookasideAllocate反编译代码
在函数SrvNetAllocateBufferFromPool中,对于用户请求的内存分配大小,内部通过ExAllocatePoolWithTag函数分配的内存实际要大于请求值(多出部分用于存储部分内存相关数据结构)。以请求分配0x1100大小为例,经过一系列判断后,最后分配的内存大小allocate_size = 0x1100 + E8 + 2*(MmSizeOfMdl + 8)。
图10 SrvNetAllocateBufferFromPool函数反编译代码
内存分配完毕之后,SrvNetAllocateBufferFromPool函数还对分配的内存进行了一系列初始化操作,最后返回了一个内存信息结构体指针作为函数的返回值。
这里需要注意如下的数据关系:SrvNetAllocateBufferFromPool函数返回值return_buffer指向一个内存数据结构,该内存数据结构起始地址同实际分配内存(函数ExAllocatePoolWithTag分配的内存)起始地址的的偏移为0x1150;return_buffer+0x18位置指向了实际分配内存起始地址偏移0x50位置处,而最终return_buffer会作为函数SrvNetAllocateBuffer的返回值。其内存布局关系如图12。
漏洞内存破坏分析
图13 Srv2DecompressData解压压缩数据
实际上,该函数调用了Windows库函数RtlDecompressBufferEx2来实现解压,根据RtlDecompressBufferEx2的函数原型来对应分析SmbCompressionDecompress函数的各个参数。
SmbCompressionDecompress(CompressAlgo,//压缩算法
Compressed_buf,//指向数据包中的压缩数据
Compressed_size,//数据包中压缩数据大小,计算得到
UnCompressedBuf,//解压后的数据存储地址,*(alloc_buffer+0x18)+0x10
UnCompressedSize,//压缩数据原始大小,源于数据包OriginalCompressedSegmentSize
FinalUnCompressedSize)//最终解压后数据大小
从反编译代码可以看出,函数SmbCompressionDecompress中保存解压后数据的地址为*(alloc_buffer+0x18)+0x10的位置,根据内存分配过程分析,alloc_buffer + 0x18指向了实际内存分配起始位置偏移0x50处,所以拷贝目的地址为实际内存分配起始地址偏移0x60位置处。
在解压过程中,压缩数据解压后将存储到这个地址指向的内存中。根据evilData数据的构造过程,解压后的数据为占坑数据和tokenAddr。拷贝到该处地址后,tokenAddr将覆盖原内存数据结构中alloc_buffer+0x18处的数据。也就是解压缩函数SmbCompressionDecompress返回后,alloc_buffer+0x18将指向验证程序的tokenAddr内核地址。拷贝过程如图14和15所示。
继续看Srv2DecompressData的后续处理流程,解压成功后,函数判断offset的结果不为0。不为0则进行内存移动,内存拷贝的参数如下:
此时,alloc_buffer+0x18已经指向验证程序的tokenAddr内核地址,而SMB_payload此时指向evilData中的权限数据,offset则为0x10。因此,这个内存移动完成后,权限数据将写入tokenAddr处。这意味着,SMS Server成功修改了验证程序的权限,从而实现了验证程序的提权!
还有一个细节需要注意,在解压时,Srv2DecompressData函数会判断实际的解压后数据大小FinalUnCompressedSize是否和数据包中原始数据大小OriginalCompressedSegmentSize一致,如图16所示。
按理来说实际解压后的数据大小为0x1100,不等于数据包中的原始压缩数据大小0xffffffff,这里应该进入到后面内存释放的流程。然而,实际上在函数SmbCompressionDecompress中,调用RtlDecompressBufferEx2成功后会直接将OriginalCompressedSegmentSize赋值给FinalUnCompressedSize。这也是该漏洞关于任意地址写入成功的关键之一。
漏洞修复建议
1.https://fortiguard.com/encyclopedia/ips/48773
2.https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/ADV200005
3.https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0796
4.https://www.catalog.update.microsoft.com/Search.aspx?q=KB4551762
5.https://github.com/danigargu/CVE-2020-0796
6.https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5606ad47-5ee0-437a-817e-70c366052962
7.https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-rtldecompressbufferex2
扫码加活动群秘书,回复“安全”进入活动群,参与抽奖活动,赢取大奖
字节跳动春季招聘全面启动,扫码或者电脑端打开链接(https://job.toutiao.com/s/cQJsMH),搜索你心仪的职位,投递简历,进入本人的专属内推渠道,流程短,速度快,工资高😁
原文始发于微信公众号(玄魂工作室):Windows SMB Ghost(CVE-2020-0796)漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论