Port scanning is a way of figuring out which ports are listening and accepting connections. Because most services run on standard, documented ports, this information can be used to determine which services are running. The simplest form of port scanning involves trying to open TCP connections to every possible port on the target system. While this is effective, it's also noisy and detectable. Also, when connections are established, services will normally log the IP address. To avoid this, several clever techniques have been invented to avoid detection.
A SYN scan is also sometimes called a half-open scan. This is because it doesn't actually open a full TCP connection. Recall the TCP/IP handshake: When a full connection is made, first a SYN packet is sent, then a SYN/ACK packet is sent back, and finally an ACK packet is returned to complete the handshake and open the connection. A SYN scan doesn't complete the handshake, so a full connection is never opened. Instead, only the initial SYN packet is sent, and the response is examined. If a SYN/ACK packet is received in response, that port must be accepting connections. This is recorded, and a RST packet is sent to tear down the connection to prevent the service from accidentally being DoSed.
In response to SYN scanning, new tools to detect and log half-open connections were created. So, yet another collection of techniques for stealth port scanning evolved: FIN, X-mas, and Null scans. These all involve sending a nonsensical packet to every port on the target system. If a port is listening, these packets just get ignored. However, if the port is closed and the implementation follows protocol (RFC 793), a RST packet will be sent. This difference can be used to detect which ports are accepting connections, without actually opening any connections.
The FIN scan sends a FIN packet, the X-mas scan sends a packet with FIN, URG, and PUSH turned on (named because the flags are lit up like a Christmas tree), and the Null scan sends a packet with no TCP flags set. While these types of scans are stealthier, they can also be unreliable. For instance, Microsoft's implementation of TCP doesn't send RST packets like it should, making this form of scanning ineffective.
Another way to avoid detection is to hide among several decoys. This technique simply spoofs connections from various decoy IP addresses in between each real port-scanning connection. The responses from the spoofed connections aren't needed, because they are simply misleads. However the spoofed decoy addresses must use real IP addresses of live hosts; otherwise the target may be accidentally be SYN flooded.
Idle scanning is a way to scan a target using spoofed packets from an idle host, by observing changes in the idle host. The attacker needs to find a usable idle host that is not sending or receiving any other network traffic and has a TCP implementation that produces predictable IP IDs that change by a known increment with each packet. IP IDs are meant to be unique per packet per session, and they are commonly incremented by 1 or 254 (depending on byte ordering) on Windows 95 and 2000, respectively. Predictable IP IDs have never really been considered a security risk, and idle scanning takes advantage of this misconception.
First the attacker gets the current IP ID of the idle host by contacting it with a SYN packet or an unsolicited SYN/ACK packet, and observing the IP ID of the response. By repeating this process a couple more times, the increment that the IP ID changes with each packet can be determined.
Then the attacker sends a spoofed SYN packet with the idle host's IP address to a port on the target machine. One of two things will happen, depending on whether that port on the victim machine is listening:
If that port is listening, a SYN/ACK packet will be sent back to the idle host. But because the idle host didn't actually send out the initial SYN packet, this response appears to be unsolicited to the idle host, and it responds by sending back a RST packet.
If that port isn't listening, the target machine will send a RST packet back to the idle host, which requires no response.
At this point, the attacker contacts the idle host again to determine how much the IP ID has incremented. If it has only incremented by one interval, no other packets were sent out by the idle host between the two checks. This implies that the port on the target machine is closed. If the IP ID has incremented by two intervals, one packet, presumably a RST packet, was sent out by the idle machine between the checks. This implies that the port on the target machine is open.
The steps are illustrated here for both possible outcomes:
Of course, if the idle host isn't truly idle, the results will be skewed. If there is light traffic on the idle host, multiple packets can be sent for each port. If 20 packets are sent, then a change of 20 incremental steps should be seen for an open port, and none for a closed port. Even if there is light traffic, such as one or two non–scan-related packets on the idle host, this difference is large enough that it can still be detected.
If this technique is used properly on an idle host that doesn't have any logging capabilities, the attacker can scan any target without ever revealing her IP address.
Port scans are often used to profile systems before they are attacked. Knowing what ports are open allows an attacker to determine which services can be attacked. Many IDSs offer methods to detect port scans, but by then the information has already been leaked. While writing this chapter, I wondered if it were possible to prevent port scans before they actually happened. Hacking really is all about coming up with new ideas, so a simple, newly developed method for proactive port-scanning defense will be presented here.
First of all, the FIN, Null, and X-mas scans can be prevented by a simple kernel modification. If the kernel never sends reset packets, these scans will turn up nothing. The following output uses grep to find the kernel code responsible for sending reset packets.
# grep -n -A 12 "void.*send_reset" /usr/src/linux/net/ipv4/tcp_ipv4.c 1161:static void tcp_v4_send_reset(struct sk_buff *skb) 1162-{ 1163- struct tcphdr *th = skb->h.th; 1164- struct tcphdr rth; 1165- struct ip_reply_arg arg; 1166- 1167- return; // Modification: Never send RST, always return. 1168- 1169- /* Never send a reset in response to a reset. */ 1170- if (th->rst) 1171- return; 1172- 1173- if (((struct rtable*)skb->dst)->rt_type != RTN_LOCAL)
By adding the return command (shown above in bold), the tcp_v4_send_reset() kernel function will simply return instead of doing anything. After the kernel is recompiled, the result is a kernel that doesn't send out reset packets, avoiding information leakage.
FIN scan before the kernel modification: # nmap -vvv -sF 192.168.0.189 Starting nmap V. 3.00 ( www.insecure.org/nmap/ ) Host (192.168.0.189) appears to be up ... good. Initiating FIN Scan against (192.168.0.189) The FIN Scan took 17 seconds to scan 1601 ports. Adding open port 22/tcp Interesting ports on (192.168.0.189): (The 1600 ports scanned but not shown below are in state: closed) Port State Service 22/tcp open ssh Nmap run completed -- 1 IP address (1 host up) scanned in 17 seconds # FIN scan after the kernel modification: # nmap -sF 192.168.0.189 Starting nmap V. 3.00 ( www.insecure.org/nmap/ ) All 1601 scanned ports on (192.168.0.189) are: filtered Nmap run completed -- 1 IP address (1 host up) scanned in 100 seconds #
This works fine for scans that rely on RST packets, but preventing information leakage with SYN scans and full-connect scans is a bit more difficult. In order to maintain functionality, open ports have to respond with SYN/ACK packets, but if all of the closed ports also responded with SYN/ACK packets, the amount of useful information an attacker could retrieve from port scans would be minimized. Simply opening every port would cause a major performance hit, though, which isn't desirable. Ideally, this should all be done without using the TCP stack. That sounds like a job for a nemesis script:
File: shroud.sh #!/bin/sh HOST="192.168.0.189" /usr/sbin/tcpdump -e -S -n -p -l "(tcp[13] == 2) and (dst host $HOST) and !(dst port 22)" | /bin/awk '{ # Output numbers as unsigned CONVFMT="%u"; # Seed the randomizer srand(); # Parse the tcpdump input for packet information dst_mac = $2; src_mac = $3; split($6, dst, "."); split($8, src, "."); src_ip = src[1]"."src[2]"."src[3]"."src[4]; dst_ip = dst[1]"."dst[2]"."dst[3]"."dst[4]; src_port = substr(src[5], 1, length(src[5])-1); dst_port = dst[5]; # Increment the received seq number for the new ack number ack_num = substr($10,1,index($10,":")-1)+1; # Generate a random seq number seq_num = rand() * 4294967296; # Feed all this information to nemesis exec_string = "nemesis tcp -v -fS -fA -S "src_ip" -x "src_port" -H "src_mac" -D "dst_ip" -y "dst_port" -M "dst_mac" -s "seq_num" -a "ack_num; # Display some helpful debugging info.. input vs. output print "[in] "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "$10; print "[out] "exec_string; # Inject the packet with nemesis system(exec_string); }'
When running this script, make sure that the HOST variable is set to the current IP address of your host.
The 13th octet is used for a tcpdump filter again, this time only accepting packets that are destined for the given host IP on any port, except for 22, and that only have the SYN flag on. This will pick up SYN scan attempts, full-connect scan attempts, and any other type of connection attempt. Then the packet information is parsed through awk, and fed into nemesis to craft a realistic-looking SYN/ACK response packet. Port 22 must be avoided, because ssh is already responding on that port. All of this is done without using the TCP stack.
With the shroud script running, a telnet attempt will appear to connect even though the host machine isn't even listening to the traffic, as shown here:
From overdose @ 192.168.0.193: overdose$ telnet 192.168.0.189 12345 Trying 192.168.0.189... Connected to 192.168.0.189. Escape character is '^]'. ^] telnet> q Connection closed. overdose$ The shroud.sh script running on 192.168.0.189: # ./shroud.sh tcpdump: listening on eth1 [in] 14:07:09.793997 0:0:ad:d1:c7:ed 0:2:2d:4:93:e4 0800 74: 192.168.0.193.32837 > 192.168.0.189.12345: S 2071082535:2071082535(0) [out] nemesis tcp -v -fS -fA -S 192.168.0.189 -x 12345 -H 0:2:2d:4:93:e4 -D 192.168.0.193 -y 32837 -M 0:0:ad:d1:c7:ed -s 979061690 -a 2071082536 TCP Packet Injection -=- The NEMESIS Project Version 1.4beta3 (Build 22) [MAC] 00:02:2D:04:93:E4 > 00:00:AD:D1:C7:ED [Ethernet type] IP (0x0800) [IP] 192.168.0.189 > 192.168.0.193 [IP ID] 2678 [IP Proto] TCP (6) [IP TTL] 255 [IP TOS] 00 [IP Frag offset] 0000 [IP Frag flags] [TCP Ports] 12345 > 32837 [TCP Flags] SYN ACK [TCP Urgent Pointer] 0 [TCP Window Size] 4096 [TCP Ack number] 2071082536 [TCP Seq number] 979061690 Wrote 54 byte TCP packet through linktype DLT_EN10MB. TCP Packet Injected
Now that the script appears to be working properly, any port-scanning methods involving SYN packets should be fooled into thinking that every possible port is open.
overdose# nmap -sS 192.168.0.189 Starting nmap V. 3.00 ( www.insecure.org/nmap/ ) Interesting ports on (192.168.0.189): Port State Service 1/tcp open tcpmux 2/tcp open compressnet 3/tcp open compressnet 4/tcp open unknown 5/tcp open rje 6/tcp open unknown 7/tcp open echo 8/tcp open unknown 9/tcp open discard 10/tcp open unknown 11/tcp open systat 12/tcp open unknown 13/tcp open daytime 14/tcp open unknown 15/tcp open netstat 16/tcp open unknown 17/tcp open qotd 18/tcp open msp 19/tcp open chargen 20/tcp open ftp-data 21/tcp open ftp 22/tcp open ssh 23/tcp open telnet 24/tcp open priv-mail 25/tcp open smtp [ output trimmed ] 32780/tcp open sometimes-rpc23 32786/tcp open sometimes-rpc25 32787/tcp open sometimes-rpc27 43188/tcp open reachout 44442/tcp open coldfusion-auth 44443/tcp open coldfusion-auth 47557/tcp open dbbrowse 49400/tcp open compaqdiag 54320/tcp open bo2k 61439/tcp open netprowler-manager 61440/tcp open netprowler-manager2 61441/tcp open netprowler-sensor 65301/tcp open pcanywhere Nmap run completed -- 1 IP address (1 host up) scanned in 37 seconds overdose#
The only service that is actually running is ssh on port 22, but it is hidden in a sea of false positives. A dedicated attacker could simply telnet to every port to check the banners, but this technique could easily be expanded to spoof banners also. In fact, let's do that right now.
The client machine will respond to the spoofed SYN/ACK with a single ACK packet. This packet will always increment the sequence number by exactly one, so the proper response packet containing the banner can actually be predicted, generated, and sent to the client machine before that machine can even generate the ACK response. The banner response packet will have the ACK and PSH flags turned on, to match normal banner packets. Interestingly, both packets can be generated and sent out without even caring about the ACK response from the client. This means the script doesn't have to keep track of connection states, and instead the client's TCP stack will sort out the packets.
The modified shroud script looks like this:
File: shroud2.sh #!/bin/sh HOST="192.168.0.189" /usr/sbin/tcpdump -e -S -n -p -l "(tcp[13] == 2) and (dst host $HOST)" | /bin/awk '{ # Output numbers as unsigned CONVFMT="%u"; # Seed the randomizer srand(); # Parse the tcpdump input for packet information dst_mac = $2; src_mac = $3; split($6, dst, "."); split($8, src, "."); src_ip = src[1]"."src[2]"."src[3]"."src[4]; dst_ip = dst[1]"."dst[2]"."dst[3]"."dst[4]; src_port = substr(src[5], 1, length(src[5])-1); dst_port = dst[5]; # Increment the received seq number for the new ack number ack_num = substr($10,1,index($10,":")-1)+1; # Generate a random seq number seq_num = rand() * 4294967296; # Precalculate the sequence number for the next packet seq_num2 = seq_num + 1; # Feed all this information to nemesis exec_string = "nemesis tcp -fS -fA -S "src_ip" -x "src_port" -H "src_mac" -D "dst_ip" -y "dst_port" -M "dst_mac" -s "seq_num" -a "ack_num; # Display some helpful debugging info.. input vs. output print "[in] "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "$10; print "[out] "exec_string; # Inject the packet with nemesis system(exec_string); # Do it again to craft the second packet, this time ACK/PSH with a banner exec_string = "nemesis tcp -v -fP -fA -S "src_ip" -x "src_port" -H "src_mac" -D "dst_ip" -y "dst_port" -M "dst_mac" -s "seq_num2" -a "ack_num" -P banner"; # Display some helpful debugging info.. print "[out2] "exec_string; # Inject the second packet with nemesis system(exec_string); }'
The payload of the banner packet will be pulled from a file called banner. Just to make things extra confusing for the attacker, this can be made to look exactly like the valid ssh banner. The following output looks at a normal ssh banner and puts a similar-looking banner in the banner data file. Again, when running this script, remember to set the HOST variable to your current host's IP.
On 192.168.0.189: tetsuo# telnet 127.0.0.1 22 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. SSH-1.99-OpenSSH_3.5p1 ^] telnet> quit Connection closed. tetsuo# printf "SSH-1.99-OpenSSH_3.5p1\n\r" > banner tetsuo# ./shroud2.sh tcpdump: listening on eth1 [in] 14:41:12.931803 0:0:ad:d1:c7:ed 0:2:2d:4:93:e4 0800 74: 192.168.0.193.32843 > 192.168.0.189.12345: S 4226290404:4226290404(0) [out] nemesis tcp -fS -fA -S 192.168.0.189 -x 12345 -H 0:2:2d:4:93:e4 -D 192.168.0.193 -y 32843 -M 0:0:ad:d1:c7:ed -s 1943811492 -a 4226290405 TCP Packet Injected [out2] nemesis tcp -v -fP -fA -S 192.168.0.189 -x 12345 -H 0:2:2d:4:93:e4 -D 192.168.0.193 -y 32843 -M 0:0:ad:d1:c7:ed -s 1943811493 -a 4226290405 -P banner TCP Packet Injection -=- The NEMESIS Project Version 1.4beta3 (Build 22) [MAC] 00:02:2D:04:93:E4 > 00:00:AD:D1:C7:ED [Ethernet type] IP (0x0800) [IP] 192.168.0.189 > 192.168.0.193 [IP ID] 23711 [IP Proto] TCP (6) [IP TTL] 255 [IP TOS] 00 [IP Frag offset] 0000 [IP Frag flags] [TCP Ports] 12345 > 32843 [TCP Flags] ACK PSH [TCP Urgent Pointer] 0 [TCP Window Size] 4096 [TCP Ack number] 4226290405 Wrote 78 byte TCP packet through linktype DLT_EN10MB. TCP Packet Injected
From another machine (overdose), it appears that a valid connection to a ssh server has occurred.
From overdose @ 192.168.0.193: overdose$ telnet 192.168.0.189 12345 Trying 192.168.0.189... Connected to 192.168.0.189. Escape character is '^]'. SSH-1.99-OpenSSH_3.5p1
Further variations could be created to randomly choose from a library of various banners or to send out a sequence of menacing ANSI sequences. Imagination is a wonderful thing.
Of course, there are also ways to get around a technique like this. I can think of at least one way right now. Can you?