CVE-2020-0769逆向分析

22 篇文章 1 订阅
6 篇文章 0 订阅

受影响版本:

系统 版本
Microsoft Windows 10
Windows 10 1607
Windows 10 1709
Windows 10 1803
Windows 10 1809
Windows 10 1903
Windows 10 1909
Windows 7 SP1
Windows 8.1
Windows RT 8.1
Windows Server 2008 SP2
Windows Server 2008 R2 SP1
Windows Server 2012
Windows Server 2012 R2
Windows Server 2016
Windows Server 2019
Windows Server 1803
Windows Server 1903
Windows Server 1909
此漏洞只会影响SMB v3.1.1
客户端与服务端都存在此漏洞
服务端漏洞位于srv2.sys内核模块中,客户端漏洞位于mrxxmb.sys模块

漏洞描述
从win10 1903/win server 1903开始对SMB v3.1.1进行数据压缩的支持
包格式
在这里插入图片描述

翻译后内容

在这里插入图片描述
在这里插入图片描述

此次漏洞触发原因是因为客户端/服务端在进行数据解压是未对OriginalCompressedSegmentSize与Offset/Length 进行合理的长度检查造成的
先来大致梳理一下函数的调用关系:
DriverEntry=>Srv2DeviceControl=>Srv2ProcessFsctl=>Srv2StartDriver=>Srv2StartInstance=>Srv2ReceiveHandler
然后Srv2ReceiveHandler函数会将Srv2DecompressMessageAsync函数放入SLIST_ENTRY链表中进行回调异步调用
在这里插入图片描述

然后Srv2DecompressMessageAsync函数会调用去调用Srv2DecompressData函数
在这里插入图片描述

Srv2DecompressData函数会根据OriginalCompressedSegmentSize与Offset/Length进行内存分配
在这里插入图片描述

_mm_srli_si128函数是一个与XMM寄存器相关的函数,此函数让第一个参数v3逻辑运算向右移8个字节,要注意他的移动单位是字节不是位
此时Size指向ProtocolId,v4指向CompressionAlgorithm,Size偏移1个32位即4字节便是OriginalCompressedSegmentSize,v4偏移一个4字节便是Offset/Length
然后SrvNetAllocateBuffer函数申请内存空间其大小等于OriginalCompressedSegmentSize+Offset而这两个值都是可控的,然后进入SrvNetAllocateBuffer查看如何进行内存分配,要注意此函数与之后要看SmbCompressionDecompress的位于sysnet.sys模块中
在这里插入图片描述

进入SrvNetAllocateBuffe后他会先判断SrvDisableNetBufferLookAsideList是否为真,或者,参数1即要分配的内存大小是否大于0x100100
如果一方成立就进入if在判断参数1是否大于0x100100如果大于的话就返回失败,如果仅仅是SrvDisableNetBufferLookAsideList为真那就调用SrvNetAllocateBufferFromPool进行内存分配,再来看看SrvDisableNetBufferLookAsideList是如何初始化的
在这里插入图片描述

可以看出SrvDisableNetBufferLookAsideList是在函数SrvNetRefreshLanmanServerParameters中进行初始化的

在这里插入图片描述

可以看出SrvDisableNetBufferLookAsideList的值肯定为一个布尔值即真或假,SrvLibGetDWord函数会去调用ZwOpenKey打开注册表键值然后使用ZwQueryValueKey去读取注册表如果读取成功则返回一个指定值,如果读取失败则返回ZwQueryValueKey的返回值即失败原因,在我的系统里没有在注册表找到这个项,所以SrvDisableNetBufferLookAsideList的值默认为false,也就是说SrvNetAllocateBuffer的第一个if正常情况下不会去执行,顺着流程往下走可以看到
在这里插入图片描述

他会先判断参数1是否大于0x1100,然后求出到底用哪个值做SrvNetBufferLookasides的下标来获取内存,如果不大于0x1100则默认下标为0,再来看看SrvNetBufferLookasides是如何初始化的
在这里插入图片描述

进入SrvNetCreateBufferLookasides函数,一直追下去会发现PplCreateLookasideList内部其实还是调用ExInitializeLookasideListEx函数来进行LookasideList列表的初始化,我们直接进入SrvNetBufferLookasideAllocate查看分配了新的LookasideList列表的函数,这里(1<<(v3+12))+256是要分配内存的大小,根据计算此大小依次为[0x900,0x1100,0x2100,0x4100,0x8100,0x10100…0x80100]
在这里插入图片描述

SrvNetBufferLookasideAllocate在内部又调用了SrvNetAllocateBufferFromPool函数
在这里插入图片描述

在SrvNetAllocateBufferFromPool函数中调用了ExAllocatePoolWithTag函数来分配指定类型的内存
在这里插入图片描述

分配大小v7我重命名为size,然后会发现size=v6+v3=(2*(MmSizeOfMdl+8))+(lParam2 + 232)
在这里插入图片描述
在这里插入图片描述

最后要返回的数据我重命名为backdata,刚刚从ExAllocatePoolWithTag函数获取到的数据重命名为ExAllocData
可以看出backdata=&ExAllocData[lParam2+0x57]&0xFFFFFFFFFFFFFFF8ui64
在这里插入图片描述

假设lparam2为0x1100,那0x1100+0x57=0x1157,0x1157&0xFFFFFFFFFFFFFFF8ui64=0x1150,也就是说返回的数据是从ExAllocatePoolWithTag函数获取到的数据的0x1150偏移处开始的
根据上面可以总结出,SrvNetAllocateBuffer函数最后会创建一个‘结构体+数据’这种类型的一块内存,这块内存结构大致如下
在这里插入图片描述

回到srv2.sys中的Srv2DecompressData,在用SrvNetAllocateBuffer申请过内存后会调用SmbCompressionDecompress函数来解压缩数据,此函数也在srvnet.sys中,其本质上是调用RtlDecompressBufferEx2函数来进行数据解压缩的
在这里插入图片描述

这里解释一下几个重要参数,方便与Srv2DecompressData中的传入的参数一一对应

  • CompressionFormat:解压缩算法,此参数不用过多关注,他对应
  • SmbCompressionDecompress的第一个参数
  • UncompressedBuffer:解压后数据存放的缓冲区地址,对应
  • SmbCompressionDecompress的第四个参数
  • UncompressedBufferSize:解压数据缓冲区大小,对
  • SmbCompressionDecompress的第五个参数
  • CompressedBuffer:待解压数据,对应SmbCompressionDecompress的第二个参数
  • CompressedBufferSize:待解压数据大小,对应SmbCompressionDecompress的第三个参数
    返回值便是RtlDecompressBufferEx2函数的返回值
    再回到Srv2DecompressData看看是如何调用SmbCompressionDecompress的
    在这里插入图片描述

可以看出他会从*(_QWORD *)(*(_QWORD *)(v1 + 240) + 24i64) + Size.m128i_u32[3] + 16i64处获取压缩数据,经过解压放入Size.m128i_u32[3] + *(_QWORD *)(backdata + 24)backdata + 24指向刚刚SrvNetAllocateBuffer申请内存的起始位置,在这里也就是将解压后数据放入‘内存起始位置+SMB数据包offset/length’处,第六个参数v11用于接收解压后的数据大小,当SmbCompressionDecompress函数调用失败或者解压后的数据大小与SMB包中OriginalCompressedSegmentSize的值不一致时(不过如果RtlDecompressBufferEx2调用成功的话OriginalCompressedSegmentSize的值就会赋给v11),否则继续往后运行,接着往后看
在这里插入图片描述

这段代码可以解释为,如果offset/length不为0,则从(v1 + 240) + 24i64) + 16i64)处获取数据后放入(v8 + 24)指向的地址,根据分析上面SmbCompressionDecompress函数的调用可知(v1 + 240) + 24i64) + 16i64)大致指向压缩数据内存位置,(v8 + 24)指向内存起始的位置。

由于OriginalCompressedSegmentSize与Offset/Length长度我们可控,且SrvNetAllocateBuffer函数会根据他们俩来申请一块‘数据+结构体’形式的内存,我们可以申请一块较小的内存,将我们想要让重新赋值的某块内存的地址想办法构造payload填充到(v8 + 24)处,然后在momove函数执行时就会将我们想要写入的数据写入到(v8 + 24)处

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值