Windows SMB Ghost(CVE-2020-0796)缝隙分析
颁布功夫 2020-04-09缝隙介绍
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中执行肆意代码。
宝运莱官方网站ADLab安全钻研人员在对该缝隙进行钻研的过程中发现目前流传的一些缝隙分析存在某些问题,因而对该缝隙进行了深刻的分析,并在Windows 10系统上进行了复现。
缝隙复现
选取Windows 10 1903版本进行复现。在缝隙利用后,验证法式提权实现后创建了一个system权限的cmd shell,如图1所示。
图1 CVE-2020-0796本地提权
缝隙根基道理
CVE-2020-0796缝隙存在于受影响版本的Windows驱动srv2.sys中。Windows SMB v3.1.1 版本增长了对压缩数据的支持。图2所示为带压缩数据的SMB数据报文的组成。
图2 带压缩数据的SMB数据报文结构
凭据微软MS-SMB2和谈文档,SMB Compression Transform Header的结构如图3所示。
图3 SMB Compression Transform Header数据结构
ProtocolId:4字节,固定为0x424D53FC
OriginalComressedSegmentSize:4字节,原始的未压缩数据大幼
CompressionAlgorithm:2字节,压缩算法
Flags :2字节,详见和谈文档
Offset/Length:凭据Flags的取值为Offset或者Length,Offset暗示数据包中压缩数据相对于当前结构的偏移
srv2.sys中处置SMBv3压缩数据包的解压函数Srv2DecompressData未严格校验数据包中OriginalCompressedSegmentSize和Offset/Length字段的合法性。而这两个字段影响了Srv2DecompressData中内存分配函数SrvNetAllocateBuffer的参数。如图4所示的Srv2DecompressData函数反编译代码,SrvNetAllocateBuffer现实的参数为OriginalCompressedSegmentSize+Offset。这两个参数都直接起源于数据包中SMB Compression Transform Header中的字段,而函数并未判断这两个字段是否合法,就直接将其相加后作为内存分配的参数(unsigned int类型)。
图4 Srv2DecompressData函数的关键代码
这里,OriginalCompressedSegmentSize+Offset可能幼于现实必要分配的内存大幼,从而在后续挪用解压函数SmbCompressionDecompress过程中存在越界读取或者写入的风险。
提权势用过程
目前已公开的针对该缝隙的本地提权势用蕴含如下的重要过程:
(1)验证法式起初创建到SMS server的会话衔接(记为session)。
(2)验证法式获取自身token数据结构中privilege成员在内核中的地址(记tokenAddr)。
(3)验证法式通过session发送畸形压缩数据(记为evilData)给SMB server触发缝隙。其中,evilData蕴含tokenAddr、权限数据、溢出占位数据。
(4)SMS server收到evilData后触发缝隙,并批改tokenAddr地址处的权限数据,从而提升验证法式的权限。
(5)验证法式获取权限后对winlogon进行节造,来创建system用户shell。
缝隙内存分配分析
首先,看一下已公开利用的evilData数据包的内容,如图5所示。
图5 提权poc发送的带压缩数据的SMB数据包
数据包的内容很单一,其中几个关键字段数据如下:
OriginalSize:0xffffffff
Offset:0x10
Real compressed data:13字节的压缩数据,解压后应为1108字节’A’加8字节的token地址。
SMB3 raw data:现实上是由2个8字节的0x1FF2FFFFBC(总长0x10)加上0x13字节的压缩数据组成。
从上面的缝隙道理分析可知,缝隙成因是Srv2DecompressData函数对报文字段不足合法性判断造成内存分配不当。在该缝隙数据包中,OriginalSize 是一个畸形值。OriginalSize + Offset = 0xffffffff + 0x10 = 0xf 是一个很幼的值,其将会传递给SrvNetAllocateBuffer进行挪用,下面具体分析内存分配情况。SrvNetAllocateBuffer的反编译代码如图6。
图6 SrvNetAllocateBuffer内存分配过程
由于传给SrvNetAllocateBuffer的参数为0xf,凭据SrvNetAllocateBuffer的处置流程可知,该要求内存将从SrvNetBufferLookasides表中分配。这里必要把稳的是,变量SrvDisableNetBufferLookAsideList跟注册表项有关,系统默认状态下SrvDisableNetBufferLookAsideList为0。
图7 SrvDisableNetBufferLookAsideList变量初始化过程
SrvNetBufferLookasides表通过函数SrvNetCreateBuffer初始化,现实SrvNetCreateBuffer循环挪用了SrvNetBufferLookasideAllocate分配内存,挪用SrvNetBufferLookasideAllocate的参数别离为[‘0x1100’, ‘0x2100’, ‘0x4100’, ‘0x8100’, ‘0x10100’, ‘0x20100’, ‘0x40100’, ‘0x80100’, ‘0x100100’]。在这里,内存分配参数为0xf,对应的lookaside表为0x1100大幼的表项。
图8 SrvNetCreateBuffer反编译代码
SrvNetBufferLookasideAllocate函数现实是挪用SrvNetAllocateBufferFromPool来分配内存,如图9所示。
图9 SrvNetBufferLookasideAllocate反编译代码
在函数SrvNetAllocateBufferFromPool中,对于用户要求的内存分配大幼,内部通过ExAllocatePoolWithTag函数分配的内存现实要大于要求值(多出部门用于存储部门内存有关数据结构)。以要求分配0x1100大幼为例,经过一系列判断后,最后分配的内存大幼allocate_size = 0x1100 + E8 + 2*(MmSizeOfMdl + 8)。
图10 SrvNetAllocateBufferFromPool函数反编译代码
内存分配结束之后,SrvNetAllocateBufferFromPool函数还对分配的内存进行了一系列初始化操作,最后返回了一个内存信息结构体指针作为函数的返回值。
图11 SrvNetAllocateBufferFromPool初始化内存数据
这里必要把稳如下的数据关系:SrvNetAllocateBufferFromPool函数返回值return_buffer指向一个内存数据结构,该内存数据结构肇始地址同现实分配内存(函数ExAllocatePoolWithTag分配的内存)肇始地址的的偏移为0x1150;return_buffer+0x18地位指向了现实分配内存肇始地址偏移0x50地位处,而最终return_buffer会作为函数SrvNetAllocateBuffer的返回值。其内存布局关系如图12。
图12 SrvNetAllocateBuffer(0xf)返回的内存数据布局
缝隙内存粉碎分析
回到缝隙解压函数Srv2DecompressData,在进行内存分配之后,Srv2DecompressData挪用函数SmbCompressionDecompress起头解压被压缩的数据。其函数逻辑如图13所示。
图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所示。
图14 解压拷贝过程
图15解压实现后内存布局
持续看Srv2DecompressData的后续处置流程,解压成功后,函数判断offset的了局不为0。不为0则进行内存移动,内存拷贝的参数如下:
memmove(*(alloc_buffer+0x18),SMB_payload,offset)
此时,alloc_buffer+0x18已经指向验证法式的tokenAddr内核地址,而SMB_payload此时指向evilData中的权限数据,offset则为0x10。因而,这个内存移动实现后,权限数据将写入tokenAddr处。这意味着,SMS Server成功批改了验证法式的权限,从而实现了验证法式的提权!
还有一个细节必要把稳,在解压时,Srv2DecompressData函数会判断现实的解压后数据大幼FinalUnCompressedSize是否和数据包中原始数据大幼OriginalCompressedSegmentSize一致,如图16所示。
图16 Srv2DecompressData查抄压缩数据大幼
按理来说现实解压后的数据大幼为0x1100,不蹬宗数据包中的原始压缩数据大幼0xffffffff,这里应该进入到后面内存开释的流程。然而,现实上在函数SmbCompressionDecompress中,挪用RtlDecompressBufferEx2成功后会直接将OriginalCompressedSegmentSize赋值给FinalUnCompressedSize。这也是该缝隙关于肆意地址写入成功的关键之一。
图17 SmbCompressionDecompres赋值FinalUnCompressedSize
缝隙建复建议
CVE-2020-0796是内存粉碎缝隙,精心利用可导致远程代码执行,同时网络上已经出现该缝隙的本地提权势用代码。在此,建议受影响版本Windows用户实时凭据微软官方缝隙防护布告对该缝隙进行防护。
参考链接:
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


京公网安备11010802024551号