Software Vulnerability Exploitation Blog

Friday, February 23, 2007

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

The functions highlighted with red are the functions that involve in reassembly process WriteAndX command packet. ProcessSMBWriteX() process the DCE/RPC packet that has WriteAndX command. It calls SMB_Fragmentation() to see WriteAndX DCE/RPC packet is fragmented or not. If it is fragmented, SMB_Fragmentation() will call ReassembleSMBWriteX() to reassemble packet.To save time in discover the code section that vulnerable to buffer overflow, I decide to use the style “patch and diff” to discover it. I download Snort 2.6.1.3 and compare it with Snort 2.6.1 and found the following major change in ReassembleSMBWriteX():

  • 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;

Before _dcerpc->smb_state is set to STATE_GOT_NTCREATE, it has to be set to STATE_GOT_TREE_CONNECT by ProcessSMBTreeConnXReq():


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() -> ProcessSMBNTCeate() -> ProcessSMBWriteX()

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:

Photobucket - Video and Image Hosting

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:

Photobucket - Video and Image Hosting

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);
if ( success )
return 0;

I investigate ProcessDCERPCMessage() immediately and found something interest:


if ( !IsCompleteDCERPCMessage(data, size) )
return 0;

It calls IsCompleteDCERPCMessage too. Because the packet that I create is not processed in SMB_Fragmentation() before. So, ProcessDECRPCMessage() always be called. If I wanna to let ProcessDCERPCMessage() return 0, I have to create the packet at make IsCompleteDCERPCMessage() return 0. But if I create the packet like that, ReassembleSMBWriteX will never be called. I cannot create a packet that when processed with IsCompleteDCERPCMessage() 2 times, the function will return different result.

Then, I think about information in advisory:

  • Multiple WriteAndX commands are necessary

Yes, may be this trick will be the key to make IsCompleteDCERPCMessage() return the different result. I have to create the packet that when processed by IsCompleteDCERPCMessage() at the first time it will return false, but at later it will return true. I look at the code in IsCompleteDCERPCMessage() again:


/* Wait until we have the whole DCE/RPC message */
if ( dcerpc->frag_length > size )
return 0;

If I fake frag length field, make it less than size, IsCompleteDCERPCMessage() will return false at the first time. But what I can do to make it true at later. I think the following code section in SMB_Fragmentation() answer me:


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;
if ( IsCompleteDCERPCMessage(_dcerpc->write_andx_buf, _dcerpc->write_andx_buf_len) )

_dcerpc->write_andx_buf_len is add with writeX_length every time that SMB_Fragmentation is called with the condition that there is one or more DCE/RPC packet processed by this function and is not finished. From this information, I decide to add 2nd WriteAndX command into the packet with conditions:

  • 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:

Photobucket - Video and Image Hosting

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 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 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 Blogger Trirat Puttaraksa, at March 12, 2007 9:56 AM  

Post a Comment

<< Home