0%

ms17-010漏洞分析

分析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,实现远程代码执行。