(部分排版不是很美观,预览的时候没有问题,发出来表格会有点乱,不知道为什么:))
Server Message Block Protocol,服务器信息块协议(SMB),为网络计算机客户程序提供一种从服务程序读写文件并请求服务的方法。SMB协议可在互联网的TCP/IP协议或者互联网数据包交换和NetBEUI等协议之上使用。使用SMB协议,应用程序可访问远程服务器的文件以及打印机、信槽和命名管道等资源。因而,客户程序可以读、写以及更新远程计算机上的文件,它也可以跟接收SMB客户请求的任意服务程序通信。
SMBv2是SMB协议的第二版本,相较SMBv1做了诸多扩展,部分数据包结构发生了变化,但仍保留了SMBv1的部分基本特征。
该漏洞位于SMB Server的SMBv2协议部分,攻击者可以通过构造恶意的SMBv2请求来触发该漏洞,成功触发该漏洞后可以实现远程代码执行。
• Microsoft Windows 7 • Microsoft Windows 8 • Microsoft Windows 8.1 • Microsoft Windows 10 • Microsoft Windows RT • Microsoft Windows RT 8.1 • Microsoft Windows Server 2008 • Microsoft Windows Server 2008 R2 • Microsoft Windows Server 2012 • Microsoft Windows Server 2012 R2 • Microsoft Windows Server 2012 R2 (Server Core) • Microsoft Windows Server 2016 • Microsoft Windows Server 2019 • Microsoft Windows Server version 1709 (Server Core Installation) • Microsoft Windows Server version 1803 (Server Core Installation)
微软官方针对该漏洞已发布安全更新补丁,补丁地址:
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0630
靶机操作:在C盘根目录下创建test目录,并开启SMB共享(可以为其他位置和名称,但需要修改poc代码)
kali中直接执行:
成功执行后,靶机崩溃:
备注:如果靶机无法成功崩溃,多执行几次poc。
SMBv2具备将多个操作组合到单个请求中的功能,支持更大的缓冲区大小,SMB连接可以在短暂的网络中断中保持状态,支持符号链接以及其他改进。SMBv2与SMBv1一样,其开头也是一个NetBIOS Sessioni Service
信息块,NetBIOS Sessioni Service
信息块的结构如下:
SMBv2消息由固定长度的SMB2_Header
和变长的SMB2_Data
组成。SMB2_Header
有2种变体:ASYNC
和SYNC
,二者主要取决于Flags
字段的SMB2_FLAGS_ASYNC_COMMAND
位是否被设置为1,如果为1则表示ASYNC
,用于响应服务器异步处理的请求,否则表示SYNC
。以下为一个SYNC/ASYNC SMB2_Header
结构(小端序传输):
Command
字段详细内容:
Flags
字段详细内容:
当Command
字段值为0x05时表示一个SMB2_CREATE
请求,该请求由client发送到server,为了创建或访问文件。在命名管道或打印机场景下,server必须创建一个新文件。该请求具备SMB2 Packet Header标准结构,然后跟Data部分,其Data部分结构如下:
RequestedOplockLevel
字段的可选值如下:
ImpersonationLevel
字段的可选值如下:
CreateDisposition
中包含的可选值:
CreateOptions
中包含的详细内容:
Pathname
可以由一个或多个路径名组成,其间以'\'分割,最后一个路径名包含目录或文件名。
文件名可以包含一个stream,在Windows的NTFS系统中,stream主要用于描述一些与文件相关的信息,例如文件创建者,搜索该文件的关键词等,stream中包含的信息要比文件自身的属性中包含的信息全面。 与文件关联的每个stream都有其自己的分配大小,实际大小和有效数据长度 :
Naming Coventions for Streams
当在Windows的命令行中指定文件时,一个完整的stream名称如下:
client发送SMB2 SET_INFO
请求数据包以设置有关文件或基础对象存储的信息,该message同样包含一个标准的的SMB2_Header,然后包含以下数据结构:
InfoType
字段的可选值:
AdditionalInformation
字段可选值如下:
Buffer
字段详细可选值:
当前面Buffer
字段包含一个FileRenameInformation
类时,InfoType
字段的值为SMB2_0_INFO_FILE(0x01)
,FileInfoClass
值为0x0a。FileRenameInformation
类用于重命名一个文件,其格式如下所示:
漏洞出现原因主要为由于对filename长度验证不完善造成。其主要过程如下:
在通过SMBv2重命名文件时,原有文件命名为OldFileName
,首先通过发送SMB2_CREATE
请求消息到server,如果请求的文件存在则server会返回一个SMB2_CREATE
响应消息,该消息中包含名为FileGUID
的文件标识符;
client发送一个SMB2_SET_INFO
请求消息,其中FileID
字段为FileGUID
,Buffer
字段为FileRenameInformation
中的新文件名。一旦该请求被server接收,server就会通过Smb2ExecuteSetInfoReal()
函数调用Smb2UpdateLeaseFileName()
函数;
Smb2UpdateLeaseFileName()
函数会检查FileNameLength
字段是否小于0xffff。如果小于,会检查FileName
字段的值是否以":"开头,以确认字段中是否包含stream。如果包含stream,该函数遍历OldFileName
并检查OldFileName
的最后一个路径名是否包含stream。如果发现OldFileName
包含一个stream,函数会移除stream字符串,并将剩余的前缀字符串存储到名为prefixFileName
的变量中,其长度为prefixFileNameLen
;
Smb2UpdateLeaseFileName()
函数没有prefixFileNameLen
进行验证,直接按照以下公式计算新文件名的长度:
FileLengthVar
类型为无符号short类型。
Smb2UpdateLeaseFileName()
函数检查计算得到的FileLengthVar
是否大于OldFileName
的unicode形式的长度,称为unicodeFileLen
。如果大于,函数创建一个大小为FileLengthVar
的buffer并将prefixFileName
和FileName
拷贝到buffer中;如果FileLengthVar
小于等于unicodeFileLen
,函数创建的buffer大小变为unicodeFileLen
,并进行复制操作。
以上函数处理过程可简单描述为如下流程图:
函数在计算FileLengthVar
的长度之前并没有对prefixFileNameLen
进行验证,如果prefixFileNameLen
为一个任意大小的值,那么就可能导致FileLengthVar
大于0xffff,从而发生整数溢出。在这种情况下,FileLengthVar
会小于unicodeFileLen
,那么在后续进行内存分配时分配的内存大小为unicodeFileLen
。但是在进行复制操作时,复制的数据为prefixFileName
和FileName
,其长度和大于unicodeFileLen
,导致在进行copy操作时发生溢出。
一个stream允许的最多字符数量为0x200(包含":"符号在内),那么FileNameLength
字段的最大值为0x400 = 0x200 * 2
,为触发该漏洞,SMB2_CREATE
消息中的NameLength
字段的值必须大于0xfbff(0xffff - 0x400)。
反汇编存在漏洞的文件:srv2.sys
,来自Windows Server 2012 R2 X64,版本为6.3.9600.17396
。
首先处理相关参数和信息:
然后调用存在漏洞的Smb2UpdateLeaseFileName()
函数:
首先检查FileNameLength
是否小于0xFFFF:
如果小于0xFFFF,则进行循环搜索:
符号:
然后按照之前的公式进行计算,而且可以明显看出,在进行计算之前并没有进行任何的数据验证,导致漏洞存在:
最后,进行缓冲区分配和copy操作:
简单总结以上过程,真正的触发点在于FileLengthVar
没有进行数据检查,如果超过0xFFFF,则未通过if判断,从而分配的缓冲区大小为unicodeFileLen
,但copy的数据为prefixFileName
和FileName
,其长度和大于unicodeFileLen
,导致在进行copy操作时发生溢出:
因为确认了出问题的函数,所以在Smb2ExecuteSetInfoReal()
函数下断,发送poc,断下:
找到调用Smb2UpdateLeaseFileName()
函数处,下断,执行:
Smb2UpdateLeaseFileName()
函数有3个参数,第一个参数rcx中存放的是FileInfoClass
,偏移为0x03,主要用于定位栈中数据使用(猜测);第2个参数rdx中存放的是我们poc中发送的用于修改的FileName
的具体内容,以:
开头;第3个参数r8中存放的是FileNameLength
,恰好为我们poc中构造的512个字节(包含:
)。自此,进入函数后将开始进行文件重命名过程:
这里对新的FileName
的长度做了校验,要求小于0xFFFF。然后,小写转大写:
转换完成:
检查新的FileName
是否以:
开头:
因为在新的FileName
中找到了:
字符,所以转而在OldFileName
中查找:
字符,以便进行后续处理:
这里获取到OldFileName
的:
字符前面部分的长度prefixFileNameLen = 0x7f04
:
然后按照公式
计算重命名后新的完整的文件名的长度:
在进行计算时,使用的寄存器为bp,prefixFileNameLen
长度为0x7f04,FileNameLength
长度为0x200,所以最终的计算结果为0x10008。但是因为使用bp进行最后计算结果的存储,所以发生整数溢出,导致最后结果变为0x8。
然后,进行需要分配的缓冲区大小的计算:
在进行内存分配时,根据不同的计算结果有2种不同的内存分配的路径:
开始进行copy操作,使用的函数为memcpy()
:
执行到这里的前提是计算得到的FileNameLengVar
小于unicodeFileNameLen
,但是原来的OldFileName
的stream长度为1,现在新增的stream长度为0x200,明显会发生溢出,会覆盖掉rcx指向的地址下面的数据。
进行copy前rcx指向的地址空间的数据布局如下:
而在执行完copy操作后,rcx指向的地址的内存布局变成了如下所示,多余的0x1fe个字节覆盖了原来的一些地址和数据,很明显会影响到后续程序的正常运行:
然后直接运行,程序发生crash,根据栈回溯可以获取一些信息:
因为在重命名文件时发生了缓冲区溢出,覆盖掉了一些关键的地址或者参数,而这些被覆盖的数据可能正是后续函数需要使用的数据。根据栈回溯信息,初步判断是影响了CloseFile的相关函数。
基本条件
触发过程
attacker首先建立SMBv2连接,包括 Negotiate Protocol,Session Setup, Tree Connect messages的交换:
attacker通过发送SMB2_CREATE
消息创建并打开文件:
attacker通过SMB2_SET_INFO
消息来重命名文件:
应用协议:SMB/CIFS
,端口:139/445 TCP
该漏洞的poc涉及到4个py文件,ntlm.py
,securityblob.py
以及pyDes.py
来源于pysmb
,主要用于SMBv2的验证部分,而poc.py
则会执行漏洞触发过程,执行成功后会导致BSOD。攻击利用的PoC核心代码:
Smb2UpdateLeaseFileName()
函数的补丁对比结果:
函数并没有做过多修改,进入查看具体更新内容:
根据补丁比较结果可以看出,微软针对该漏洞的修复方式是对计算获得的FileLengthVar
的长度进行验证,在小于0xFFFE
的情况下再进行后续的缓冲区分配操作:
针对该漏洞,目前很难做到无损检测
流量防御:因为真正触发漏洞的数据为SMB2_CREATE
消息中的NameLength
字段设置的值过大,才导致了在后续进行计算时发生错误。因此,可以简单检测NameLength
字段中的数据是否大于0xFBFF(0xFFFF - 200 * 2)来进行判断。
终端防御:使用热补丁,改变程序执行流。
因为检测的字段并不一定可以触发漏洞,倘若正常流量的创建文件名长度大于了0xFBFF(情况应该很少),且后续不再进行重命名的操作,那么就不会触发漏洞。所以,存在误报风险。
看雪论坛内部共享,未经允许,请勿转载,谢谢
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-8-8 13:04
被有毒编辑
,原因: