Sitemap
Python in Plain English

New Python content every day. Follow to join our 3.5M+ monthly readers.

Building a Port Scanner in Python

Create a multi-threaded Python port scanner with banner grabbing for network security auditing.

10 min readSep 15, 2024

--

Press enter or click to view image in full size
Photo by Ma He on Unsplash

Port scanning is a technique used to identify open ports and services available on a network. It’s commonly used by network administrators for security auditing and by ethical hackers to discover potential vulnerabilities in a system. Python, with its simplicity and rich set of networking libraries, is a great choice for building a basic port scanner.

Hi,my name is CyCoderX. In this article, we’ll explore how to build a simple port scanner in Python, understand how it works, and examine some common port scanning techniques. By the end, you’ll have a working Python script capable of scanning a target IP address and identifying open ports.

Please consider helping by clapping and following me! 😊

Understanding Ports and Networking Basics

Before diving into the code, let’s quickly cover what ports are and why port scanning is important.

What Are Ports?

In networking, a port is a logical address assigned to a service or application on a device. It helps identify the type of communication happening over the network. Ports range from 0 to 65535 and are categorized into:

  • Well-known Ports (0–1023): Reserved for popular services like HTTP (port 80) and FTP (port 21).
  • Registered Ports (1024–49151): Assigned to specific services by software developers.
  • Dynamic/Private Ports (49152–65535): Typically used for client applications.

Why Port Scanning?

Port scanning helps identify which ports are open or closed on a system. Open ports could indicate running services that may be exploited if not properly secured, so identifying them is crucial for system security.

Introduction to Socket Programming in Python

Python’s socket module is part of its standard library and provides the tools you need to perform low-level network communication. We’ll use this module to create connections and determine whether a port is open.

Basic Socket Functions

Here are a few key functions from the socket module that we’ll use in the port scanner:

  • socket.socket(): This creates a new socket object.
  • socket.connect(): This tries to connect to an IP address on a specified port.
  • socket.settimeout(): This sets a timeout to avoid waiting indefinitely for a response.

Example: Simple Socket Connection

To understand how sockets work, let’s look at a basic example of connecting to a web server:

import socket

def connect_to_server():
server = 'python.org'
port = 80
s = socket.socket()
s.settimeout(1)

try:
s.connect((server, port))
print(f"Connection to {server} on port {port} succeeded!")
except socket.error:
print(f"Connection to {server} on port {port} failed.")
finally:
s.close()

connect_to_server()

Explanation:

Import the socket module: This module provides access to the BSD socket interface, which is used for network communication.

Define the connect_to_server function:

  • Set server and port: The server is set to ‘python.org’ and the port to 80 (HTTP port).
  • Create a socket object: s = socket.socket() creates a new socket using the default parameters (IPv4 and TCP).
  • Set a timeout: s.settimeout(1) sets a timeout of 1 second for blocking socket operations.

Try to connect to the server:

  • Attempt connection: s.connect((server, port)) tries to connect to the specified server and port.
  • Print success message: If the connection is successful, it prints a success message.
  • Handle connection errors: If there is a socket error, it prints a failure message.

Close the socket: s.close() ensures the socket is closed whether the connection attempt was successful or not.

Call the function: connect_to_server() is called to execute the above steps.

This simple script attempts to connect to python.org on port 80 (the HTTP port). If successful, it prints a success message, otherwise it reports a failure.

Press enter or click to view image in full size
Photo by julien Tromeur on Unsplash

Building the Port Scanner

Now that we have an understanding of how sockets work, we can start building our port scanner. The scanner will attempt to connect to each port within a specified range on a target IP address. If a connection is successful, it means the port is open.

Step 1: Setting Up the Scanner Function

We’ll write a function that scans a range of ports on a given IP address. Let’s start with the core functionality:

def port_scan(target, port_range):
print(f"Scanning target: {target}")

for port in range(*port_range):
s = socket.socket()
s.settimeout(0.5)

try:
s.connect((target, port))
print(f"Port {port} is open")
except:
pass # Ignore closed ports
finally:
s.close()

# Example usage:
target_ip = '192.168.1.1' # Replace with your target IP
port_range = (1, 1024) # Scan ports from 1 to 1024
port_scan(target_ip, port_range)

Explanation:

Define the port_scan function:

  • Print target information: Outputs the target being scanned.
  • Iterate over port range: Loops through each port in the specified range.
  • Create a socket object: s = socket.socket() creates a new socket for each port.
  • Set a timeout: s.settimeout(0.5) sets a timeout of 0.5 seconds for each connection attempt.

Try to connect to each port:

  • Attempt connection: s.connect((target, port)) tries to connect to the target on the current port.
  • Print open port: If the connection is successful, it prints that the port is open.
  • Handle connection failures: If the connection fails, it does nothing (ignores closed ports).

Close the socket: s.close() ensures the socket is closed after each connection attempt.

Example usage:

  • Set target IP: target_ip = '192.168.1.1' specifies the target IP address.
  • Set port range: port_range = (1, 1024) specifies the range of ports to scan (from 1 to 1024).
  • Call the function: port_scan(target_ip, port_range) initiates the port scan on the specified target and port range.

This function loops through the specified range of ports and attempts to connect to each one. If a connection is successful, it prints the open port number.

Improving the Port Scanner with Multi-threading

Scanning a large range of ports sequentially can take time, especially if many ports are closed. To speed up the process, we can use multi-threading, which allows us to scan multiple ports simultaneously. Python’s threading module makes it easy to implement this.

Step 2: Adding Multi-threading

Let’s modify the port_scan function to use threads so that multiple ports can be scanned at the same time:

import threading

def threaded_port_scan(target, port_range):
def scan_port(port):
s = socket.socket()
s.settimeout(0.5)
try:
s.connect((target, port))
print(f"Port {port} is open")
except:
pass
finally:
s.close()

threads = []
for port in range(*port_range):
t = threading.Thread(target=scan_port, args=(port,))
threads.append(t)
t.start()

# Wait for all threads to finish
for t in threads:
t.join()

# Example usage:
target_ip = '192.168.1.1' # Replace with your target IP
port_range = (1, 1024) # Scan ports from 1 to 1024
threaded_port_scan(target_ip, port_range)

Code Explanation:

Import the threading module: This module allows for the creation and management of threads in Python.

Define the threaded_port_scan function:

Get CyCoderX’s stories in your inbox

Join Medium for free to get updates from this writer.

Define the scan_port function:

  • Create a socket object: s = socket.socket() creates a new socket.
  • Set a timeout: s.settimeout(0.5) sets a timeout of 0.5 seconds for each connection attempt.
  • Attempt connection: s.connect((target, port)) tries to connect to the target on the current port.
  • Print open port: If the connection is successful, it prints that the port is open.
  • Handle connection failures: If the connection fails, it does nothing (ignores closed ports).
  • Close the socket: s.close() ensures the socket is closed after each connection attempt.

Create a list for threads: threads = [] initializes an empty list to store thread objects.

Iterate over port range: Loops through each port in the specified range.

Create and start threads:

  • Create a thread: t = threading.Thread(target=scan_port, args=(port,)) creates a new thread to run the scan_port function for each port.
  • Add thread to list: threads.append(t) adds the thread to the list.
  • Start the thread: t.start() starts the thread, initiating the port scan for that port.

Wait for all threads to finish:

  • Join threads: for t in threads: t.join() waits for each thread to complete before moving on.

Example usage:

  • Set target IP: target_ip = '192.168.1.1' specifies the target IP address.
  • Set port range: port_range = (1, 1024) specifies the range of ports to scan (from 1 to 1024).
  • Call the function: threaded_port_scan(target_ip, port_range) initiates the threaded port scan on the specified target and port range.

How It Works:

  • We create a scan_port() function that handles the scanning of individual ports.
  • We create a new thread for each port in the range and start it.
  • After starting all threads, we call join() to ensure that the main program waits for all threads to complete before exiting.

This method significantly speeds up the scanning process since multiple ports are being checked simultaneously.

Adding a Banner Grabbing Feature

To enhance the port scanner, we can implement banner grabbing. This technique involves sending a request to an open port and capturing the response, which often reveals information about the service running on that port (e.g., an HTTP server may return details about its software version).

Step 3: Implementing Banner Grabbing

Let’s add a simple banner grabbing function to our port scanner:

def grab_banner(s):
try:
banner = s.recv(1024).decode().strip()
return banner
except:
return None

def banner_grabbing_port_scan(target, port_range):
def scan_port(port):
s = socket.socket()
s.settimeout(0.5)
try:
s.connect((target, port))
banner = grab_banner(s)
if banner:
print(f"Port {port} is open: {banner}")
else:
print(f"Port {port} is open")
except (socket.timeout, ConnectionRefusedError):
pass
finally:
s.close()

threads = []
for port in range(*port_range):
t = threading.Thread(target=scan_port, args=(port,))
threads.append(t)
t.start()

for t in threads:
t.join()

# Example usage:
target_ip = '192.168.1.1' # Replace with your target IP
port_range = (1, 1024) # Scan ports from 1 to 1024
banner_grabbing_port_scan(target_ip, port_range)

In this example:

  • We define the grab_banner() function, which attempts to receive and decode a response from the open port.
  • If a response (banner) is received, it’s printed along with the port number.

Full Code and Summary:

Python Port Scanner with Multi-threading and Banner Grabbing

import socket
import threading

def grab_banner(s):
try:
banner = s.recv(1024).decode().strip()
return banner
except:
return None

def scan_port(target, port):
s = socket.socket()
s.settimeout(0.5)
try:
s.connect((target, port))
banner = grab_banner(s)
if banner:
print(f"Port {port} is open: {banner}")
else:
print(f"Port {port} is open")
except (socket.timeout, ConnectionRefusedError):
pass
finally:
s.close()

def banner_grabbing_port_scan(target, port_range):
threads = []
for port in range(*port_range):
t = threading.Thread(target=scan_port, args=(target, port))
threads.append(t)
t.start()

for t in threads:
t.join()

# Example usage:
target_ip = '192.168.1.1' # Replace with your target IP
port_range = (1, 1024) # Scan ports from 1 to 1024
banner_grabbing_port_scan(target_ip, port_range)

Summary of What the Code Does:

  • Builds a multi-threaded port scanner in Python using the socket module.
  • Scans a specified range of ports on a target IP address.
  • Uses multi-threading to scan ports simultaneously, speeding up the process.
  • Checks each port to see if it is open or closed.
  • Adds a banner grabbing feature to capture service information from open ports.
  • Handles timeouts and connection errors gracefully, avoiding crashes.

Conclusion

In this article, we’ve walked through building a simple yet powerful port scanner in Python using the socket module. We explored the basics of networking, how to scan ports, and ways to improve our scanner with multi-threading and error handling. Additionally, we implemented a banner grabbing feature to collect more information about the services running on open ports.

Here’s a quick recap of the key points:

  • Understanding ports: Ports are crucial for network communication, and open ports can expose vulnerabilities.
  • Socket programming: We used Python’s socket module to create connections and check port statuses.
  • Multi-threading: By implementing multi-threading, we made our port scanner faster and more efficient.
  • Error handling: Properly managing errors and timeouts ensured our scanner was robust.
  • Banner grabbing: We added a feature to capture service information from open ports.

Port scanning is an essential skill for anyone interested in network security, and Python provides all the tools you need to implement it efficiently. As you gain more experience, you can further enhance your port scanner by adding features like logging, scanning specific protocols (e.g., TCP/UDP), or even integrating it with larger security auditing tools.

Further Exploration

Now that you’ve built a basic port scanner, here are some ways you can extend this project:

  • Advanced scanning techniques: Implement more complex scanning techniques like SYN scanning or stealth scans.
  • UDP port scanning: Add functionality to scan UDP ports, which behave differently from TCP ports.
  • Service fingerprinting: Integrate libraries like scapy or nmap to perform more advanced service detection and OS fingerprinting.

By continuing to experiment and enhance your port scanner, you’ll deepen your understanding of network security and Python’s networking capabilities.

Press enter or click to view image in full size
Photo by Call Me Fred on Unsplash

Final Words:

Thank you for taking the time to read my article.

This article was first published on Medium by CyCoderX.

Hey There! I’m CyCoderX, a data engineer who loves crafting end-to-end solutions. I write articles about Python, SQL, AI, Data Engineering, lifestyle and more!

If you want to explore similar articles and updates, feel free to explore my Medium profile:

Join me as we explore the exciting world of tech, data and beyond!

What did you think about this article? Let me know in the comments below … or above, depending on your device! 🙃

Please consider supporting me by:

  1. Clapping 50 times for this story
  2. Leaving a comment telling me your thoughts
  3. Highlighting your favorite part of the story

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

--

--

CyCoderX
CyCoderX

Written by CyCoderX

Machine Learning & Data Engineer | Data Science | Python & SQL | AI | Software Developer | Azure & AWS Cloud Specialist | Blogger simplifying Big Data & Trends

Python in Plain English
Python in Plain English

Published in Python in Plain English

New Python content every day. Follow to join our 3.5M+ monthly readers.