Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

0
0
【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

2017-04-19 16:06:21
来源:360安全卫士技术博客 作者:360安全卫士

阅读:828次
点赞(0)
收藏





【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

传送门

【漏洞分析】MS 17-010:NSA Eternalblue SMB 漏洞分析

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.5 漏洞相关的重点那里

3.1 SMB Message structure

SMB Messages are divisible into three parts:

* fixed-length header

* variable length parameter block

* variable length data block

header的结构如下:

SMB_Header { UCHARProtocol[4]; UCHARCommand; SMB_ERRORStatus; UCHARFlags; USHORTFlags2; USHORTPIDHigh; UCHARSecurityFeatures[8]; USHORTReserved; USHORTTID; USHORTPIDLow; USHORTUID; USHORTMID; }

更详细见 (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)

结构如图:


【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

更详细看 (https://msdn.microsoft.com/en-us/library/ee441848.aspx)

3.5 总结下漏洞相关的重点

客户端处理SMB_COM_TRANSACTION命令的时候如果数据大小超过MaxBufferSize,则需要使用SMB_COM_TRANSACTION_SECONDARY传输剩下的数据。

对于作为同一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数据包中的相同,那么就认为是同一部分的请求。

代码是这么实现的。

int__thiscallSrvSmbTransactionSecondary(intthis) { intv1;//ebx@1 intpSmbParamter;//edi@1 TRANSCATION*pTransation;//eax@1MAPDST unsignedintParameterDisplayment;//ecx@10MAPDST size_tParameterSize;//eax@10MAPDST size_tDataSize;//edx@10MAPDST intpTransList;//[sp+Ch][bp-24h]@1 PERESOURCEResource;//[sp+18h][bp-18h]@26 struct_ERESOURCE*Resourcea;//[sp+18h][bp-18h]@30 intpSmbHeader;//[sp+1Ch][bp-14h]@1 intParameterOffset;//[sp+20h][bp-10h]@10 intDataOffset;//[sp+28h][bp-8h]@10 v1=this; pSmbParamter=*(_DWORD*)(this+0x6C); pSmbHeader=*(_DWORD*)(this+0x68); pTransList=*(_DWORD*)(this+0x4C); // //首先查找SMB_COM_TRANSACTION_SECONDARY对应的SMB_COM_TRANSACTION // pTransation=(TRANSCATION*)SrvFindTransaction(pTransList,pSmbHeader,0); if(!pTransation) { return2; } if(!*(_BYTE*)(pTransation->field_10+0x98)) { ParameterDisplayment=*(_WORD*)(pSmbParamter+9); ParameterOffset=*(_WORD*)(pSmbParamter+7); ParameterSize=*(_WORD*)(pSmbParamter+5); DataOffset=*(_WORD*)(pSmbParamter+0xD); DataSize=*(_WORD*)(pSmbParamter+0xB); DataDisplayment=*(_WORD*)(pSmbParamter+0xF); if(pTransation->field_93) { //... } else { //Check Resource=*(PERESOURCE*)(*(_DWORD*)(v1+0x60)+0x10); if(ParameterSize+ParameterOffset>*(_DWORD*)(*(_DWORD*)(v1+0x60)+0x10) ||DataOffset+DataSize>(unsignedint)Resource ||ParameterSize+ParameterDisplayment>pTransation->TotalParameterCount ||DataSize+DataDisplayment>pTransation->TotalDataCount) { //CheckFaild } else { if(pTransation->field_94!=1) { // //这里将SMB_COM_TRANSACTION_SECONDARY传过来的Parameter和Data都保存 //到Transaction中 // //拷贝ParameterBuffer if(ParameterSize) _memmove( (void*)(ParameterDisplayment+pTransation->ParameterBuffer), (constvoid*)(pSmbHeader+ParameterOffset), ParameterSize);//parameter //复制DataBuffer,这里注意下,下面会提到!! if(DataSize) _memmove( (void*)(DataDisplayment+pTransation->DataBuffer), (constvoid*)(pSmbHeader+DataOffset), DataSize);//data return2; } } } } return1; }

这个SMB_COM_TRANSACTION_SECONDARY命令的处理函数的大体流程就是首先查找SMB_COM_TRANSACTION_SECONDARY对应的SMB_COM_TRANSACTION如果找到就将SMB_COM_TRANSACTION_SECONDARY中的Parameter和Data都复制到SMB_COM_TRANSACTION中去。

4.2 SrvFindTransaction

下面来看下查找的逻辑SrvFindTransaction:

int__stdcallSrvFindTransaction(inta1,intpSmbHeaderOrMid,inta3) { SMB_HEADER*pSmbHeader_;//edi@1 struct_ERESOURCE*v4;//ebx@4 _DWORD**pTransList;//edx@4 _DWORD*i;//ecx@4 TRANSCATION*v7;//eax@5 intv9;//esi@14 pSmbHeader_=(SMB_HEADER*)pSmbHeaderOrMid; // //command0x2fisSUM_CMD_WRITE_ANDX //a3=SUM_CMD_WRITE_ANDX->Fid // if(*(_BYTE*)(pSmbHeaderOrMid+4)==0x2F) pSmbHeaderOrMid=a3; else LOWORD(pSmbHeaderOrMid)=*(_WORD*)(pSmbHeaderOrMid+0x1E); v4=(struct_ERESOURCE*)(a1+0x130); ExAcquireResourceExclusiveLite((PERESOURCE)(a1+0x130),1u); pTransList=(_DWORD**)(*(_DWORD*)(a1+0xF4)+8); for(i=*pTransList;;i=(_DWORD*)*i) { if(i==pTransList) { // //查到最后了退出 // ExReleaseResourceLite(v4); return0; } // //这里是对比TID,PID,UID,MID //这里注意这个MID,如果命令是SUM_CMD_WRITE_ANDXMID就是SUM_CMD_WRITE_ANDXMID数据包中的Fid // v7=(TRANSCATION*)(i-6); if(*((_WORD*)i+47)==pSmbHeader_->TID &&v7->ProcessId==pSmbHeader_->PIDLow &&v7->UserId==pSmbHeader_->UID &&v7->MutiplexId==(_WORD)pSmbHeaderOrMid)//MutilplexId如果名令时SMB_CMD_WRITE_ANDX那么这里是Fid { break; } } if(BYTE1(v7->field_0)==2) { _InterlockedExchangeAdd((volatilesigned__int32*)(v7->field_8+4),1u); v9=(int)(i-6); } else { v9=0; } ExReleaseResourceLite(v4); returnv9; }

大家可以看下逻辑。重点在这里如果命令是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代码如下:

signedint__thiscallSrvSmbWriteAndX(intthis) { ...略 pTrans=(TRANSCATION*)SrvFindTransaction(pTransList,pSmbTranscationBuffer,Fid); pTrans_=pTrans; if(!pTrans) { if((unsignedint)SrvWmiEnableLevel>=2&&SrvWmiEnableFlags&1&&KeGetCurrentIrql()<2u) WPP_SF_(64,&unk_1E1CC); v40="d:\\xpsp\\base\\fs\\srv\\smbrdwrt.c"; v37=3216; gotoLABEL_69; } if(pTrans->TotalDataCount-pTrans->nTransDataCounts<v11) { SrvCloseTransaction(pTrans); SrvDereferenceTransaction(pTrans_); _SrvSetSmbError2(v1,0x80000005,0,3238,(int)"d:\\xpsp\\base\\fs\\srv\\smbrdwrt.c"); StatusCode=0x80000005; gotoLABEL_100; } v31=Size; qmemcpy((void*)pTrans->DataBuffer,VirtualAddress,Size); // //!!!!这里将DataBuffer指针给增加了!!!! // pTrans_->DataBuffer+=Size; pTrans_->nTransDataCounts+=v31; ...略 }

看到pTrans_-&gt;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:


【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

然后发送SMB_CMD_WRITE_ANDX命令还增加pTrans_-&gt;DataBuffer这个指针:


【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

5 漏洞利用

从上面描述可以看出,该漏洞为类型混淆导致的越界写漏洞。前期通过spray 使多个TRANSACTION相邻,然后让其中一个TRANSACTION触发该漏洞,再通过该TRANSACTION越界写其相邻的TRANSACTION

spary 最终 memory view:


【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

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 结构部份如下:

typedefstruct_TRANSACTION{ //... /*+0xc*/LPVOIDConnection; //... /*+0x34*/LPWORDInSetup; //... /*+0x40*/LPBYTEOutParameters; /*+0x44*/LPBYTEInData;/*写的目的地址*/ /*+0x48*/LPBYTEOutData;/*读的目的地址*/ //... /*+0x60*/DWORDDataCount;/*控制可读的长度*/ /*+0x64*/DWORDTotalDataCount }TRANSACTION

写操作:

SMB_PROCESSOR_RETURN_TYPESrvSmbTransactionSecondary( SMB_PROCESSOR_PARAMETERS ) { //... request=(PREQ_TRANSACTION_SECONDARY)WorkContext->RequestParameters; header=WorkContext->RequestHeader; transaction=SrvFindTransaction(connection,header,0); //... dataOffset=SmbGetUshort(&request->DataOffset); dataCount=SmbGetUshort(&request->DataCount); dataDisplacement=SmbGetUshort(&request->DataDisplacement); //... //CopytheparameteranddatabytesthatarrivedinthisSMB. //... if(dataCount!=0){ RtlMoveMemory( transaction->InData+dataDisplacement, (PCHAR)header+dataOffset, dataCount); } //... }

读操作:

VOIDSrvCompleteExecuteTransaction( INOUTPWORK_CONTEXTWorkContext, INSMB_TRANS_STATUSResultStatus) { //... transaction=WorkContext->Parameters.Transaction; header=WorkContext->ResponseHeader; transactionCommand=(UCHAR)SmbGetUshort(&transaction->InSetup[0]); //... //Copytheappropriateparameteranddatabytesintothemessage. if(paramLength!=0){ RtlMoveMemory(paramPtr,transaction->OutParameters,paramLength); } if(dataLength!=0){ RtlMoveMemory(dataPtr,transaction->OutData,dataLength); } //... }

从exp运行的log可以看出该漏洞利用分为两部份:信息泄露 与 代码执行


【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

5.1 信息泄露

需要泄露的信息包括 Transaction2Dispatch Table基址 与 一块NonePagedPool Buffer地址. 通过修改Transaction2Dispatch Table中的函数指针来执行 shellcode, 其中NonePagedPool Buffer就是用于存放shellcode.

5.1.1 泄露Transaction2Dispatch Table基址

从exp运行的log可以看出首先泄露CONNECTION结构基址:CONNECTION地址存放于TRANSACTION-&gt;Connection字段。看到这,你可能已经想到该怎么做了:直接利用constrol transaction的0x200字节的越界能力修改write_transaction的DataCount字段让其可以越界读leak_transaction上的内容,从而读出TRANSACTION-&gt;Connection。 但exp作者却并没有这么做,这里并不打算深究其原因,或许是有其他限制,或许不是。

作者这里利用了另一种复杂不少方法,通过另一种方法修改 write_transaction的DataCount字段。

5.1.1.1 write_transaction初始状态如下:


【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析

可以看出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操作,会走到:

SMB_STATUSSRVFASTCALLExecuteTransaction( INOUTPWORK_CONTEXTWorkContext) { //... transaction=WorkContext->Parameters.Transaction; //... command=SmbGetUshort(&transaction->InSetup[0]); //... switch(command){ caseTRANS_TRANSACT_NMPIPE: resultStatus=SrvTransactNamedPipe(WorkContext); break; caseTRANS_PEEK_NMPIPE://0x23 resultStatus=SrvPeekNamedPipe(WorkContext); break; caseTRANS_CALL_NMPIPE: resultStatus=SrvCallNamedPipe(WorkContext); break; //... } //... }

由于之前已经将write_transaction的InSetup修改为0x23, 所以会call SrvPeekNamedPipe。

#ChildEBPRetAddr 00b21f8d44b24cdccesrv!ExecuteTransaction+0x23b(FPO:[0,0,0]) 01b21f8d7cb248a836srv!SrvSmbTransactionSecondary+0x2f1(FPO:[Non-Fpo]) 02b21f8d88b249ad98srv!SrvProcessSmb+0xb7(FPO:[0,0,0]) 03b21f8dac805c7160srv!WorkerThread+0x11e(FPO:[Non-Fpo]) 04b21f8ddc80542dd2nt!PspSystemThreadStartup+0x34(FPO:[Non-Fpo]) 050000000000000000nt!KiThreadStartup+0x16 SrvPeekNamedPipe()调用IoCallDriver最终调到RestartPeekNamedPipe()函数 VOIDSRVFASTCALLRestartPeekNamedPipe( INOUTPWORK_CONTEXTWorkContext) { //... // //Success.Generateandsendtheresponse. // transaction=WorkContext->Parameters.Transaction; pipePeekBuffer=(PFILE_PIPE_PEEK_BUFFER)transaction->OutParameters; readDataAvailable=(USHORT)pipePeekBuffer->ReadDataAvailable; messageLength=(USHORT)pipePeekBuffer->MessageLength; namedPipeState=(USHORT)pipePeekBuffer->NamedPipeState; // //...thencopythembackinthenewformat. // respPeekNmPipe=(PRESP_PEEK_NMPIPE)pipePeekBuffer; SmbPutAlignedUshort( &respPeekNmPipe->ReadDataAvailable, readDataAvailable ); SmbPutAlignedUshort( &respPeekNmPipe->MessageLength, messageLength ); SmbPutAlignedUshort( &respPeekNmPipe->NamedPipeState, namedPipeState ); // //Sendtheresponse.Settheoutputcounts. // //NTreturntous4ULONGSofparameterbytes,followedbydata. //Wereturntotheclient6parameterbytes. // transaction->SetupCount=0; transaction->ParameterCount=6; transaction->DataCount=WorkContext->Irp->IoStatus.Information-(4*sizeof(ULONG)); //... }

该函数最终会修改Transaction-&gt;DataCount 为 0x23c。

kd>ub srv!RestartPeekNamedPipe+0x42: b770013766895004movwordptr[eax+4],dx b770013b83614c00anddwordptr[ecx+4Ch],0 b770013fc7415406000000movdwordptr[ecx+54h],6 b77001468b4678moveax,dwordptr[esi+78h] b77001498b401cmoveax,dwordptr[eax+1Ch] b770014c83e810subeax,10h b770014f85fftestedi,edi b7700151894160movdwordptr[ecx+60h],eax kd>?ecx+0x60 Evaluateexpression:367768=00059c98 kd>reax eax=0000023c kd>dd00059c38 00059c38109c020c00000000ffdff50089a29c18 00059c48e1ed9900e15e29600005acf800058ba8 00059c580002000000059cd00000230700000001 00059c680000000000059cd48993b3e500059cd4 00059c7800059d14ffdff5000005acd400000000 00059c8800000000000000060000000000000ff0 00059c980000023c000000000000000100000000 00059ca800000101080000000800cee600000000 kd>k #ChildEBPRetAddr 00b7c0ed88b76dad98srv!RestartPeekNamedPipe+0x5f 01b7c0edac805c6160srv!WorkerThread+0x11e 02b7c0eddc80541dd2nt!PspSystemThreadStartup+0x34 030000000000000000nt!KiThreadStartup+0x16

至此,已经成功修改了write_transaction的DataCount值,之后便可以越界读出 leak_transacion-&gt;Connection值: 89a29c18。

5.1.1.3 SRV global data pointer

kd>dds89a29c18 89a29c1802580202 89a29c1c0000001d 89a29c2000000000 89a29c2400000000 89a29c2800000000 89a29c2c00000000 89a29c3000000000 89a29c3400000000 89a29c3800000000 89a29c3cb76d8becsrv!SrvGlobalSpinLocks+0x3c 89a29c40899d0020 89a29c44000005b3 89a29c488976c200 89a29c4c00004000 89a29c5010000100 89a29c5400000000 89a29c588988e010 89a29c5c89aedc30 89a29c6089a7d898 89a29c640001ffff

其中srv!SrvGlobalSpinLocks+0x3c 就是所谓的 SRV global data pointer :b76d8bec

5.1.1.4 Locating function tables

kd>ddsb76d8bec-0x654 b76d8598b7709683srv!SrvSmbOpen2 b76d859cb76f62a8srv!SrvSmbFindFirst2 b76d85a0b76f74e5srv!SrvSmbFindNext2 b76d85a4b76f6309srv!SrvSmbQueryFsInformation b76d85a8b7707293srv!SrvSmbSetFsInformation b76d85acb77041adsrv!SrvSmbQueryPathInformation b76d85b0b7703ce7srv!SrvSmbSetPathInformation b76d85b4b77025adsrv!SrvSmbQueryFileInformation b76d85b8b770367fsrv!SrvSmbSetFileInformation b76d85bcb7705c85srv!SrvSmbFsctl b76d85c0b7706419srv!SrvSmbIoctl2 b76d85c4b7705c85srv!SrvSmbFsctl b76d85c8b7705c85srv!SrvSmbFsctl b76d85ccb77047bbsrv!SrvSmbCreateDirectory2 b76d85d0b7709a51srv!SrvTransactionNotImplemented b76d85d4b7709a51srv!SrvTransactionNotImplemented b76d85d8b76fb144srv!SrvSmbGetDfsReferral b76d85dcb76faf7esrv!SrvSmbReportDfsInconsistency b76d85e000000000

5.1.2 泄露Npp Buffer (shellcode buffer)

这里又得回到ExecuteTransaction函数:

SMB_STATUSSRVFASTCALLExecuteTransaction( INOUTPWORK_CONTEXTWorkContext) { //... header=WorkContext-&gt;ResponseHeader; response=(PRESP_TRANSACTION)WorkContext-&gt;ResponseParameters; ntResponse=(PRESP_NT_TRANSACTION)WorkContext-&gt;ResponseParameters; // //Setupoutputpointers // if(transaction-&gt;OutParameters==NULL){ // //ParameterswillgointotheSMBbuffer.Calculatethepointer //thenroundituptothenextDWORDaddress. // transaction-&gt;OutParameters=(PCHAR)(transaction-&gt;OutSetup+ transaction-&gt;MaxSetupCount); offset=(transaction-&gt;OutParameters-(PCHAR)header+3)&amp;~3; transaction-&gt;OutParameters=(PCHAR)header+offset; } if(transaction-&gt;OutData==NULL){ // //DatawillgointotheSMBbuffer.Calculatethepointer //thenroundituptothenextDWORDaddress. // transaction-&gt;OutData=transaction-&gt;OutParameters+ transaction-&gt;MaxParameterCount; offset=(transaction-&gt;OutData-(PCHAR)header+3)&amp;~3; transaction-&gt;OutData=(PCHAR)header+offset; } //... command=SmbGetUshort(&amp;transaction-&gt;InSetup[0]); //... switch(command){ caseTRANS_TRANSACT_NMPIPE: resultStatus=SrvTransactNamedPipe(WorkContext); break; caseTRANS_PEEK_NMPIPE: resultStatus=SrvPeekNamedPipe(WorkContext); break; //... }

这在这个函数里有这么一个逻辑,当transaction-&gt;OutParameters==NULL里,会将PWORK_CONTEXT-&gt;ResponseHeader加上一定的offset赋于它,PWORK_CONTEXT-&gt;ResponseHeader就是个NonePagedPool.

kd>ubeip srv!ExecuteTransaction+0x60: b76e8d0546incesi b76e8d0650pusheax b76e8d07d1e0shleax,1 b76e8d092bc2subeax,edx b76e8d0b8d440803leaeax,[eax+ecx+3] b76e8d0f83e0fcandeax,0FFFFFFFCh b76e8d1203c2addeax,edx b76e8d14894640movdwordptr[esi+40h],eax kd>dd5ace0 0005ace0109c020c0000000089b2c94889a29c18 0005acf0e1ed9900e15e29600005bda000059c50 0005ad00000200000005ad780000236140010036 0005ad10000000000005ad0c8993fa250005ad7c 0005ad208993fa280005ad7cb76d84ec00000004 0005ad3000000000000000000000000000000010 0005ad4000000000000000000000010000000000 0005ad5000000101080000000800cee60000004b kd>?esi+0x40 Evaluateexpression:372000=0005ad20 kd>reax eax=8993fa28

5.1.2.1 transaction->OutParameters=NULL

Transaction 初始状态下OutParameters并不为NULL:

kd>dd5ace0 0005ace0109c020c0000000089b2c94889a29c18 0005acf0e1ed9900e15e29600005bda000059c50 0005ad00000200000005ad780000236100000001 0005ad10000000000005ad7c000000000005ad7c 0005ad200005adbc0005ad7c0005bd7c00000000 0005ad3000000000000000000000000000000fc0 0005ad4000000000000000400000000000000000 0005ad5000000101080000000800cee60000004b

这里通过write_transaction越界 写 leak_transaction-&gt;OutParameters为NULL, 然后发包触发写leak_transaction操作,之后leak_transaction-&gt;OutParameters便为一 Npp Buffer值了。

5.1.2.2 leak_transaction->OutData = &leak_transaction->OutParameters

这里要事先泄露leak_transaction的基址,其实也不难,通过读leak_transaction的OutData 或 OutParameters 或 InData 字段的值再减去一定的偏移便得到了基址,使leak_transaction-&gt;OutData = &amp;leak_transaction-&gt;OutParameters之后,发包触发leak_transaction读操作便将该Npp buffer地址泄露出来了。

5.1.2.3 写shellcode到Npp Buffer

将control_transaction-&gt;OutData设为Npp Buffer+0x100地址,然后发包发送shellcode,便将shellcode写到了Npp Buffer+0x100内。

5.2 代码执行

至此,直接将Npp buffer+0x100写到之前泄露出来的函数表里

kd>ddsb76d8598 b76d8598b7709683srv!SrvSmbOpen2 b76d859cb76f62a8srv!SrvSmbFindFirst2 b76d85a0b76f74e5srv!SrvSmbFindNext2 b76d85a4b76f6309srv!SrvSmbQueryFsInformation b76d85a8b7707293srv!SrvSmbSetFsInformation b76d85acb77041adsrv!SrvSmbQueryPathInformation b76d85b0b7703ce7srv!SrvSmbSetPathInformation b76d85b4b77025adsrv!SrvSmbQueryFileInformation b76d85b8b770367fsrv!SrvSmbSetFileInformation b76d85bcb7705c85srv!SrvSmbFsctl b76d85c0b7706419srv!SrvSmbIoctl2 b76d85c4b7705c85srv!SrvSmbFsctl b76d85c8b7705c85srv!SrvSmbFsctl b76d85ccb77047bbsrv!SrvSmbCreateDirectory2 b76d85d08993fb28 b76d85d4b7709a51srv!SrvTransactionNotImplemented b76d85d8b76fb144srv!SrvSmbGetDfsReferral b76d85dcb76faf7esrv!SrvSmbReportDfsInconsistency b76d85e000000000

之后发包就能触发该函数调用:

kd>k #ChildEBPRetAddr WARNING:FrameIPnotinanyknownmodule.Followingframesmaybewrong. 00b72b4cf0b76e8d760x8993fb28 01b72b4d04b76e341fsrv!ExecuteTransaction+0xdb 02b72b4d7cb76ca836srv!SrvSmbTransaction+0x7ac 03b72b4d88b76dad98srv!SrvProcessSmb+0xb7 04b72b4dac805c6160srv!WorkerThread+0x11e 05b72b4ddc80541dd2nt!PspSystemThreadStartup+0x34 060000000000000000nt!KiThreadStartup+0x16

6 关于补丁

了解了漏洞原理之后修补都很简单了。只要在srv!SrvFindTransaction里面判断一下SMB COMMAND的类型是否一致就好了。

修补前

TRANSCATION*__fastcallSrvFindTransaction(intpConnect,SMB_HEADER*Fid,__int16a3) { _DWORD**pTransList;//eax@4 _DWORD*v6;//ebx@4 PDEVICE_OBJECTv7;//ecx@5 TRANSCATION*pTransaction;//esi@6 charCommand_Trans;//al@10 charCommand_header;//dl@10 __int16MIDorFID;//[sp+Ch][bp-Ch]@2 struct_ERESOURCE*Resource;//[sp+14h][bp-4h]@4 if(Fid->Command==0x2F) MIDorFID=a3; else MIDorFID=Fid->MID; Resource=(struct_ERESOURCE*)(pConnect+0x19C); ExAcquireResourceExclusiveLite((PERESOURCE)(pConnect+0x19C),1u); pTransList=(_DWORD**)(*(_DWORD*)(pConnect+0x160)+8); v6=*pTransList; if(*pTransList==pTransList) gotoLABEL_14; v7=WPP_GLOBAL_Control; while(1) { pTransaction=(TRANSCATION*)(v6-6); if(*((_WORD*)v6+49)==Fid->TID &&pTransaction->PID==Fid->PID &&pTransaction->UID==Fid->UID &&pTransaction->MID==MIDorFID) { break; } LABEL_13: v6=(_DWORD*)*v6; if(v6==(_DWORD*)(*(_DWORD*)(pConnect+0x160)+8)) gotoLABEL_14; } // //这里添加了对COMMAND的比较。比较pTransaction和请求中SMB_HEADER中的COMMAND进行对比 // Command_Trans=pTransaction->Command; Command_header=Fid->Command; if(Command_Trans!=Command_header) { if((PDEVICE_OBJECT*)v7!=&WPP_GLOBAL_Control) { WPP_SF_qDD(v7->AttachedDevice,v7->CurrentIrp,(_BYTE)v6-24,Command_header,Command_Trans); v7=WPP_GLOBAL_Control; } gotoLABEL_13; } if(BYTE1(pTransaction->field_0)==2) { _InterlockedIncrement((volatilesigned__int32*)(pTransaction->field_8+4)); gotoLABEL_15; } LABEL_14: pTransaction=0; LABEL_15: ExReleaseResourceLite(Resource); returnpTransaction; }

补丁点就是if ( Command_Trans != Command_header )看注释的地方。


7 总结

总之,这个漏洞还是非常好的,远程任意地址写,还可以信息泄露。威力很大。


8 联系作者

pgboy 微博

zhong_sf 微博

如果你感觉排版不舒服可以去这里下载原始的markdown文件。


传送门

【漏洞分析】MS 17-010:NSA Eternalblue SMB 漏洞分析



【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析
【漏洞分析】NSA Eternalromance (永恒浪漫) 漏洞分析
本文转载自 360安全卫士技术博客
原文链接:http://blogs.360.cn/360safe/2017/04/19/eternalromance-analyze/

Viewing all articles
Browse latest Browse all 12749