✨ Noticias - 20/12/2024 | Ahora se publicará también contenido en inglés. Ah, ¡felices fiestas a todos! 🎉✨          🌍          ✨ News - 15/12/2024 | Now, English content will also be posted, check it at the English section. Oh, Merry Christmas everyone! 🎉✨

miércoles, 18 de diciembre de 2024

An implementation for UDP & SYN Stealth (TCP) port scan | Scapy - Ubuntu 18.04

Every result obtained from the port scanning scripts will be compared to Nmap's to ensure they work correctly.

Installing Scapy software through the command $ sudo apt install python-scapy is necessary. Once installed, it can be launched by entering $ sudo scapy, although we will not use Scapy that way in this post.

UDP and SYN Stealth (TCP) port scan methods

As it is mentioned in the title of this post, two port scanning scripts will be developed. One is based on the delivery of UDP packages, another on TCP with host discovery (Ping ICMP).

UDP scan (without retransmissions)

This sort of scans are based on the delivery of UDP packages that will provide information about the availability of the target host and the status of the ports to be scanned. Usually, this UDP packages lack of data and will only carry bytes from its header. However, some concrete UDP ports do require the incoming UDP packages to contain certain data.

Possible port statuses depending on the result - UDP Scan

The information required to find out both the target host and ports status resides in the response type and in certain fields from the ICMP header (if any). Here below, a table retreived from Nmap's documentation shows the relationship between the response type and the associated status:

The open status is assigned to those ports that responded to the UDP scan package with another UDP package. A port is said to be closed when the response is an ICMP package type 3 and code 3 (Destination Unreachable), or filtered if the type is 3 and the code is either 1, 2, 9, 10, or 13. Read more about this at https://rlworkman.net/howtos/iptables/spanish/chunkyhtml/a4189.html.

Finally, ports that do not respond to the UDP scan, even after multiple retransmissions (this is not the case), are classified as open | filtered. Many firewalls discard incoming packages without responding, so it can not be determined whether the port is open or filtered.

Let's say a UDP package is being sent to port 53 of a given host which discards it. Then, there will not be any kind of response (neither UDP nor ICMP), so we can not certainly know whether it is open or filtered. 

This can be slightly hard to understand at first instance, so let's look at it this way:

  1. No UDP or ICMP response is received -> the target port has received the package but it has dropped it, and we do not know the reason...
  2. This lack of evidence prevents us from understanding whether it is open or filtered.

One good way to go through this issue would be performing UDP scans based on specific data or flags for those concrete port/s we are targeting. These ports might recognize the incomming packages as valid ones and, unless they filter them, they will respond.

Following is a Python script that reflects what has been mentioned above.

Python code - udp-scan.py

#! /usr/bin/python

from scapy.all import *

dst_ip = raw_input("IP Address:\n")
dst_ports_input = input("Target ports -> for example: '23,53,500' or '53'\n")
dst_ports = dst_ports_input.split(",")

i = 0
while i < len(dst_ports):
    dst_ports[i] = int(dst_ports[i])
    i += 1

dst_timeout = 5

filtered = []
opened = []
closed = []
filtered_or_opened = []

for port in dst_ports:
    print("Scanning port", port, "...")
    udp_scan_resp = IP(dst=dst_ip)/UDP(dport=port)
    response = sr1(udp_scan_resp, timeout=dst_timeout)
    if response is None:
        filtered_or_opened.append(port)
    elif udp_scan_resp.haslayer(UDP):
        opened.append(port)
    elif udp_scan_resp.haslayer(ICMP):
        if int(udp_scan_resp.getlayer(ICMP).type) == 3 and int(udp_scan_resp.getlayer(ICMP).code) == 3:
            closed.append(port)
        elif int(udp_scan_resp.getlayer(ICMP).type) == 3 and int(udp_scan_resp.getlayer(ICMP).code) in [1, 2, 9, 10, 13]:
            filtered.append(port)

print("Scan report:")
print("Opened ports ->", opened)
print("Filtered ports ->", filtered)
print("Closed ports ->", closed)
print("Filtered/opened ports ->", filtered_or_opened)

Usage example - udp-scan.py

To run this script, it is necessary to place it inside the /usr/lib/python2.7/dist-packages/scapy directory, where all the .py files of Scapy are located, allowing us to perform the required imports. To execute it, we run the command python udp-scan.py.

After running udp-scan.py and specifying the target IP and port(s), a brief report is displayed showing the status of each port. In this case, the status of the ports is open/filtered, meaning there was no response, and it cannot be determined (case 2 in the table in Figure 1).

To ensure this works as expected, the same test has been performed using the Nmap tool: nmap -sU -p 53 193.136.28.31.


Once again, this port's status is open/filtered, which justifies the proper functioning of our script. In this case, it also indicates "1 host up," meaning it has detected that the device at that address is available. This is because Nmap performs a 'host discovering' phase before the UDP scan. Our script does not do this. It can be disabled by using the -sP option.

TCP - SYN Stealth (host discovery enabled)

This scan first involves performing host discovery using ICMP. In this way, if we receive an ICMP response, we can deduce that the host is active. After this quick check of the target device's availability, a TCP packet without data is then sent.

Port statuses depending on the response - TCP SYN Stealth

This packet will have the SYN flag set in its header, as its purpose is to prompt the target host to respond to this "connection initiation" with a SYN-ACK. Similarly to what was described in the UDP scan:

  • No response received -> Filtered port.

  • A response is received (proceed to check its header's flags):

    • Flags set to 0x14 -> Closed port.

    • Flags set to 0x12 -> Open  port.

    • If the response is ICMP type=3, code=1,2,3,9,10,13 -> Filtered port.

The previous flags are expressed in hexadecimal. In binary, 0x12 would be 0001 (for the 1) and 0010 (for the 2), so 0x12 = 0001 0010. Each of these two blocks corresponds to a flag; specifically, 0001 is ACK and 0010 is SYN. Therefore, 0x12 indicates ACK-SYN, which is the response that determines that the i-th port is open.

Similarly, 0x14 corresponds to 0001 0100, which are the ACK-RESET flags. This indicates that the port is closed.

Read more about this at: https://sites.google.com/site/customconfusion/summary-sheets/tcp-flags.

Python code - syn-stealth.py

Concluding the previous theoretical study, here is the equivalent code in Python:

#! /usr/bin/python

from scapy.all import *

dst_ip = raw_input("IP Address:\n")

print("Host discovering, please wait...")
package=IP(dst=dst_ip)/ICMP()
icmp_response=sr1(package,timeout=1,verbose=0)

if icmp_response is None:
    print("Host at",dst_ip,"unreachable...\n")
else:
    print("Host at",dst_ip,"is available!\n")

port_input = input("Target ports  -> separated by ','\n")
dst_ports = port_input.split(",")

i=0
while i<len(dst_ports):
    dst_ports[i] = int(dst_ports[i])
    i+=1

filtered = []
opened = []
closed = []

print("Now trying TCP Stealth scan...")
for port in dst_ports:
    print("Scanning port", port, "...")
    p = IP(dst=dst_ip/TCP(sport=RandShort(),dport=port,flags='S'))
    response = sr1(p, timeout=dst_timeout)
    if response is None:
        filtered.append(port)
    elif response.haslayer(TCP):
        opened.append(port)
        if response.haslayer(TCP).flags == 0x14:
            closed.append(port)
        elif response.haslayer(TCP).flags == 0x12:
            opened.append(port)
        elif int(response.haslayer(ICMP).type) == 3 and int(response.haslayer(ICMP).code) in [1,2,3,9,10,13]:
            filtered.append(port)

print("Scan report:")
print("Opened ports ->", opened)
print("Filtered ports ->", filtered)
print("Closed ports ->", closed)

Usage example - syn-stealth.py

This script is executed similarly to the previous one; within the directory /usr/lib/python2.7/dist-packages/scapy by running python syn-stealth.py in the command line.

We launch the scan against the same IP as in udp-scan.py, but this time targeting ports that host TCP protocol processes: HTTP (80), HTTPS (443), etc.

The output shows that ports 80 and 443 are open (ACK-SYN or 0x12), and ports 21 and 22 are filtered (ICMP response described earlier). We perform the same test from Nmap using the command: nmap -sS -p 21-443 193.136.28.31.


Similarly, ports 80 and 443 are open, and ports 21 and 22 (along with many others in the range 21-443) are filtered.

No hay comentarios:

Publicar un comentario