來源:360安全衛士技術博客
作者:pgboy && zhong_sf
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的結構如下:
SMB_Header
{
UCHAR Protocol[4];
UCHAR Command;
SMB_ERROR Status;
UCHAR Flags;
USHORT Flags2;
USHORT PIDHigh;
UCHAR SecurityFeatures[8];
USHORT Reserved;
USHORT TID;
USHORT PIDLow;
USHORT UID;
USHORT MID;
}
更詳細見 (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 總結下漏洞相關的重點
- 客戶端處理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 __thiscall SrvSmbTransactionSecondary(int this)
{
int v1; // ebx@1
int pSmbParamter; // edi@1
TRANSCATION *pTransation; // eax@1 MAPDST
unsigned int ParameterDisplayment; // ecx@10 MAPDST
size_t ParameterSize; // eax@10 MAPDST
size_t DataSize; // edx@10 MAPDST
int pTransList; // [sp+Ch] [bp-24h]@1
PERESOURCE Resource; // [sp+18h] [bp-18h]@26
struct _ERESOURCE *Resourcea; // [sp+18h] [bp-18h]@30
int pSmbHeader; // [sp+1Ch] [bp-14h]@1
int ParameterOffset; // [sp+20h] [bp-10h]@10
int DataOffset; // [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 )
{
return 2;
}
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 > (unsigned int)Resource
|| ParameterSize + ParameterDisplayment > pTransation->TotalParameterCount
|| DataSize + DataDisplayment > pTransation->TotalDataCount )
{
//CheckFaild
}
else
{
if ( pTransation->field_94 != 1 )
{
//
// 這里將SMB_COM_TRANSACTION_SECONDARY傳過來的Parameter和Data都保存
// 到Transaction中
//
//拷貝Parameter Buffer
if ( ParameterSize )
_memmove(
(void *)(ParameterDisplayment + pTransation->ParameterBuffer),
(const void *)(pSmbHeader + ParameterOffset),
ParameterSize); // parameter
//復制Data Buffer,這里注意下,下面會提到!!
if ( DataSize )
_memmove(
(void *)(DataDisplayment + pTransation->DataBuffer),
(const void *)(pSmbHeader + DataOffset),
DataSize); // data
return 2;
}
}
}
}
return 1;
}
這個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 __stdcall SrvFindTransaction(int a1, int pSmbHeaderOrMid, int a3)
{
SMB_HEADER *pSmbHeader_; // edi@1
struct _ERESOURCE *v4; // ebx@4
_DWORD **pTransList; // edx@4
_DWORD *i; // ecx@4
TRANSCATION *v7; // eax@5
int v9; // esi@14
pSmbHeader_ = (SMB_HEADER *)pSmbHeaderOrMid;
//
// command 0x2f is SUM_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);
return 0;
}
//
// 這里是對比TID,PID,UID,MID
// 這里注意這個MID,如果命令是SUM_CMD_WRITE_ANDX MID就是SUM_CMD_WRITE_ANDX MID數據包中的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((volatile signed __int32 *)(v7->field_8 + 4), 1u);
v9 = (int)(i - 6);
}
else
{
v9 = 0;
}
ExReleaseResourceLite(v4);
return v9;
}
大家可以看下邏輯。重點在這里如果命令是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代碼如下:
signed int __thiscall SrvSmbWriteAndX(int this)
{
...略
pTrans = (TRANSCATION *)SrvFindTransaction(pTransList, pSmbTranscationBuffer, Fid);
pTrans_ = pTrans;
if ( !pTrans )
{
if ( (unsigned int)SrvWmiEnableLevel >= 2 && SrvWmiEnableFlags & 1 && KeGetCurrentIrql() < 2u )
WPP\_SF_(64, &unk_1E1CC);
v40 = "d:\\xpsp\\base\\fs\\srv\\smbrdwrt.c";
v37 = 3216;
goto LABEL_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;
goto LABEL_100;
}
v31 = Size;
qmemcpy((void *)pTrans->DataBuffer, VirtualAddress, Size);
//
// !!!!這里將DataBuffer指針給增加了!!!!
//
pTrans_->DataBuffer += Size;
pTrans_->nTransDataCounts += v31;
...略
}
看到pTrans_->DataBuffer += Size這句相信大家就能明白了。這里將DataBuffer的指針增大了。再處理此Transcation的SMB_COM_TRANSACTION_SECONDARY命令的時候也就是SrvSmbTransactionSecondary中復制Data的memcpy可就越界了!!!!!
所以此漏洞可以總結成類型混淆造成的越界寫。
4.3 抓包Demo
通過對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 結構部份如下:
typedef struct _TRANSACTION {
//...
/*+0xc*/ LPVOID Connection;
//...
/*+0x34*/ LPWORD InSetup;
//...
/*+0x40*/ LPBYTE OutParameters;
/*+0x44*/ LPBYTE InData; /*寫的目的地址*/
/*+0x48*/ LPBYTE OutData; /*讀的目的地址*/
//...
/*+0x60*/ DWORD DataCount; /*控制可讀的長度*/
/*+0x64*/ DWORD TotalDataCount
}TRANSACTION
寫操作:
SMB_PROCESSOR_RETURN_TYPE SrvSmbTransactionSecondary (
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 );
//...
// Copy the parameter and data bytes that arrived in this SMB.
//...
if ( dataCount != 0 ) {
RtlMoveMemory(
transaction->InData + dataDisplacement,
(PCHAR)header + dataOffset,
dataCount);
}
//...
}
讀操作:
VOID SrvCompleteExecuteTransaction (
IN OUT PWORK_CONTEXT WorkContext,
IN SMB_TRANS_STATUS ResultStatus)
{
//...
transaction = WorkContext->Parameters.Transaction;
header = WorkContext->ResponseHeader;
transactionCommand = (UCHAR)SmbGetUshort( &transaction->InSetup[0] );
//...
// Copy the appropriate parameter and data bytes into the message.
if ( paramLength != 0 ) {
RtlMoveMemory( paramPtr, transaction->OutParameters, paramLength );
}
if ( dataLength != 0 ) {
RtlMoveMemory( dataPtr, transaction->OutData, dataLength );
}
//...
}
從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操作,會走到:
SMB_STATUS SRVFASTCALL ExecuteTransaction (
IN OUT PWORK_CONTEXT WorkContext)
{
//...
transaction = WorkContext->Parameters.Transaction;
//...
command = SmbGetUshort(&transaction->InSetup[0]);
//...
switch( command ) {
case TRANS_TRANSACT_NMPIPE:
resultStatus = SrvTransactNamedPipe( WorkContext );
break;
case TRANS_PEEK_NMPIPE: //0x23
resultStatus = SrvPeekNamedPipe( WorkContext );
break;
case TRANS_CALL_NMPIPE:
resultStatus = SrvCallNamedPipe( WorkContext );
break;
//...
}
//...
}
由于之前已經將write_transaction的InSetup修改為0x23, 所以會call SrvPeekNamedPipe。
# ChildEBP RetAddr
00 b21f8d44 b24cdcce srv!ExecuteTransaction+0x23b (FPO: [0,0,0])
01 b21f8d7c b248a836 srv!SrvSmbTransactionSecondary+0x2f1 (FPO: [Non-Fpo])
02 b21f8d88 b249ad98 srv!SrvProcessSmb+0xb7 (FPO: [0,0,0])
03 b21f8dac 805c7160 srv!WorkerThread+0x11e (FPO: [Non-Fpo])
04 b21f8ddc 80542dd2 nt!PspSystemThreadStartup+0x34 (FPO: [Non-Fpo])
05 00000000 00000000 nt!KiThreadStartup+0x16
SrvPeekNamedPipe() 調用IoCallDriver最終調到 RestartPeekNamedPipe()函數
VOID SRVFASTCALL RestartPeekNamedPipe (
IN OUT PWORK_CONTEXT WorkContext)
{
//...
//
// Success. Generate and send the response.
//
transaction = WorkContext->Parameters.Transaction;
pipePeekBuffer = (PFILE_PIPE_PEEK_BUFFER)transaction->OutParameters;
readDataAvailable = (USHORT)pipePeekBuffer->ReadDataAvailable;
messageLength = (USHORT)pipePeekBuffer->MessageLength;
namedPipeState = (USHORT)pipePeekBuffer->NamedPipeState;
//
// ... then copy them back in the new format.
//
respPeekNmPipe = (PRESP_PEEK_NMPIPE)pipePeekBuffer;
SmbPutAlignedUshort(
&respPeekNmPipe->ReadDataAvailable,
readDataAvailable
);
SmbPutAlignedUshort(
&respPeekNmPipe->MessageLength,
messageLength
);
SmbPutAlignedUshort(
&respPeekNmPipe->NamedPipeState,
namedPipeState
);
//
// Send the response. Set the output counts.
//
// NT return to us 4 ULONGS of parameter bytes, followed by data.
// We return to the client 6 parameter bytes.
//
transaction->SetupCount = 0;
transaction->ParameterCount = 6;
transaction->DataCount = WorkContext->Irp->IoStatus.Information - (4 * sizeof(ULONG));
//...
}
該函數最終會修改Transaction->DataCount 為 0x23c。
kd> ub
srv!RestartPeekNamedPipe+0x42:
b7700137 66895004 mov word ptr [eax+4],dx
b770013b 83614c00 and dword ptr [ecx+4Ch],0
b770013f c7415406000000 mov dword ptr [ecx+54h],6
b7700146 8b4678 mov eax,dword ptr [esi+78h]
b7700149 8b401c mov eax,dword ptr [eax+1Ch]
b770014c 83e810 sub eax,10h
b770014f 85ff test edi,edi
b7700151 894160 mov dword ptr [ecx+60h],eax
kd> ?ecx+0x60
Evaluate expression: 367768 = 00059c98
kd> r eax
eax=0000023c
kd> dd 00059c38
00059c38 109c020c 00000000 ffdff500 89a29c18
00059c48 e1ed9900 e15e2960 0005acf8 00058ba8
00059c58 00020000 00059cd0 00002307 00000001
00059c68 00000000 00059cd4 8993b3e5 00059cd4
00059c78 00059d14 ffdff500 0005acd4 00000000
00059c88 00000000 00000006 00000000 00000ff0
00059c98 0000023c 00000000 00000001 00000000
00059ca8 00000101 08000000 0800cee6 00000000
kd> k
# ChildEBP RetAddr
00 b7c0ed88 b76dad98 srv!RestartPeekNamedPipe+0x5f
01 b7c0edac 805c6160 srv!WorkerThread+0x11e
02 b7c0eddc 80541dd2 nt!PspSystemThreadStartup+0x34
03 00000000 00000000 nt!KiThreadStartup+0x16
至此,已經成功修改了write_transaction的DataCount值,之后便可以越界讀出 leak_transacion->Connection值: 89a29c18。
5.1.1.3 SRV global data pointer
kd> dds 89a29c18
89a29c18 02580202
89a29c1c 0000001d
89a29c20 00000000
89a29c24 00000000
89a29c28 00000000
89a29c2c 00000000
89a29c30 00000000
89a29c34 00000000
89a29c38 00000000
89a29c3c b76d8bec srv!SrvGlobalSpinLocks+0x3c
89a29c40 899d0020
89a29c44 000005b3
89a29c48 8976c200
89a29c4c 00004000
89a29c50 10000100
89a29c54 00000000
89a29c58 8988e010
89a29c5c 89aedc30
89a29c60 89a7d898
89a29c64 0001ffff
其中srv!SrvGlobalSpinLocks+0x3c 就是所謂的 SRV global data pointer :b76d8bec
5.1.1.4 Locating function tables
kd> dds b76d8bec-0x654
b76d8598 b7709683 srv!SrvSmbOpen2
b76d859c b76f62a8 srv!SrvSmbFindFirst2
b76d85a0 b76f74e5 srv!SrvSmbFindNext2
b76d85a4 b76f6309 srv!SrvSmbQueryFsInformation
b76d85a8 b7707293 srv!SrvSmbSetFsInformation
b76d85ac b77041ad srv!SrvSmbQueryPathInformation
b76d85b0 b7703ce7 srv!SrvSmbSetPathInformation
b76d85b4 b77025ad srv!SrvSmbQueryFileInformation
b76d85b8 b770367f srv!SrvSmbSetFileInformation
b76d85bc b7705c85 srv!SrvSmbFsctl
b76d85c0 b7706419 srv!SrvSmbIoctl2
b76d85c4 b7705c85 srv!SrvSmbFsctl
b76d85c8 b7705c85 srv!SrvSmbFsctl
b76d85cc b77047bb srv!SrvSmbCreateDirectory2
b76d85d0 b7709a51 srv!SrvTransactionNotImplemented
b76d85d4 b7709a51 srv!SrvTransactionNotImplemented
b76d85d8 b76fb144 srv!SrvSmbGetDfsReferral
b76d85dc b76faf7e srv!SrvSmbReportDfsInconsistency
b76d85e0 00000000
5.1.2 泄露Npp Buffer (shellcode buffer)
這里又得回到ExecuteTransaction函數:
SMB_STATUS SRVFASTCALL ExecuteTransaction (
IN OUT PWORK_CONTEXT WorkContext)
{
//...
header = WorkContext->ResponseHeader;
response = (PRESP_TRANSACTION)WorkContext->ResponseParameters;
ntResponse = (PRESP_NT_TRANSACTION)WorkContext->ResponseParameters;
//
// Setup output pointers
//
if ( transaction->OutParameters == NULL ) {
//
// Parameters will go into the SMB buffer. Calculate the pointer
// then round it up to the next DWORD address.
//
transaction->OutParameters = (PCHAR)(transaction->OutSetup +
transaction->MaxSetupCount);
offset = (transaction->OutParameters - (PCHAR)header + 3) & ~3;
transaction->OutParameters = (PCHAR)header + offset;
}
if ( transaction->OutData == NULL ) {
//
// Data will go into the SMB buffer. Calculate the pointer
// then round it up to the next DWORD address.
//
transaction->OutData = transaction->OutParameters +
transaction->MaxParameterCount;
offset = (transaction->OutData - (PCHAR)header + 3) & ~3;
transaction->OutData = (PCHAR)header + offset;
}
//...
command = SmbGetUshort(&transaction->InSetup[0]);
//...
switch( command ) {
case TRANS_TRANSACT_NMPIPE:
resultStatus = SrvTransactNamedPipe( WorkContext );
break;
case TRANS_PEEK_NMPIPE:
resultStatus = SrvPeekNamedPipe( WorkContext );
break;
//...
}
這在這個函數里有這么一個邏輯,當transaction->OutParameters==NULL里,會將PWORK\_CONTEXT->ResponseHeader加上一定的offset賦于它,PWORK_CONTEXT->ResponseHeader就是個NonePagedPool.
kd> ub eip
srv!ExecuteTransaction+0x60:
b76e8d05 46 inc esi
b76e8d06 50 push eax
b76e8d07 d1e0 shl eax,1
b76e8d09 2bc2 sub eax,edx
b76e8d0b 8d440803 lea eax,[eax+ecx+3]
b76e8d0f 83e0fc and eax,0FFFFFFFCh
b76e8d12 03c2 add eax,edx
b76e8d14 894640 mov dword ptr [esi+40h],eax
kd> dd 5ace0
0005ace0 109c020c 00000000 89b2c948 89a29c18
0005acf0 e1ed9900 e15e2960 0005bda0 00059c50
0005ad00 00020000 0005ad78 00002361 40010036
0005ad10 00000000 0005ad0c 8993fa25 0005ad7c
0005ad20 8993fa28 0005ad7c b76d84ec 00000004
0005ad30 00000000 00000000 00000000 00000010
0005ad40 00000000 00000000 00000100 00000000
0005ad50 00000101 08000000 0800cee6 0000004b
kd> ? esi+0x40
Evaluate expression: 372000 = 0005ad20
kd> r eax
eax=8993fa28
5.1.2.1 transaction->OutParameters=NULL
Transaction 初始狀態下OutParameters并不為NULL:
kd> dd 5ace0
0005ace0 109c020c 00000000 89b2c948 89a29c18
0005acf0 e1ed9900 e15e2960 0005bda0 00059c50
0005ad00 00020000 0005ad78 00002361 00000001
0005ad10 00000000 0005ad7c 00000000 0005ad7c
0005ad20 0005adbc 0005ad7c 0005bd7c 00000000
0005ad30 00000000 00000000 00000000 00000fc0
0005ad40 00000000 00000040 00000000 00000000
0005ad50 00000101 08000000 0800cee6 0000004b
這里通過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寫到之前泄露出來的函數表里
kd> dds b76d8598
b76d8598 b7709683 srv!SrvSmbOpen2
b76d859c b76f62a8 srv!SrvSmbFindFirst2
b76d85a0 b76f74e5 srv!SrvSmbFindNext2
b76d85a4 b76f6309 srv!SrvSmbQueryFsInformation
b76d85a8 b7707293 srv!SrvSmbSetFsInformation
b76d85ac b77041ad srv!SrvSmbQueryPathInformation
b76d85b0 b7703ce7 srv!SrvSmbSetPathInformation
b76d85b4 b77025ad srv!SrvSmbQueryFileInformation
b76d85b8 b770367f srv!SrvSmbSetFileInformation
b76d85bc b7705c85 srv!SrvSmbFsctl
b76d85c0 b7706419 srv!SrvSmbIoctl2
b76d85c4 b7705c85 srv!SrvSmbFsctl
b76d85c8 b7705c85 srv!SrvSmbFsctl
b76d85cc b77047bb srv!SrvSmbCreateDirectory2
b76d85d0 8993fb28
b76d85d4 b7709a51 srv!SrvTransactionNotImplemented
b76d85d8 b76fb144 srv!SrvSmbGetDfsReferral
b76d85dc b76faf7e srv!SrvSmbReportDfsInconsistency
b76d85e0 00000000
之后發包就能觸發該函數調用:
kd> k
# ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 b72b4cf0 b76e8d76 0x8993fb28
01 b72b4d04 b76e341f srv!ExecuteTransaction+0xdb
02 b72b4d7c b76ca836 srv!SrvSmbTransaction+0x7ac
03 b72b4d88 b76dad98 srv!SrvProcessSmb+0xb7
04 b72b4dac 805c6160 srv!WorkerThread+0x11e
05 b72b4ddc 80541dd2 nt!PspSystemThreadStartup+0x34
06 00000000 00000000 nt!KiThreadStartup+0x16
6 關于補丁
了解了漏洞原理之后修補都很簡單了。只要在srv!SrvFindTransaction里面判斷一下SMB COMMAND的類型是否一致就好了。
修補前
TRANSCATION *__fastcall SrvFindTransaction(int pConnect, SMB_HEADER *Fid, __int16 a3)
{
_DWORD **pTransList; // eax@4
_DWORD *v6; // ebx@4
PDEVICE_OBJECT v7; // ecx@5
TRANSCATION *pTransaction; // esi@6
char Command_Trans; // al@10
char Command_header; // dl@10
__int16 MIDorFID; // [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 )
goto LABEL_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) )
goto LABEL_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;
}
goto LABEL_13;
}
if ( BYTE1(pTransaction->field_0) == 2 )
{
_InterlockedIncrement((volatile signed __int32 *)(pTransaction->field_8 + 4));
goto LABEL_15;
}
LABEL_14:
pTransaction = 0;
LABEL_15:
ExReleaseResourceLite(Resource);
return pTransaction;
}
補丁點就是if ( Command_Trans != Command_header )看注釋的地方。
7 總結
總之,這個漏洞還是非常好的,遠程任意地址寫,還可以信息泄露。威力很大。
8 聯系作者
如果你感覺排版不舒服可以去這里下載原始的markdown文件。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/283/