专栏首页玄魂工作室Windows SMB Ghost(CVE-2020-0796)漏洞分析

Windows SMB Ghost(CVE-2020-0796)漏洞分析

本文来自启明星辰订阅号。

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

本文分享自微信公众号 - 玄魂工作室(xuanhun521),作者:启明星辰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 看代码学渗透 Day5 - escapeshellarg与escapeshellcmd使用不当

    --------------------------------------------------------------------------------...

    用户1631416
  • Kali Linux Web渗透测试手册(第二版) - 6.1

    thr0cyte,Gr33k,花花,MrTools,R1ght0us,7089bAt,

    用户1631416
  • 如何学python 第十七课 类-面向对象的概念

    欢迎回来。今天要说的东西将会改变我们写程序的方式。今天我们介绍‘类’(class)。 概述 什么叫‘类’?类,类型。变量类型。从日常生活的感觉来说,‘类’其实...

    用户1631416
  • 红筹企业上科创板新规公布,财务报表披露要“就高不就低”丨科创板报

    科创板又传来新消息,3月8日,证监会发布《公开发行证券的公司信息披露编报规则第24号——科创板创新试点红筹企业财务报告信息特别规定》。其中,着重提到,红筹企业在...

    镁客网
  • 机器学习不神秘!手把手教你用R语言打造文本分类器

    简单安装几个R软件包,你就直接在自己电脑上打造出一个文本分类器,用进行机器来评估人类写作。 本文是一篇极简的上手教程,它想用清晰明了的步骤告诉读者,机器学习不...

    AI科技大本营
  • 立即执行函数

    这两种格式都能保证函数立马执行,这也是立即函数的基础常见的格式,()运算符加上匿名函数,还有另外几种格式也能立即执行:

    wade
  • 什么数据可以成为“数据资产”?数据资产化又该如何实现?

    数据资产化已成为企业数据资产管理的最重要的环节,怎样识别数据资产、利用现有的数据资产创造价值,将是企业不得不面临的一个课题。

    探码科技
  • BitMap 的基本原理和实现

    本篇是大数据算法系列 第一篇《 BitMap 的原理和实现》,BitMap 的思想的和原理是很多算法的基础,因此我们以 BitMap 开篇。

    木东居士
  • Unity 添加自定义菜单(插件),添加功能

    用户1258909
  • BitMap 的基本原理和实现

    木东居士

扫码关注云+社区

领取腾讯云代金券