Subject: [Phrack] Port Scanning without the SYN flag
.oO Phrack 49 Oo.
Volume Seven, Issue Forty-Nine
15 of 16
Port Scanning without the SYN flag / Uriel Maimon
(lifesux@cox.org)
---------------------------------------------------------
Introduction :
--------------
During the course of time, there has risen a demand to know the services
a certain host offers. The field of portscanning rose to offer a solution
to this need. At first, implementations such as SATAN, connected to each
tcp port using the full three-way-handshake (opening a full tcp connection).
The upside to this method is that the user who is scanning does not need to
custom build the ip packet he is scanning with, because he uses standard
system calls, and does not need root access (generally a uid of 0 is needed
to use SOCK_RAW, /dev/bpf,/dev/nit and so forth) the major down side to this
method is that it is easily detectable and also easily detered, using any
number of methods, most notably the TCP Wrappers made by Wietse Venema.
The next step was of course SYN-scanning or 'half open scanning' which
implies that a full tcp connection is never established. The process of
establishing a tcp connection is three phased: the originating party first
sends a TCP packet with the SYN flag on, then the target party sends a TCP
packet with the flags SYN and ACK on if the port is open, or, if the port
is closed, the target party resets the connection with the RST flag. The
third phase of the negotiation is when the originating party sends a final
TCP packet with the ACK flag on (all these packets, of course, have the
corresponding sequence numbers, ack numbers, etc). The connection is now
open. A SYN-scanner only sends the first packet in the three-way-handshake,
the SYN packet, and waits for the SYN|ACK or a RST. When it receives one of
the two it knows whether or not the port is listening. The major advantage to
this method is that it is not detected by normal logs such as "SATAN
detectors" or Wiestse's tcp_wrappers. The main disadvantages are:
1) This method can still be detected by certian loggers that log SYN
connection attempts ('tcplog' for example), and can still be detected by
netstat(1).
2) The sender, under most operating systems, needs to custom build the
entire IP packet for this kind of scanning (I don't know of any operating
system under which this is not true, if you know of one, please let me know).
This requires access to SOCK_RAW (getprotbyname('raw'); under most systems)
or /dev/bpf (Berkeley packet filter), /dev/nit (Sun 'Network Interface Tap')
etc. This usually requires root or privileged group access.
3) A great deal of firewalls who would filter out this scan, will not
filter out the StealthScan(TM) (all rights reserved to vicious little red
blow ficiouz deliciouz (kosher) chicken surpass INC PLC LTD).
A note about UDP portscanning:
------------------------------
In this article I will ignore UDP portscanning for the simple reason that it
lacks the complexity of tcp; it is not a connection oriented stream protocol
but rather a connectionless datagram protocol. To scan a UDP port to see if
it is listening, simply send any UDP packet to the port. You will receive
an ICMP 'Destination Port Unreachable' packet if the port is not listening.
To the best of my knowledge this is the only way to scan UDP ports. I will
be glad to be corrected -- if anyone knows of a different method please
E-mail me.
The StealthScan:
----------------
This method relies on bad net code in the BSD code. Since most of the
networking code in most any operating system today is BSD netcode or a
derivative thereof it works on most systems. (A most obvious exception to
this is Cisco routers... Gosh! GOOD networking code ?!?@$! <GASP> HERESY!
Alan Cox will have a heart attack when he hears of this!)
Disadvantages of this technique:
1) The IP packet must still be custom built. I see no solution for this
problem, unless some really insecure system calls will be put in. I see
no real need for this because SLIP/PPP services are so common these days,
getting super user access on a machine is not a problem any more.
2) This method relies on bugs in net code. This can and probably will be
fixed in the near future. (Shhhhhh. Don't tell Alan Cox. He hates good
efficient networking code.) OpenBSD, for example, has already fixed this bug.
3) The outcome of a scan is never known, and the outcome is not similar over
different architectures and operating systems. It is not reliable.
Main advantages of this method over the other methods:
1) Very difficult to log. Even once the method is known, devising a logging
method without fixing the actual bug itself is problematic.
2) Can circumvent some firewalls.
3) Will not show up on netstat(1).
4) Does not consist of any part of the standard TCP three-way-handshake.
5) Several different methods consisting of the same principle.
The actual algorithm :
I use TCP packets with the ACK, and FIN flags turned on. I use these simply
because they are packets that should always return RST on an unopened
connection sent to a port. From now on I refer to such packets as 'RST' ,
'FIN', or 'ACK' packets.
method #1:
Send a FIN packet. If the destination host returns a RST then the port is
closed, if there is no return RST then the port is listening. The fact that
this method works on so many hosts is a sad testimonial to the state of the
networking code in most operating system kernels.
method #2
Send an ACK packet. If the returning packets ttl is lower than in the
rest of the RST packets received, or if the window size is greater than
zero, the port is probably listening.
(Note on the ttl: This bug is almost understandable. Every function in IP
is a routing function. With every interface change, the packets ttl is
subtracted by one. In the case of an open port, the ttl was decremented when
it was received and examined, but when it was 'noticed' the flag was not a
SYN, a RST was sent, with a ttl one lower then if the port had simply been
closed. This might not be the case. I have not checked this theory against
the BSD networking code. Feel free to correct me.
Uriel
/*
* scantcp.c
*
* version 1.32
*
* Scans for listening TCP ports by sending packets to them and waiting for
* replies. Relys upon the TCP specs and some TCP implementation bugs found
* when viewing tcpdump logs.
*
* As always, portions recycled (eventually, with some stops) from n00k.c
* (Wow, that little piece of code I wrote long ago still serves as the base
* interface for newer tools)
*
* Technique:
* 1. Active scanning: not supported - why bother.
*
* 2. Half-open scanning:
* a. send SYN
* b. if reply is SYN|ACK send RST, port is listening
* c. if reply is RST, port is not listening
*
* 3. Stealth scanning: (works on nearly all systems tested)
* a. sends FIN
* b. if RST is returned, not listening.
* c. otherwise, port is probably listening.
*
* (This bug in many TCP implementations is not limited to FIN only; in fact
* many other flag combinations will have similar effects. FIN alone was
* selected because always returns a plain RST when not listening, and the
* code here was fit to handle RSTs already so it took me like 2 minutes
* to add this scanning method)
*
* 4. Stealth scanning: (may not work on all systems)
* a. sends ACK
* b. waits for RST
* c. if TTL is low or window is not 0, port is probably listening.
*
* (stealth scanning was created after I watched some tcpdump logs with
* these symptoms. The low-TTL implementation bug is currently believed
* to appear on Linux only, the non-zero window on ACK seems to exists on
* all BSDs.)
*
* CHANGES:
* --------
* 0. (v1.0)
* - First code, worked but was put aside since I didn't have time nor
* need to continue developing it.
* 1. (v1.1)
* - BASE CODE MOSTLY REWRITTEN (the old code wasn't that maintainable)
* - Added code to actually enforce the usecond-delay without usleep()
* (replies might be lost if usleep()ing)
* 2. (v1.2)
* - Added another stealth scanning method (FIN).
* Tested and passed on:
* AIX 3
* AIX 4
* IRIX 5.3
* SunOS 4.1.3
* System V 4.0
* Linux
* FreeBSD
* Solaris
*
* Tested and failed on:
* Cisco router with services on ( IOS 11.0)
*
* 3. (v1.21)
* - Code commented since I intend on abandoning this for a while.
*
* 4. (v1.3)
* - Resending for ports that weren't replied for.
* (took some modifications in the internal structures. this also
* makes it possible to use non-linear port ranges
* (say 1-1024 and 6000))
*
* 5. (v1.31)
* - Flood detection - will slow up the sending rate if not replies are
* recieved for STCP_THRESHOLD consecutive sends. Saves alot of resends
* on easily-flooded networks.
*
* 6. (v1.32)
* - Multiple port ranges support.
* The format is: <start-end>|<num>[,<start-end>|<num>,...]
*
* Examples: 20-26,113
* 20-100,113-150,6000,6660-6669
*
* PLANNED: (when I have time for this)
* ------------------------------------
* (v2.x) - Multiple flag combination selections, smart algorithm to point
* out uncommon replies and cross-check them with another flag
*
*/
unsigned long dest_addr;
unsigned long spoof_addr;
unsigned long usecdelay;
unsigned waitdelay;
int slowfactor = STCP_SLOWFACTOR;
struct portrec /* the port-data structure */
{
unsigned n;
int state;
unsigned char ttl;
unsigned short int window;
unsigned long int seq;
char sends;
} *ports;
char *portstr;
unsigned char scanflags;
int done;
int rawsock; /* socket descriptors */
int tcpsock;
int lastidx = 0; /* last sent index */
int maxports; /* total number of ports */
void timeout(int signum) /* timeout handler */
{ /* this is actually the data */
int someopen = 0; /* analyzer function. werd. */
unsigned lastsent;
int checklowttl = 0;
struct portrec *p;
printf("* SCANNING IS OVERnn");
fflush(stdout);
done = 1;
for (lastsent = 0;lastsent<maxports;lastsent++)
{
p = ports+lastsent;
if (p->state == -1)
if (p->ttl > 64)
{
checklowttl = 1;
break;
}
}
/* the above loop checks whether there's need to report low-ttl packets */
for (lastsent = 0;lastsent<maxports;lastsent++)
{
p = ports+lastsent;
destaddr.sin_port = htons(p->n);
tcpip_send(rawsock,&destaddr,
spoof_addr,destaddr.sin_addr.s_addr,
STCP_PORT,ntohs(destaddr.sin_port),
TH_RST,
p->seq++, 0,
512,
NULL,
0);
} /* just RST -everything- sent */
/* this inclued packets a reply */
/* (even RST) was recieved for */
for (lastsent = 0;lastsent<maxports;lastsent++)
{ /* here is the data analyzer */
p = ports+lastsent;
switch (scanflags)
{
case TH_SYN:
switch(p->state)
{
case -1: break;
case 1 : printf("# port %d is listening.n",p->n);
someopen++;
break;
case 2 : printf("# port %d maybe listening (unknown response).n",
p->n);
someopen++;
break;
default: printf("# port %d needs to be rescanned.n",p->n);
}
break;
case TH_ACK:
switch (p->state)
{
case -1:
if (((p->ttl < 65) && checklowttl) || (p->window >0))
{
printf("# port %d maybe listening",p->n);
if (p->ttl < 65) printf(" (low ttl)");
if (p->window >0) printf(" (big window)");
printf(".n");
someopen++;
}
break;
case 1:
case 2:
printf("# port %d has an unexpected response.n",
p->n);
break;
default:
printf("# port %d needs to be rescanned.n",p->n);
}
break;
case TH_FIN:
switch (p->state)
{
case -1:
break;
case 0 :
printf("# port %d maybe open.n",p->n);
someopen++;
break;
default:
printf("# port %d has an unexpected response.n",p->n);
}
}
}
printf("-----------------------------------------------n");
printf("# total ports open or maybe open: %dnn",someopen);
free(ports);
exit(0); /* heh. */
}
int resolve_one(const char *name, unsigned long *addr, const char *desc)
{
struct sockaddr_in tempaddr;
if (resolve(name, &tempaddr,0) == -1) {
printf("error: can't resolve the %s.n",desc);
return -1;
}
*addr = tempaddr.sin_addr.s_addr;
return 0;
}
void give_info(void)
{
printf("# response address : %s (%s)n",spoof_name,inet_ntoa(spoof_addr));
printf("# target address : %s (%s)n",dest_name,inet_ntoa(dest_addr));
printf("# ports : %sn",portstr);
printf("# (total number of ports) : %dn",maxports);
printf("# delay between sends : %lu microsecondsn",usecdelay);
printf("# delay : %u secondsn",waitdelay);
printf("# flood dectection threshold : %d unanswered sendsn",STCP_THRESHOLD);
printf("# slow factor : %dn",slowfactor);
printf("# max sends per port : %dnn",STCP_SENDS);
}
int parse_args(int argc, char *argv[])
{
if (strrchr(argv[0],'/') != NULL)
argv[0] = strrchr(argv[0],'/') + 1;
if (argc < 7) {
printf("%s: not enough argumentsn",argv[0]);
return -1;
}