find datagrams with particular data (here, packets with command MAIL from the SMTP protocol and GET command from HTTP)

Notes

I usually type tcpdump -n -i eth1 -s 1600 before my filter but I won't do that throughout the article. -n prevents DNS lookups, -i specifies the interface and -s specifies the size of the packets (default is 65536 bytes). Be careful if you use -s 0 because depending on the version, you might be capturing 64K or full-lenght packets.

All commands are typed as root.

Feel free to contact me for comments, suggestions or for reporting mistakes. Let me know if something is not clear.

I'll try to keep this document updated with new useful rules.

Let's start

Before I begin with advanced filters, let's review the basic syntax of tcpdump.

I'll consider we are only working with the IPv4 protocol suite for these examples.

In an ideal world, every field would fit inside one byte. This is not the case, of course.

Exercise: Are IP options set?

Let's say we want to know if the IP header has options set. We can't just try to filter out the 21st byte because if no options are set, data start at the 21st byte. We know a "normal" header is usually 20 bytes (160 bits) long. With options set, the header is longer than that. The IP header has the header length field which we will filter here to know if the header is longer than 20 bytes.

+-+-+-+-+-+-+-+-+
|Version| IHL |
+-+-+-+-+-+-+-+-+

Usually the first byte has a value of 01000101 in binary.

Anyhow, we need to divide the first byte in half...

0100 0101
0100 = 4 in decimal. This is the IP version.
0101 = 5 in decimal. This is the number of blocks of 32 bits in the headers. 5 x 32 bits = 160 bits or 20 bytes.

The second half of the first byte would be bigger than 5 if the header had IP options set.

We have two ways of dealing with that kind of filters.
- Either try to match a value bigger than 01000101. This would trigger matches for IPv4 traffic with IP options set, but ALSO any IPv6 traffic !

The first field in the IP header would usually have a decimal value of 69. If we had IP options set, we would probably have 01000110 (IPv4 = 4 + header = 6), which in decimal equals 70.

This rule should do the job:

tcpdump 'ip[0] > 69'

Somehow, the proper way is to mask the first half/field of the first byte, because as mentioned earlier, this filter would match any IPv6 traffic.
- The proper/right way : "masking" the first half of the byte

0100 0101 will become 0000 0101 thanks to a mask of 0000 1111

0100 0101 : 1st byte originally
0000 1111 : mask (0xf in hex or 15 in decimal). 0 will mask the values while 1 will keep the values intact.
vvvv vvvv <-- those are arrows :-)
0000 0101 : final result

You should see the mask as a power switch. 1 means on/enabled, 0 means off/disabled.

Matching HTTP data

Let's make a filter that will find any packets containing GET requests.

The HTTP request will begin by:

GET / HTTP/1.1\r\n (16 bytes counting the carriage return but not the backslashes !)

If no IP options are set.. the GET command will use bytes 20, 21 and 22. Usually, options takes 12 bytes (12th byte indicates the header length, which should report 32 bytes). So we should match bytes 32, 33 and 34 (1st byte = byte 0).

Tcpdump can only match data size of either 1, 2 or 4 bytes, we will take the following ASCII character following the GET command (a space).

We may want to filter ICMP messages type 4, these kind of messages are sent in case of congestion of the network.

tcpdump 'icmp[0] = 4'

If we want to find the ICMP echo replies only, having an ID of 500. By looking at the image with all the ICMP packet description we see the ICMP echo reply have the ID spread across the 5th and 6th byte. For some reason, we have to filter out with the value in hex.

tcpdump -i eth0 '(icmp[0] = 0) and (icmp[4:2] = 0x1f4)'

Acknowledgment

Yousong Zhou (China):

tcpflags

Python hex conversion

portrange

typo corrections

Keith Makan (South Africa):

reference to this article in "Penetration Testing with the Bash shell" published at Packt Publishing, available here.