Not All SYNs Are Created Equal


During a recent assessment I noticed that I was getting back (or, not getting back, as it were) a filtered response to nmap and hping SYN scans. That’s normal enough for sites that drop incoming scan traffic, but the weird part was that if I used a standard connect scan, i.e. one that completes the three-way-handshake, I would get back a ton of open ports on the same host.

So if I did a “regular” scan, I’d send a SYN, get back a SYN-ACK, and then respond with an ACK. Fair enough, but if I sent just the SYN from nmap or tcpdump, the host would not respond at all. Well, after a couple of minutes of head-scratching, logic revealed the path to the truth:

The two SYN packets are different.

If these two SYN packets weren’t different, then the target host would have no way of knowing that the SYN-scan’s SYN packet wasn’t legitimate, and as such would respond with a SYN-ACK as with the standard connect scan. In short, the only way for the host (or a filtering device in between) to react to one SYN differently than another is for the packet itself to be different.

Anatomy of a SYN

As it turns out, there’s a very tangible reason for the two packets being different. The SYN packets created by most port scanners out there are created via the raw socket interface, and they tend to have some fairly standard characteristics that stand out to both humans and computers (as we’ll see below).

Legitimate SYN packets, however, are created by the OS’s connect() syscall. This is what happens when you want to use a regular application on your system, like a web browser or a mail client. This is the “regular” way of building a SYN packet, and as we’ll see in a moment, the packets made in this way are quite different than those made by scanner applications.

The raw socket technique can be thought of as “building” packets; it’s a method for modifying actual packet headers before they leave the machine. Common applications of this include spoofing the source address, changing checksums, and lighting up odd TCP flag combinations. The connect() method, however, is a packaged deal. When you call connect(), you get pretty much the same kind of packet every time. You don’t get to mangle it, morph it, or corrupt it. What you get is what you get.

Given these differences, a number of products over the years have been coded to look at incoming SYN packets for the attributes associated with security scanners. They know that pretty much the only applications making these kinds of packets are illegitimate, so when they see them they immediately drop them.

The Differences Illustrated

Let’s actually take a look at the actual unique qualities of raw socket/scanner SYN packets and those created by connect(). Below are three SYN packets from three different applications:

An Nmap SYN (-sS) Scan

14:53:09.185860 IP (tos 0x0, ttl  45, id 61607, offset 0, flags [none], proto: TCP (6), length: 44) 
source.60058 > dest.22: S, cksum 0x885a (correct), 877120720:877120720(0) win 2048
0x0000:  4500 002c f0a7 0000 2d06 8121 8115 0c09  E..,....-..!....
0x0010:  8115 0dd0 ea9a 0016 3447 ccd0 0000 0000  ........4G......
0x0020:  6002 0800 885a 0000 0204 05b4            `....Z......

Unsupervised Learning — Security, Tech, and AI in 10 minutes…

Get a weekly breakdown of what's happening in security and tech—and why it matters.

An Nmap Connect (-sT) Scan

14:51:42.706802 IP (tos 0x0, ttl 64, id 61772, offset 0, flags [DF], proto: TCP (6), length: 60) 
source.36982 > dest.22: S, cksum 0x6e57 (correct), 113706876:113706876(0) win 5264
0x0000:  4500 003c f14c 4000 4006 2d6c 8115 0c09  E..<.L@[email protected]....
0x0010:  8115 0dd0 9076 0016 06c7 077c 0000 0000  .....v.....|....
0x0020:  a002 1490 6e57 0000 0204 0524 0402 080a  ....nW.....$....
0x0030:  14aa f630 0000 0000 0103 0302            ...0........

A Legitimate SYN From Firefox

15:31:34.079416 IP (tos 0x0, ttl  64, id 20244, offset 0, flags [DF], proto: TCP (6), length: 60) 
source.35970 > dest.80: S, cksum 0x0ac1 (correct), 2647022145:2647022145(0) win 5840
0x0000:  4500 003c 4f14 4000 4006 7417 0afb 0257  E..
0x0010:  4815 222a 8c82 0050 9dc6 5a41 0000 0000  H."*...P..ZA....
0x0020:  a002 16d0 0ac1 0000 0204 05b4 0402 080a  ................
0x0030:  14b4 1555 0000 0000 0103 0302            ...U........


Notice that the two latter SYN packets are very similar. They are the two created by the OS’s connect() syscall, while the first packet was created via a raw socket. Here are a few of the differences:

  • The size of the connect() packets is 60 bytes, and only 44 for the raw socket packet.

  • The TTL values for the connect() packets are 64, and 45 on the raw socket packet.

  • The “don’t fragment” bit is set in the “legitimate”, connect() packets, but not in the other.

So the upshot is that you may actually get better scan results in many environments by doing “regular”, connect scans instead of SYN scans because of how the SYNs for each are constructed.

The next thing on my agenda is to use hping and/or nemesis to make a few custom SYN packets. I can build some that look just like the legitimate SYN packets — matching the size, TTL, and flag contents exactly. Then I can simply toggle each of them in sequence to figure out which value (or values) is considered illegitimate.

I’ll do that soon and post the results for anyone interested. ::

Related posts: