受影响版本:

系统 版本
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)处

最新文章

  1. CRM Setstate plugin
  2. 处理字符串-String类和正则表达式
  3. PYTHON 集合set 方法
  4. 测试杂感:Bug Bash
  5. L18 如何快速查找文档获得帮助
  6. pip的安装及使用
  7. js前端验证时间大小
  8. 实战系列之 Node.js 玩转 Java
  9. 如何解决 touchstart 事件与 click 事件的冲突
  10. Django 系列博客(十六)
  11. 使用Eclipse创建动态的web工程
  12. POJ--3614 Sunscreen(贪心)
  13. python 中r 和 \r
  14. McQueenRPC源码阅读
  15. Jedis使用
  16. Struts2重新学习1
  17. EF三种编程方式详细图文教程(C#+EF)之Code First
  18. SQL查找数据库中所有没有主键的数据表脚本
  19. Linux内核同步 - Read/Write spin lock
  20. Scala简介及基础语法

热门文章

  1. 在nginx下导出数据库数据
  2. LVM创建及管理
  3. Python数据类型--列表(list)
  4. 盐城5138.6118(薇)xiaojie:盐城哪里有xiaomei
  5. php进程 swoole
  6. doment ready事件和load事件的区别及实现
  7. Ⅲ Dynamic Programming
  8. 使用websocket连接(对接)asp.net core signalr
  9. docker部署nginx服务器
  10. okhttp的Post方式