分析ms17-010的文章很多,但都是点到为止,并没有深入分析,网上能找到最详细的分析文章我认为是国外的安全研究员写的https://github.com/worawit/MS17-010/blob/master/BUG.txt
国内研究员翻译
该文章写的非常详细,这里写下自己理解到的漏洞利用原理。
一、理解文章[https://github.com/worawit/MS17-010/blob/master/BUG.txt
首先我们要了解漏洞相关的SMB transaction协议,通过微软官方文档https://msdn.microsoft.com/en-us/library/ee441702.aspx
SMB消息由SMB_header,SMB_Parameters,SMB_Data组成,SMB共75种命令,与transaction子命令相关的有6种
SMB_COM_TRANSACTION(命令码为0x25)
SMB_COM_TRANSACTION_SECONDARY(命令码为0x26)
SMB_COM_TRANSACTION2(命令码为0x32)
SMB_COM_TRANSACTION2_SECONDARY(命令码为0x33)
SMB_COM_NT_TRANSACT(命令码为0xA0)
SMB_COM_NT_TRANSACT_SECONDARY(命令码为0xA1)
当transaction消息比SMB消息大(通过会话中的maxbuffersize参数判断)时,那么客户端必须使用1个或多个的SMB_COM_TRANSACT_SECONDARY命令跟在SMB_COM_TRANSACT之后(这些命令和transaction消息使用相同的TID,UID,PID,MID)去发给transaction消息。
SMB_Header主要定义了各种标志码
SMB_Header
{
UCHAR Protocol[4];
UCHAR Command; //命令码
SMB_ERROR Status;//错误信息
UCHAR Flags; //
USHORT Flags2;
USHORT PIDHigh;
UCHAR SecurityFeatures[8];
USHORT Reserved;
USHORT TID; //tree id
USHORT PIDLow;/进程ID
USHORT UID; //用户ID
USHORT MID; //multiplex ID
}
SMB_Parameters主要定义了各种传递的参数,不同命令smb_parameters结构不一样当命令码为0x25即SMB_COM_TRANSACTION命令对应的smb_parameters如下
SMB_Parameters
{
UCHAR WordCount;
Words
{
USHORT TotalParameterCount;
USHORT TotalDataCount;
USHORT MaxParameterCount;
USHORT MaxDataCount;
UCHAR MaxSetupCount;
UCHAR Reserved1;
USHORT Flags;
ULONG Timeout;
USHORT Reserved2;
USHORT ParameterCount;
USHORT ParameterOffset;
USHORT DataCount;
USHORT DataOffset;
UCHAR SetupCount;
UCHAR Reserved3;
USHORT Setup[SetupCount]; //3种Transaction子命令的该字段略有不同
}
}
SMB_Data主要包含了用于服务端操作的参数和数据
SMB_Data
{
USHORT ByteCount;
Bytes
{
SMB_STRING Name;
UCHAR Pad1[];
UCHAR Trans_Parameters[ParameterCount];
UCHAR Pad2[];
UCHAR Trans_Data[DataCount];
}
}
SMB中用来处理命令码为transaction的数据包时,会在分页池缓冲区申请内存用来存放接收/发送的数据。
经过等数据包的处理后,内存中TRANSACTION结构分布如下
| TRANSACTION | transaction data buffer |
transaction数据缓存总是跟在transaction结构之后,而transaction结构体中重要的成员如下:
- InSetup : 指向transaction缓冲区中的接收setup指针
- OutSetup : 指向transaction缓冲区中的回应setup指针 (在所有transaction数据接收完但是还不在transaction缓冲区时设置)
- InParameters : 指向transaction缓冲区接收参数的指针
- OutParameters : 指向transaction缓冲区回应参数的指针
- InData : 指向transaction缓冲区接收数据的指针
- OutData : 指向transaction缓冲区回应数据的指针
- SetupCount : transaction 请求中的setup元素个数(这决定InSetup buffer的大小)
- MaxSetupCount : transaction 回应中的setup元素最大字节(这决定outsetup buffer的大小)
- ParameterCount : 目前接收参数的字节或回应参数的个数
- TotalParameterCount : transaction请求中发生送的参数字节总数(这决定InParameters buffer的大小)
- MaxParameterCount : transaction回应中客户端能接收的最大参数字节总数(这决定OutParameters buffer大小)
- DataCount : 当前接收到的数据字节数或要发送的回应数据的字节数
- TotalDataCount : 在这个transaction请求中发送的数据字节总数
(这决定InData buffer大小)
- MaxDataCount : 客户端在transaction回复中接受的最大数据字节数。 这个决定了数据缓冲区的大小。(这决定OutData buffer大小)
- Function : NT transaction子命令码.
- Tid : The transaction Tid.
- Pid : The transaction Pid.
- Uid : The transaction Uid.
- Mid/Fid : The transaction Mid.
- AllDataReceived : 当ParameterCount == TotalParamterCount && DataCount == TotalDataCount时,布尔值为1 .
而transaction数据缓存区布局如下:
第一种:除TRANS_MAILSLOT_WRITE和SetupCount字段置为0的”TRANS“数据包外,其它SMB_COM_TRANSACTION数据包,IN和OUT缓冲区是重叠的(InSetup指针和OutParameters指针指向同一内存地址)
+---------------+------------------------------------------------------+
| TRANSACTION | transaction data buffer |
+---------------+------------------------------------------------------+
| InSetup | InParameters | InData | |
+------------------------------------------------------+
| OutParameters |OutData |
+------------------------------------------------------+
第二种:除第一种情况外的其它SMB_COM_TRANSACTION数据包和所有SMB_COM_TRANSACTION2数据包的内存布局如下所述,所有缓冲区都不重叠。
+---------------+-------------------------------------------------------------------+
| TRANSACTION | transaction data buffer |
+---------------+-------------------------------------------------------------------+
| InSetup | InParameters | InData | OutParameters | OutData |
+-------------------------------------------------------------------+
第三种:SMB_COM_NT_TRANS数据包的内存布局如下所述,InParameters和OutParameters之间、InData和OutData之间的缓冲区都是重叠的。
+---------------+-----------------------------------------------------------+
| TRANSACTION | transaction data buffer |
+---------------+-----------------------------------------------------------+
| InSetup | InParameters| InData ||
+---------+----------------------+--------------------------+
| | OutParameters|OutData |
+-----------------------------------------------------------+
大概了解了transaction协议后,作者通过补丁对比发现了9个bug,我们来分析在NSA泄露工具中被用到bug。
===========
Bug1: Uninitialized transaction InParameters and InData buffer
没有被用到
===============
Bug2: TRANS_PEEK_NMPIPE transaction subcommand expects MaxParameterCount to be 16
SrvPeekNamedPipe()用来处理TRANS_PEEK_NMPIPE subcommand子命令,它会将命名管道数据trans_data存在OutParameters缓冲区中,具体位于OutParameters+16的内存位置。如果MaxParameterCount参数是16,Outdata会指向正确的管道数据,但是如果恶意修改数据包中的MaxParameterCount使其大于16。由于OutParameter和OutData相邻,outdata 指针原本指向outparameters+MaxParameterCount(16)即outdata,而如果将MaxParameterCount(16)修改为大于outdata的长度,就可以读取到outdata以外的数据。
这个漏洞并没有在NSA泄露工具中被用到,但是可以用来检测客户端是否已经打了MS17-010补丁。
我们可以构造一个TRANS_PEEK_NMPIPE命令请求数据包,当数据包中的参数MaxParameterCount、MaxDataCount满足二者之和大于0x1040且MaxDataCount+16小于0×10400,在没有安装ms17-010补丁时会返回0xC0000205错误码,而如果漏洞已经修复则返回的错误码就不是0xc0000205,而是由Insetup决定。可以通过相应数据包是否返回0xc0000205错误码来判断补丁是否被修补(可以在checker.py中看到利用该BUG检测补丁安装情况)
===============
Bug6: Transaction secondary can be used with any transaction type
由于服务端通过secondary数据包判断transaction命令类型,之后决定使用哪种函数来处理transaction数据
===============
Bug7: Wrong type assigment in SrvOs2FeaListSizeToNt()
SrvOs2FeaListSizeToNt()函数处理客户端的SMB_COM_TRANSACTION2请求时,会将其FEA_LIST转换为FILE_FULL_EA_INFORMATION数据结构并为其分配缓冲区。FILE_FULL_EA_INFORMATION结构中计算缓冲区长度时,计算出来的缓冲区长度值为word类型(最大0x0000FFFF),因此在mov操作时寄存器eax高位不变,而原本eax高位为1最后导致计算出来的缓冲区长度为(0x0001FFFF)远远超过FFFF导致缓冲区溢出
(word)feasize end=(word)feaend-(word)feastart
feaend esi=a4217035=v3
feastart eax=a42070d8=v1
sub esi,eax 此操作后esi=FF5D
mov word ptr [eax],si [eax]原本值为(0x00010000)此操作后[eax]=0x0001FF5D而有效的[eax]应该为0x0000FF5D
此漏洞发生在SrvOs2FeaListSizeToNt()函数处理客户端的SMB_COM_TRANSACTION2请求时,但SMB_COM_TRANSACTION2命令中totaldatacount字段类型为ushort(最大为0XFFFF),这造成从客户端接收到的transaction数据被分配到缓冲区时造成缓冲区溢出
但是SMB_COM_TRANSACTION2命令能发送的最大transaction数据为0xFFFF(totaldatacount字段为USHORT)
===============
Bug9: SESSION_SETUP_AND_X request format confusion
SMB_COM_SESSION_SETUP_ANDX请求有两个格式,两种格式中的wordcount字段是不同的,
而负责处理SMB_COM_SESSION_SETUP_ANDX请求的BlockingSessionSetupAndX()函数伪代码如下:
BlockingSessionSetupAndX()
{
// …
// check word count
if (! (request->WordCount == 13 || (request->WordCount == 12 && (request->Capablilities & CAP_EXTENDED_SECURITY))) ) {
// error and return
}
// ...
if ((request->Capablilities & CAP_EXTENDED_SECURITY) && (smbHeader->Flags2 & FLAGS2_EXTENDED_SECURITY)) {
// this request is Extend Security request
GetExtendSecurityParameters(); // extract parameters and data to variables
SrvValidateSecurityBuffer(); // do authentication
}
else {
// this request is NT Security request
GetNtSecurityParameters(); // extract parameters and data to variables
SrvValidateUser(); // do authentication
}
// ...
}
如果发送Extended Security的SMB_COM_SESSION_SETUP_ANDX请求(WordCount为12)。数据包中含有CAP_EXTENDED_SECURITY,但没有FLAGS2_EXTENDED_SECURITY。服务端会将其当作NT Security请求(WordCount为12)来处理。到服务端从数据包中提取parameters和data时,导致从错误位置读取bytecount(用户可以操控bytecount),而bytecount是用来计算存储NativeOS和NativeLanMan unicode string (UTF16)缓冲区大小的。
首先永恒之蓝利用bug9发送SMB_COM_SESSION_SETUP_ANDX请求(WordCount为12)且含有CAP_EXTENDED_SECURITY,但没有FLAGS2_EXTENDED_SECURITY。来实现堆喷射技术构造写有shellcode和nop的堆空间
由于只有SMB_COM_NT_TRANS请求的TotalDataCount为4个字节(最大FFFFFFFF),SMB_COM_TRANSACTION2请求的TotalDataCount都为2个字节(最大FFFF)。为保证bug7中传递的transaction数据大于0xFFFF先利用bug6,发送SMB_COM_NT_TRANS设置4字节的TotalDataCount,之后发送SMB_COM_TRANSACTION2_SECONDARY请求,使服务端触发bug7,而由于bug7中缓冲区溢出,结构体指针被覆盖为指向高位地址的恶意构造的堆空间。最后客户端使用SMB处理函数指针时EIP被非法篡改指向SHELLCODE,实现远程代码执行。