Understanding SYN Scan
SECURITY
Understanding SYN Scan
2017-04-27
By
David "DeMO" Martínez Oliveira

A SYN scan is, maybe, one of the simplest stealth port scanning techniques you can think about. There are some claims around the Internet about how it works and I was curious to know more about thats. This post summarises my findings.
Recently I had to look into the details of port scanning and I started looking into the SYN Scan, as being one of the simplest, so-called stealth, port scanning techniques. The idea is very simple and it is very well explained in many places in the Internet. Let's just repeat it here for completeness.

SYN Scan

Roughly, a SYN Scan works initiating a normal TCP connection but not completing the TCP three-way-handshake. A normal connection looks like this:

SOURCE		DESTINATION	SOURCE		DESTINATION  
+--------  SYN ------->           +--------  SYN ------->           
<------ SYN | ACK ----+		  <--------  RST -------+
+--------- ACK -------->
 
    OPEN PORT                           CLOSED PORT
  
A SYN Scan, will send the SYN packet normally, but it will never send back the ACK to complete the connection (in the case that the port is open).

What happens is that, we know the estate of the remote port just after receiving the first packet from the destination. If it is a SYN|ACK, the port is Open and we do not really need to complete the three-way-handshake. In other words we do not need to send the last ACK packet. If we receive a RST, then the port is closed and we are done.

A real port scanner like nmap, considers a few more cases: Filtered packets not returning anything and host/networks unreachable errors manifested as ICMP packets received back by the port scanner. However, all this is very well explained in many different websites and I will not go further into those topics.

The Claims

Despite of the very good explanation on how the SYN scan works, I have found a couple of common sentences all around the Internet that are not fully explained in detail, but taken for granted by everybody. This is what I want to cover in this paper. Specifically the claims I'm looking at are:

  • A connect scan is much noisy than a SYN scan.
  • The scanner (e.g. nmap) send a RST packet after receiving the SYN|ACK
  • You can be DoSed if you do not send that last RST packet

I will go one by one through this list to try to figure out what is behind each of those claims.

The Noisy Connect Scan

The first claim is that a SYN scan is a less noisy that a full connect scan. This is completely true but... what does it really mean?. After looking around for some time the conclusion I reached is that this is not as sophisticated as it may sound.

To illustrate this claim, let's make use of a small server program:

#include <stdio.h>
#include <string.h>
  
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int
main (int argc, char *argv[])
{
  struct sockaddr_in   server, client;
  socklen_t            len = sizeof (struct sockaddr_in);
  int  s,s1;
  
  server.sin_addr.s_addr = INADDR_ANY;
  server.sin_family = AF_INET;
  server.sin_port = htons(9000);
  
  s = socket (PF_INET, SOCK_STREAM, 0);
  bind (s, (struct sockaddr *) &server, sizeof (server));
  listen (s, 10);
  
  while (1)
  {
    s1 = accept (s, (struct sockaddr *)&client, &len);
    printf ("Connection from %s\n", inet_ntoa (client.sin_addr));
    write (s1, "Hi!\n", 4);
    close (s1);
  }
  return 0;
}
  

This is a typical server program, that greets any client and.... LOGS its IP. The server can only get the IP of the client once the accept system call returns and that only happens whenever the connection is fully established.

Let's see how our server behaves when scanned using a connect scan and a SYN scan.

Connect and SYN scan on our test server

There you go. I bet this is the noisy thingy. It is at application level and it is the application who actually logs the IP. I will try an IDS (e.g. snort), but I will expect that an IDS can detect both scans without problems.

Let's move on

The RST Packet

You will read in many places around the Internet that a SYN scanner has to send a RST packet at the end. This claims comes from the fact that you will always see that packet if you sniff the traffic when doing a scan.

Just start up you preferred sniffer, and scan one single port you know it is open in your machine (alternatively, start a netcat listening in some port `nc -l 10000`). You will see something like:

SYN Scan as seen by Wireshark

So, the RST packet seems to be sent but... it is not sent by the port scanner. The easiest way to verify this is to strace nmap and see what it is actually sending. (Alternatively use the --packet-trace flag):

Stracing NMAP

Let's see what we will get:

$ sudo strace -e trace=network -x -s 1024 -f nmap -sS 127.0.0.1 -p 22
socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)

Starting Nmap 5.21 ( http://nmap.org ) at 2016-11-04 14:36 CET
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
socket(PF_INET, SOCK_RAW, IPPROTO_RAW)  = 3
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
setsockopt(3, SOL_IP, IP_HDRINCL, [1], 4) = 0
socket(PF_PACKET, SOCK_RAW, 768)        = 4
bind(4, {sa_family=AF_PACKET, proto=0x03, if1, pkttype=PACKET_HOST, addr(0)={0, }, 20) = 0
getsockopt(4, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
setsockopt(4, SOL_PACKET, PACKET_AUXDATA, [1], 4) = 0
getsockopt(4, SOL_PACKET, PACKET_HDRLEN, [28], [4]) = 0
setsockopt(4, SOL_PACKET, PACKET_VERSION, [1], 4) = 0
setsockopt(4, SOL_PACKET, PACKET_RESERVE, [4], 4) = 0
getsockopt(4, SOL_SOCKET, SO_TYPE, [3], [4]) = 0
getsockopt(4, SOL_PACKET, PACKET_RESERVE, [4], [4]) = 0
setsockopt(4, SOL_PACKET, PACKET_RX_RING, {block_size=4096, block_nr=518, frame_size=176, frame_nr=11914}, 16) = 0
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 5
setsockopt(4, SOL_SOCKET, SO_ATTACH_FILTER, "\x01\x00\x00\x00\x00\x00\x00\x00\x68\x93\xa3\x6e\x28\x7f\x00\x00", 16) = 0
recvfrom(4, 0x7ffc637e32af, 1, 32, 0, 0) = -1 EAGAIN (Resource temporarily unavailable)
setsockopt(4, SOL_SOCKET, SO_ATTACH_FILTER, "\x36\x00\x49\x00\x00\x00\x00\x00\xb0\x82\xbf\x01\x00\x00\x00\x00", 16) = 0
sendto(3, "\x45\x00\x00\x2c\x59\x23\x00\x00\x35\x06\x2e\xa7\x7f\x00\x00\x01\x7f\x00\x00\x01\xeb\xc2\x00\x16\x0f\x66\x60\x1b\x00\x00\x00\x00\x60\x02\x08\x00\x36\xca\x00\x00\x02\x04\x05\xb4", 44, 0, {sa_family=AF_INET, sin_port=htons(22), sin_addr=inet_addr("127.0.0.1")}, 16) = 44
setsockopt(4, SOL_PACKET, PACKET_RX_RING, {block_size=0, block_nr=0, frame_size=0, frame_nr=0}, 16) = -1 EBUSY (Device or resource busy)
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00028s latency).
PORT   STATE SERVICE
22/tcp open  ssh

Nmap done: 1 IP address (1 host up) scanned in 0.13 seconds
  

Well, you can analyze the whole trace, but the relevant line is this:

  sendto(3, "\x45\x00\x00\x2c\x59\x23\x00\x00\x35\x06\x2e\xa7\x7f\x00\x00\x01\x7f\x00\x00\x01\xeb\xc2\x00\x16\x0f\x66\x60\x1b\x00\x00\x00\x00\x60\x02\x08\x00\x36\xca\x00\x00\x02\x04\x05\xb4", 44, 0, {sa_family=AF_INET, sin_port=htons(22), sin_addr=inet_addr("127.0.0.1")}, 16) = 44
  

This is the one and only packet sent by nmap during a SYN scan. You can parse it, or even easier, you can capture it with wireshark and match the contents and you will see that those hex numbers compose a TCP SYN packet.

The mysterious RST packet

As you can see there is nothing else send out by nmap after the SYN packet. So, where does this RST packet comes from?

Easy. It is generated by the local TCP/IP stack. The default behaviour of the stack is to reply with a RST packet to anything received that does not make sense (not exactly, but it can be taken as a behaviour baseline). Actually this is abused by other types of scanning techniques that wouldn't work otherwise.

The kernel keeps some internal structures for TCP sockets in order to identify the connection peers, keep buffers, etc... When a TCP packet arrives it has to be matched to one of those structures (unless it is a SYN packet and therefore there is no kernel structure yet) in order for TCP to do its job. If there is no match, the default behavior of the stack for an unexpected packet is to send back a RST to tell the other end: Hey! man, this may be a mistake. Just stop bothering me

The conclusion of all this so far is:

  • The state of the remote port is known before the RST packet even leaves the machine. If the reply is SYN|ACK port is open, if it is RST port is closed
  • The port scanner does not really need to send the RST packet and actually nmap on a Linux box does not send it.
  • In normal conditions, the local TCP/IP stack will produce the RST packet anyway, so your sniffer and the remote machine will see it.

But, what will happen if that RST packet does not really reach the remote machine.

Getting a DoS attack from your scan

If you have been reading about this on the Internet, and even in some books, you will find a text like this:

The port scanner has to send a RST packet back to the victim to close the connection and avoid a potential DoS against the scanning machine.

The basis for this claim is how TCP works. TCP is intended to make sure that the data is reliably transmitted and to achieve this goal it uses the store and resend approach. Let's see what does this mean.

When the remote machine sends out the SYN|ACK packet in response to the connection attempt (the original SYN packet), the machine will store that packet in memory until the remote end sends back an ACK packet saying: "I have received your packet save and sound". If that ACKnowledge does not come within a certain time, the TCP stack will try to resend it and wait again for the ACK... just in case it got lost or dropped somewhere in its way.

This process can be finished in three ways:

  • A RST packet is received indicating that ACK packet will never be send and the process has to stop.
  • The ACK packet is received and the communication continues normally
  • An ICMP message is received indicating some problem that prevents the message to be delivered... host/network unreachable...(it you disconnect the wire, there is nothing TCP can do :).
  • The ACK packet will never come. TCP will retry to re-send the packet a few more timed before giving up

Is this last case the one that the DoS claim refers to. However, it is a bit of an exaggeration. Let's see way.

Will we really be DoSed?

To find out if we will be DoSed when we do not send the `RST` packet we have to get a rough idea of the amount of traffic involved in the scanning process. To estimate this we need to know a bit more about how this retry process works.

The TCP packet resend process makes use of a system parameter: The number of SYN|ACKs retries. These parameter can be checked at /proc/sys/net/ipv4.

  $  cat /proc/sys/net/ipv4/tcp_synack_retries
  5
  

Without going in all the details, the re-try algorithm uses an exponential timeout that starts with an increment of 1 second and produces the following sequence:

  1   3   6  12  24

Taking into account that a SYN|ACK packet is 44 bytes (this is the minimum IP + TCP packet size). We will just get five 44 bytes long packets during a period of 24 seconds.

However, the problem will arise when scanning big networks with many hosts up, each one running many services.

A quick analysis shows that this is not really a DoS problem, but a complete waste of resources. Let's see why using some numbers.

Calculations

Let's assume we are using a tool like nmap, that scans 1000 ports by default, in a /24 network. In other words we are scanning 1000 ports in 255 hosts. Assuming that all hosts are up and no firewall is in place, the scan itself will produce as a minimum:

255 hosts * 1000 ports/host * 44 bytes/port = 11220000 bytes = 10957 KB = 10.7 MB

That's quite some data. And that is just generated whenever you press enter (Note: that scanning all ports on all host and having 255 hosts up is a bit of a extreme case...).

Now, we have to assume that the machine running the scanner can cope with this data without problems... otherwise it will just get DoSed by the fact of kicking off the scanner.

So, what will happen if the RST packet is not sent at the end of the scanning?. Well, for those 1000 ports on each host that are actually open, we will start getting re-transmissions of the SYN|ACK packets. Let's assume the worst case (and unrealistic) scenario. All host are up and all 1000 ports are open:

In this case we will just receive, exactly the same amount of data we had already received with our normal scan, 1 second later. Then 3 seconds later. Then 6 seconds later... and after 24 seconds it will just stop.

Overall, the machine doing the scanning will receive around 60MB in 24 seconds. That is quite some data but, for nowadays standard, it is unlikely that such traffic will DoS even a normal Internet connection. In other words, the machine will receive the same traffic that would receive if 5 similar scans were launched in the next 24 secs.

So, the main conclusion of this rough analysis is:

  • If not sending the RST packet will DoS the attacker machine... then the scan itself will also DoS the machine (of course for a shorter period of time)
  • In normal conditions the amount of data coming back from the scanned network will hardly suppose a DoS. It is unrealistic that you scan a whole network were all the hosts are up and all of them have 1000 ports open.
  • However, not sending the RST packet is a dirty solution as well as a waste of resources. Fortunately, the kernel will send the packet for us anyways :). Summing up, you do not have to send the RST but you should do it.

Conclusions

We have gone into the details of many common claims we found in the Internet regarding SYN scans but that are not usually explained in detail. For me was interesting to see how, something that is mentioned all over the Internet as a trivial thing has some tricky details behind. Of course, I may be wrong on my analysis... so any feedback is appreciated.

Header Image Credits: Martinelle