If this challenge seemed harder than it should be,you are on the right track.
I ran across this problem when writing my Packet Decode tool. I have to say, it was a cool exercise for me, as I never really thought about creating tcpdump and Wireshark filters for every possible IP, TCP, UDP and ICMP field and/or value. By far the TCP options field is the most “broken” from a packet decode perspective than any other IP field.
First let’s talk about how the TCP options should have been implemented. If you look at the IPv4 options field, it begins with a “Type” identifier. If you are interested in a specific IP option, it is just a matter of checking this field for the right bit combination. Had the TCP options been implemented this way, this challenge would have been pretty straight forward.
Just about all TCP options have a “Kind” and a “Length” field, both of which are 1 byte in size. The exceptions are “End of Option List” and “No-Operation” which only have a Kind field, and thus are one byte in size. Here’s a list of the common TCP options:

Page 15 of RFC 793 tells us “The TCP header (even one including options) is an integral number of 32 bits long.” In other words, the TCP header size in bytes must be evenly divisible by four (20 bytes, 24 bytes, etc.). If you look at the list of TCP options, only “maximum segment size” is divisible by four. So the use of any other options are going to require padding.
How the padding should be applied is a bit unclear. If we look at page 26 of RFC 1323 we find this:
APPENDIX A: IMPLEMENTATION SUGGESTIONS
The following layouts are recommended for sending options on non-SYN
segments, to achieve maximum feasible alignment of 32-bit and 64-bit
machines.
+--------+--------+--------+--------+
| NOP | NOP | TSopt | 10 |
+--------+--------+--------+--------+
| TSval timestamp |
+--------+--------+--------+--------+
| TSecr timestamp |
+--------+--------+--------+--------+
Note the NOP padding appears before the timestamp option, not at the end like you might expect. Also note the RFC specifically says this is for “non-SYN segments” and that it is “recommended”, not required. Seems however that most operating systems follow this recommendation and always place padding before the Kind and Length bytes. I’ve checked Windows, Linux, Mac, various hardware, etc. and they all put the padding at the beginning.
So we can count on this being the “standard”, right? Not quite. Page 17 of RFC 793 describes NOP this way:
This option code may be used between options, for example, to
align the beginning of a subsequent option on a word boundary.
There is no guarantee that senders will use this option, so
receivers must be prepared to process options even if they do
not begin on a word boundary.
In other words, its not just that NOP may or may not show up at the beginning, NOP might not be used at all! It is entirely legal to layout the TCP option field with no NOP padding and just use End of Option List as filler at the end to achieve the proper boundary.
So what do we end up with for a filter? If we count on NOP before the option we end up with a filter that looks like this:
tcp[13]&2=2 and tcp[12]&240>80 and ((tcp[20]=1 and tcp[21:2]=0×0303) or (tcp[24]=1 and tcp[25:2]=0×0303) or (tcp[28]=1 and tcp[29:2]=0×0303) or (tcp[32]=1 and tcp[33:2]=0×0303) or (tcp[36]=1 and tcp[37:2]=0×0303) or (tcp[40]=1 and tcp[41:2]=0×0303) or (tcp[44]=1 and tcp[45:2]=0×0303) or (tcp[48]=1 and tcp[49:2]=0×0303) or (tcp[52]=1 and tcp[53:2]=0×0303) or (tcp[56]=1 and tcp[57:2]=0×0303))
To break down what this filter is doing:
- Only check SYN & SYN/ACK packets: tcp[13]&2=2
- TCP header is greater than 20 bytes (options are set): tcp[12]&240>80
- Check the first byte of each four byte boundary for NOP: tcp[20]=1, tcp[24=1, ...
- Check the next two bytes to see if Kind=3 and Length=3: tcp[21:2]=0×0303, tcp[25:2]=0×0303, …
If however we want to ensure that we catch all possibilities in case a system does not implement NOP we end up with:
tcp[13]&2=2 and tcp[12]&240>80 and (tcp[20:2]=0×0303 or tcp[21:2]=0×0303 or tcp[22:2]=0×0303 or tcp[23:2]=0×0303 or tcp[24:2]=0×0303 or tcp[25:2]=0×0303 or tcp[26:2]=0×0303 or tcp[27:2]=0×0303 or tcp[28:2]=0×0303 or tcp[29:2]=0×0303 or tcp[30:2]=0×0303 or tcp[31:2]=0×0303 or tcp[32:2]=0×0303 or tcp[33:2]=0×0303 or tcp[34:2]=0×0303 or tcp[35:2]=0×0303 or tcp[36:2]=0×0303 or tcp[37:2]=0×0303 or tcp[38:2]=0×0303 or tcp[39:2]=0×0303 or tcp[40:2]=0×0303 or tcp[41:2]=0×0303 or tcp[42:2]=0×0303 or tcp[43:2]=0×0303 or tcp[44:2]=0×0303 or tcp[45:2]=0×0303 or tcp[46:2]=0×0303 or tcp[47:2]=0×0303 or tcp[48:2]=0×0303 or tcp[49:2]=0×0303 or tcp[50:2]=0×0303 or tcp[51:2]=0×0303 or tcp[52:2]=0×0303 or tcp[53:2]=0×0303 or tcp[54:2]=0×0303 or tcp[55:2]=0×0303 or tcp[56:2]=0×0303 or tcp[57:2]=0×0303 or tcp[58:2]=0×0303)
The difference with this filter is that we are checking Kind=3 and Length=3 all the way through the options field (as Elizabeth suggested).
Can either of these filters generate false positives? Absolutely! Two possibilities:
- The value of Timestamp may match the pattern we are looking for.
- The filter assumes a 40 byte option field. It could be less with these values in the payload.
So which filter should you use? The first will generate fewer false positives but miss systems that are RFC compliant but different from the norm. The second will always catch Window Scale if it has been set, but the chance of a false positive is higher.
I’m going to designate Elizabeth as the winner of the challenge. A few others were just as close but she was the only one with the guts to post her train of thought in the comments. I’m going to award a second prize to Jeff who came up with this solution partially kidding around:
tcpdump -nn | grep ‘wscale ‘ > wscale-matches.txt
This will not generate an actual packet capture, but you could do a:
tcpdump -nn -X -s 0 | grep ‘wscale ‘ > wscale-matches.txt
And then run the output through txt2cap to get it back into pcap format. He didn’t follow the challenge specifically, but you’ve gotta give kudos for thinking outside the box as this fixes all false positive issues.
Elizabeth and Jeff, I’ll be contacting you both via e-mail. Congrats!