导语:在Shadow Brokers公开的NSA黑客武器库中,Eternalromance (永恒浪漫) 是影响Windows全平台的SMBv1漏洞攻击工具,现已被微软补丁MS17-010修复。
在Shadow Brokers公开的NSA黑客武器库中,Eternalromance (永恒浪漫) 是影响Windows全平台的SMBv1漏洞攻击工具,现已被微软补丁MS17-010修复,Windows XP和2003等不在微软支持期的系统版本没有补丁,相关用户可以下载使用360“NSA武器库免疫工具”对漏洞进行免疫,能够预防Eternalromance的攻击。
360Vulcan Team的@pgboy1988和@zhong_sf对Eternalromance使用的漏洞进行了技术分析。
1 环境
EXPLOIT:
Eternalromance-1.3.0
TARGET:
windows xp sp3
FILE:
srv.sys 5.1.2600.5512
2 Exploit使用
我们可以发现工具包中有两个Eternalromance, 一个1.4.0, 另外一个是1.3.0。经过我一翻折腾也没有把1.4.0跑起来。无奈试了下1.3.0发现竟然能成功运行。因此便有了这篇分析。大家可能都会用fuzzbunch这个命令行了。但是我们调试的时候总不能调一次都要重新输入TargetIp等那些配置吧。告诉大家一个省劲的方法。首先fuzzbunch按正常流程走一遍。在最后跑起exp的时候,在Log目录下会生成一个xml的配置文件。然后大家就可以用Eternalromance.1.3.0 –inconfig “xml路径” 来调用了。还有就是你也可以改exploit下的那个配置文件不过你得填非常多的参数。
3 基础知识
3.1 SMB Message structure
SMB Messages are divisible into three parts:
* fixed-length header
* variable length parameter block
* variable length data block
header的结构如下:
更详细见 (https://msdn.microsoft.com/en-us/library/ee441702.aspx)
3.2 SMB_COM_TRANSACTION (0x25)
为事务处理协议的传输子协议服务。这些命令可以用于CIFS文件系统内部通信的邮箱和命名管道。如果出书的数据超过了会话建立时规定的MaxBufferSize,必须使用SMB_COM_TRANSACTION_SECONDARY命令来传输超出的部分:SMB_Data.Trans_Data和SMB_Data.Trans_Parameter。这两部分在初始化消息中没有固定。
如果客户端没有发送完所有的SMB_Data.Trans_Data,会将DataCount设置为小于TotalDataCount的一个值。同样的,如果SMB_Data.Trans_Parameters没有发送完,会设置ParameterCount为一个小于TotalParameterCount的值。参数部分优先级高于数据部分,客户端在每个消息中应该尽量多的发送数据。服务器应该可以接收无序到达的SMB_Data.Trans_Parameters 和 SMB_Data.Trans_Data,不论是大量还是少量的数据。
在请求和响应消息中,SMB_Data.Trans_Parameters和SMB_Data.Trans_Data的位置和长度都是由SMB_Parameters.ParameterOffset、SMB_Parameters.ParameterCount,SMB_Parameters.DataOffset和SMB_Parameters.DataCount决定。另外需要说明的是,SMB_Parameters.ParameterDisplacement和SMB_Parameters.DataDisplacement可以用来改变发送数据段的序号。服务器应该优先发送SMB_Data.Trans_Parameters。客户端应该准备好组装收到的SMB_Data.Trans_Parameters和SMB_Data.Trans_Data,即使它们是乱序到达的。
The PID, MID, TID, and UID MUST be the same for all requests and responses that are part of the same transaction.
更详细看 (https://msdn.microsoft.com/en-us/library/ee441489.aspx)
3.3 SMB_COM_TRANSACTION_SECONDARY(0x26)
此命令用来完成SMB_COM_TRANSACTION中未传输完毕数据的传输。在请求和响应消息中,SMB_Data.Trans_Parameters和SMB_Data.Trans_Data的位置和长度都是由SMB_Parameters.ParameterOffset、SMB_Parameters.ParameterCount,SMB_Parameters.DataOffset和SMB_Parameters.DataCount决定。另外需要说明的是,SMB_Parameters.ParameterDisplacement和SMB_Parameters.DataDisplacement可以用来改变发送数据段的序号。服务器应该优先发送SMB_Data.Trans_Parameters。客户端应该准备好组装收到的SMB_Data.Trans_Parameters和SMB_Data.Trans_Data,即使它们是乱序到达的。
更详细看 (https://msdn.microsoft.com/en-us/library/ee441949.aspx)
3.4 SMB_COM_WRITE_ANDX (0x2F)
结构如图:
更详细看 (https://msdn.microsoft.com/en-us/library/ee441848.aspx)
3.5 总结下漏洞相关的重点
1. 客户端处理SMB_COM_TRANSACTION命令的时候如果数据大小超过MaxBufferSize,则需要使用SMB_COM_TRANSACTION_SECONDARY传输剩下的数据。
2. 对于作为同一Transcation部分的所有请求和响应,PID,MID,TID和UID必须相同。
4 漏洞分析
4.1 SrvSmbTransactionSecondary
结合上一节的重点中提到的。
如果我们先发送了一个数据大小大于MaxBufferSize的SMB_COM_TRANSACTION数据包。那么接下来肯定是要发送SMB_COM_TRANSACTION_SECONDARY来传输剩余的数据,那么服务器在收到处理SMB_COM_TRANSACTION_SECONDARY这个命令的时候肯定会找他对应的那个Transcation。同时服务器也可能同时收到很多个包含SMB_COM_TRANSACTION_SECONDARY命令的数据包。怎么定位某个MB_COM_TRANSACTION_SECONDARY数据包对应的SMB_COM_TRANSACTION数据包呢?重点里也提到了。如果MB_COM_TRANSACTION_SECONDARY数据包中的PID,MID,TID和UID和SMB_COM_TRANSACTION数据包中的相同,那么就认为是同一部分的请求。
代码是这么实现的。
这个SMB_COM_TRANSACTION_SECONDARY命令的处理函数的大体流程就是首先查找SMB_COM_TRANSACTION_SECONDARY对应的SMB_COM_TRANSACTION如果找到就将SMB_COM_TRANSACTION_SECONDARY中的Parameter和Data都复制到SMB_COM_TRANSACTION中去。
4.2 SrvFindTransaction
下面来看下查找的逻辑SrvFindTransaction:
大家可以看下逻辑。重点在这里如果命令是SMB_CMD_WRITE_ANDX(0x2f)话那么MID的对比就不一样了,这里是用Transaction->MID和SMB_CMD_WRITE_ANDX->Fid比较。如果一样返回pTransaction。那么问题来了。如果服务器端正好有一个其他的Transaction->MID恰好和SMB_CMD_WRITE_ANDX->Fid的相等那么将会返回一个错误的pTransaction。很经典的类型混淆。
处理SUM_CMD_WRITE_ANDX的函数SrvSmbWriteAndX代码如下:
看到pTrans_->DataBuffer += Size这句相信大家就能明白了。这里将DataBuffer的指针增大了。再处理此Transcation的SMB_COM_TRANSACTION_SECONDARY命令的时候也就是SrvSmbTransactionSecondary中复制Data的memcpy可就越界了!!!!!
所以此漏洞可以总结成类型混淆造成的越界写。
4.3 Exploit数据包
通过对Exploit抓包我们可以看到其漏洞触发过程。
首先发送SMB_COM_TRANSACTION命令创建一个TID=2049 PID=0 UID=2049 MID=16385(0x4001)的Transcation:
然后发送SMB_CMD_WRITE_ANDX命令还增加pTrans_->DataBuffer这个指针:
5 漏洞利用
从上面描述可以看出,该漏洞为类型混淆导致的越界写漏洞。前期通过spray 使多个TRANSACTION相邻,然后让其中一个TRANSACTION触发该漏洞,再通过该TRANSACTION越界写其相邻的TRANSACTION
spary 最终 memory view:
spray的目的是构造出下出三个相邻的transaction, 其中write_transaction 主要用于写操作,leak_transaction 主要用于信息泄露,而control_transaction为触发漏洞的transaction,触发之后其他InData字段会增加0x200, 以致于可写的范围可以向后延伸0x200.利用于这点可以写与其相依的write_transaction的InData字段。从面达到任意地址写的效果。
注: 本次调试中 control_transaction地址为:0x58b90, write_transaction地址为: 0x59c38, leak_transaction地址为:0x5ace0
其中TRANSACTION 结构部份如下:
写操作:
读操作:
从exp运行的log可以看出该漏洞利用分为两部份:信息泄露 与 代码执行
5.1 信息泄露
需要泄露的信息包括 Transaction2Dispatch Table基址 与 一块NonePagedPool Buffer地址. 通过修改Transaction2Dispatch Table中的函数指针来执行 shellcode, 其中NonePagedPool Buffer就是用于存放shellcode.
5.1.1 泄露Transaction2Dispatch Table基址
从exp运行的log可以看出首先泄露CONNECTION结构基址:CONNECTION地址存放于TRANSACTION->Connection字段。看到这,你可能已经想到该怎么做了:直接利用constrol transaction的0x200字节的越界能力修改write_transaction的DataCount字段让其可以越界读leak_transaction上的内容,从而读出TRANSACTION->Connection。 但exp作者却并没有这么做,这里并不打算深究其原因,或许是有其他限制,或许不是。
作者这里利用了另一种复杂不少方法,通过另一种方法修改 write_transaction的DataCount字段。
5.1.1.1 write_transaction初始状态如下:
可以看出CONNECTION地址为:89a29c18,OutData为0x5acd4 (== 59c38+0x109c)已经是write_transaction的末尾,所以其DataCount为0,表示不可读。
5.1.1.2 修改write_transaction->DataCount
首先 修改write_transaction的InSetup为0x23,这点通过control_transaction很容易完成。之后发包触发写write_transaction操作,会走到:
由于之前已经将write_transaction的InSetup修改为0x23, 所以会call SrvPeekNamedPipe。
SrvPeekNamedPipe() 调用IoCallDriver最终调到 RestartPeekNamedPipe()函数
该函数最终会修改Transaction->DataCount 为 0x23c。
至此,已经成功修改了write_transaction的DataCount值,之后便可以越界读出 leak_transacion->Connection值: 89a29c18。
5.1.1.3 SRV global data pointer
其中srv!SrvGlobalSpinLocks+0x3c 就是所谓的 SRV global data pointer :b76d8bec
5.1.1.4 Locating function tables
5.1.2 泄露Npp Buffer (shellcode buffer)
这里又得回到ExecuteTransaction函数:
这在这个函数里有这么一个逻辑,当transaction->OutParameters==NULL里,会将PWORK_CONTEXT->ResponseHeader加上一定的offset赋于它,PWORK_CONTEXT->ResponseHeader就是个NonePagedPool.
5.1.2.1 transaction->OutParameters=NULL
Transaction 初始状态下OutParameters并不为NULL:
这里通过write_transaction越界 写 leak_transaction->OutParameters为NULL, 然后发包触发写leak_transaction操作,之后leak_transaction->OutParameters便为一 Npp Buffer值了。
5.1.2.2 leak_transaction->OutData = &leak_transaction->OutParameters
这里要事先泄露leak_transaction的基址,其实也不难,通过读leak_transaction的OutData 或 OutParameters 或 InData 字段的值再减去一定的偏移便得到了基址,使leak_transaction->OutData = &leak_transaction->OutParameters之后,发包触发leak_transaction读操作便将该Npp buffer地址泄露出来了。
5.1.2.3 写shellcode到Npp Buffer
将control_transaction->OutData设为Npp Buffer+0x100地址,然后发包发送shellcode,便将shellcode写到了Npp Buffer+0x100内。
5.2 代码执行
至此,直接将Npp buffer+0x100写到之前泄露出来的函数表里
之后发包就能触发该函数调用:
6 关于补丁
了解了漏洞原理之后修补都很简单了。只要在srv!SrvFindTransaction里面判断一下SMB COMMAND的类型是否一致就好了。
修补前
补丁点就是if ( Command_Trans != Command_header )看注释的地方。
7 总结
总之,这个漏洞还是非常好的,远程任意地址写,还可以信息泄露。威力很大。
8 联系作者