Snort 2.6.1 DCE/RPC Preprocessor Remote Buffer Overflow: Part 1 - Denial Of Service
It had been 3 months that I didn’t post anything in my blog. The reason why I stopped it because I have to do many things in my work and there is no interested topic. But today I think I’ve found the new topic, yes it is Snort DEC/RPC preprocessor buffer overflow. It is the interested topic because there is no PoC provide with the advisory. I’m curious and wanna to know how to exploit it, so things come back again :)
First of all, I have to gather information as much as possible to find the starting point and so on. I think information from http://www.iss.net/threats/257.html is very useful. It provides me these information:
- It is stack-based buffer overflow
- DCE/RPC is dynamic preprocessor and enabled by default
- Overflow occurs in reassembly process
- The attacker can attack Snort with a single packet
- Multiple WriteAndX commands are necessary
From these information, I decide to view Snort’s source code directory snort-2.6.1\src\dynamic-preprocessors\dcerpc to find the component and function that responsible for DCE/RPC reassembly. I’ve found that the file that should be investigated is smb_andx_decode.c. This file has the following function:
- ReassembleSMBWriteX
- SMB_Fragmentation
- IsIPC
- SkipBytes
- SkipBytesWide
- ProcessSMBTreeConnXReq
- ProcessSMBNTCreateX
- ProcessSMBWriteX
- ProcessSMBTransaction
- ProcessSMBReadX
- ProcessSMBSetupXReq
- ProcessSMBLogoffXReq
- ProcessSMBLockingX
- It checks value of writeX_len variable with size of SMB_WRITEX_REQ
- It use sizeof(SMB_WRITEX_REQ) as the 3rd parameter in memcpy to copy value to temp_writeX variable
- It use SafeMemcpy instead of memcpy to copy value to _dpd.altBuffer
- There is a lot of error message said that WriteX header is too big
There are many possibilities that overflow occurs because of writeX_len variable. Its value comes from the expression:
unsigned int writeX_len = smb_data – (u_int8_t *) writeX;
For the valid DCE/RPC packet, writeX_len is the size of WriteAndX header. So it would be copied to temp_writeX with no problem. But if writeX_len is very big, because of malicious packet, temp_writeX and the stack will be overwrite.
Now, I’m going to investigate that writeX_len could be controlled. To control writeX_len, I have to be able control smb_data or writeX variable. I track back from ReassembleSMBWriteX() to ProcessSMBWriteX():
…
static void ReassembleSMBWriteX(SMB_WRITEX_REQ *writeX, u_int8_t *smb_data)
{
…
unsigned int writeX_len = smb_data - (u_int8_t *)writeX;
…
}
int SMB_Fragmentation(u_int8_t *smb_hdr, SMB_WRITEX_REQ *writeX, u_int8_t *smb_data, u_int16_t data_size)
{
…
ReassembleSMBWriteX(writeX, smb_data);
…
}
int ProcessSMBWriteX(SMB_HDR *smbHdr, u_int8_t *data, u_int16_t size, u_int16_t total_size)
{
SMB_WRITEX_REQ *writeX = (SMB_WRITEX_REQ) *data;
…
dce_stub_data = (u_int8_t *) smbHdr + writeX->dataOffset
…
SMB_Fragmentation((u_int8_t *) smbHdr, writeX, dce_stub_data, data_size)
…
}
…
The variable writeX in ReassembleSMBWriteX() is the writeX in ProcessSMBWriteX() and variable smb_data in ReassembleSMBWriteX() is dce_stub_data in ProcessSMBWriteX(). The variable dce_stub_data is pointer to the DCE/RPC header which start after WriteAndX header. It’s value depend on writeX->dataOffset which is not checked and I can control it, so I can control smb_data in ReassembleSMBWriteX() :)
Now, I know that where the overflow occur and how it’s overflow. The next step is the implementation. In this step I will investigate how to create a packet that triggers the overflow. I decide to start at ProcessSMBWriteX(), not ReassembleWriteX(), because I wanna to know how to construct WriteAndX packet that will triggers SMB_Fragmentation().
If I wanna to let ProcessSMBWriteX call SMB_Fragmentation(), I have to create WriteAndX header with the following condiction:
- writeX->dataOffset is less than total_size value (this is the simple one, but don’t forget that this is the value to triggers the overflow, so it has to be more than size of WriteAndX header)
- variable _dcerpc->smb_state has to be STATE_GOT_NTCREATE
I focus on _dcerpc->smb_state variable and browse the source codes. I’ve found that the statement that set _dcerpc->smb_state to STATE_GOT_NTCREATE is in ProcessSMBNTCreate().
…
if ( _dcerpc->smb_state == STATE_GOT_TREE_CONNECT )
_dcerpc->smb_state = STATE_GOT_NTCREATE;
…
if ( isIPC && _dcerpc->smb_state == STATE_START )
_dcerpc->smb_state = STATE_GOT_TREE_CONNECT
…
And _dcerpc->smb_state is set to STATE_START (value 0) by default at DCERPC_Setup() in snort_dcerpc.c:
…
memset(x, 0, size);
…
_dcerpc = x
…
So, the flow of function call in this time is:
ProcessSMBTreeConnXReq() is called by ProcessNextSMBCommand() in snort_dcerpc.c:
case SMB_COM_TREE_CONNECT_ANDX:
return ProcessSMBTreeConnXReq(smbHdr, data, size, total_size)
…
ProcessNextSMBCommand() is called by ProcessRawSMB():
return ProcessNextSMBCommand(smbHdr->command, smbHdr, data + sizeof(SMB_HDR), data_size, size);
…
ProcessRawSMB() is called from DCERPCDecode():
if ( SMBPorts[(p->dst_port/8)] & (1<<(p->dst_port%8)) )
{
/* Raw SMB */
return ProcessRawSMB(p->payload, p->payload_size);
…
and DCERPC_AutoDetect():
if ( is_smb )
{
/* Process as SMB */
return ProcessRawSMB(data, size);
…
DCERPC_AutoDetect() is called by DCERPCDecode():
if ( _autodetect )
return DCERPC_AutoDetect(p->payload, p->payload_size)
…
So, the flow of function call in this time is:
… -> DCERPCDecode() -> DCERPC_AutoDetect() -> ProcessRawSMB() -> ProcessNextSMBCommand() -> ProcessSMBTreeConnXReq() -> ProcessSMBNTCeate() -> ProcessSMBWriteX()
The functions highlighted with red are function that involve in processing SMB packet. Now, the layout of packet that I have to create look like this:
Now, the next step is to determine that how could I create the packet that snort finally call ReassembleSMBWriteX(). Because ReassembleSMBWrite() is called by SMB_Fragmentation(), which called by ProcessSMBWriteX(), so I decide to investigate SMB_Fragmentation().
The simplified flow chart of SMB_Fragmentation() look like this:
As you can see, if I wanna to let SMB_Fragmentation() call ReassembleSMBWriteX(), I have to create a packet that when IsCompleteDCERPCMessage process it, the function will return true.
IsCompleteDCERPCMessage() , in dcerpc.c, will return false if packet has one of the following properties:
- size of packet’s DCE/RPC Message less than size of DCERPC_REQ (this data structure is defined in dcerpc.h - 24 bytes)
- version of DCE/RPC is not 5
- DCE/RPC packet type is not DCERPC_REQUEST and DCERPC_BIND
- dcerpc->frag_length (Frag length field in DCERPC header) is more than the whole DCE/RPC message size
Everything seems fine, I can create the packet that make IsCompleteDCERPCMessage() return true. However, after I send the packet to snort, ReassembleSMBWriteX() never be called. I insert the message to debug snort and I’ve found that IsCompleteDCERPCMessage() never be called !!! SMB_Fragmentation() return 0 at line:
/* If not yet reassembling, attempt to parse as full DCE/RPC packet */
if ( !(_dcerpc->fragmentation & SMB_FRAGMENTATION) )
{
success = ProcessDCERPCMessage(smb_hdr, smb_data, data_size);
return 0;
…
I investigate ProcessDCERPCMessage() immediately and found something interest:
if ( !IsCompleteDCERPCMessage(data, size) )
return 0;
…
Then, I think about information in advisory:
- Multiple WriteAndX commands are necessary
…
/* Wait until we have the whole DCE/RPC message */
if ( dcerpc->frag_length > size )
return 0;
…
…
memcpy(_dcerpc->write_andx_buf + _dcerpc->write_andx_buf_len, smb_data, writeX_length);
_dcerpc->write_andx_buf_len += writeX_length;
_dcerpc->fragmentation |= SMB_FRAGMENTATION;
…
- The first DCE/RPC message frag length field is greater than the size of remaining packet size (1st DCE/RPC message + 2nd WriteAndX header + 2nd DCE/RPC Message) – this will make IsCompleteDCERPCMessage() return false at the first time.
- Sum of first write_andx_buf_len and 2nd WriteAndX dataLength field must greater than frag length of 1st WriteAndX – this will make IsCompleteDCERPCMessage() return true and call ReassembleSMBWriteAndX()
- 2nd WriteAndX dataOffset must great enough to overwrite the stack – overflow condition.
And the layout of WriteX chain look like this:
After I rerun the exploit, I got result of debug message like this:
writeX->dataOffset is 182
dcerpc->frag_length is 511
size is 176
IsComplete return because dcerpc->frag_length > size
wrnteX_length is 72
now _decrpc->write_andx_buf_len is 72
dcerpc->frag_length is 511
size is 72
IsComplete return because dcerpc->frag_length > size
writeX->dataOffset is 304
writeX_length is 511
now _decrpc->write_andx_buf_len is 583
dcerpc->frag_length is 511
size is 583
temp_writeX has size 31 bytes
writeX_len is 50
so you overwrite 19 bytes beyond temp_writeX
dcerpc->frag_length is 511
size is 583
Segmentation fault
Yezzz, snort crashes :)
P.S. Because I have no time to look for code execution, so I just think the theory about this.
- Increase the numbers of bytes to overwrite. As u can see in my PoC, it overwrites only 19 bytes beyond "temp_writeX" variable.
This number is not enough for DoS. So, increasing the numbers of bytes to overwrite
the memory may be let us control the EIP. Note: this condition will success or fail is also depened on OS buffer overflow protection mechanism ex. ASLR (I hate it). If you're exploiting on the Linux system that has buffer overflow protection guard, ex Fedora Core 4, you have to bypass these things to made the code execution. So I my opinion, code execution on Windows (2000, XP or 2003 SP0) should be easier than on Linux system that has the guard, just overwrite enough numbers of bytes to memory :) - For the shellcode issue, you can inject it as a part of WriteX header or even DCE/RPC message. But this may lead you into some problem, you have to make it looks like the valid packet, so snort will process it.
3 Comments:
Amazing work, as your other posts, thank you.
By Anonymous, at March 02, 2007 12:26 AM
How long did it take you to analyze all of this and get to a working DoS POC? How much longer did it take for code execution PoC?
By Anonymous, at March 12, 2007 4:58 AM
For DoS PoC, I take about 1-2 days to do it. This seems take a long time because I'm not familiar with DCE/RPC protocol. For code execution PoC, I take about 2-3 hours to do it :)
By Trirat Puttaraksa, at March 12, 2007 9:56 AM
Post a Comment
<< Home