Building a Port Scanner with Python

PythonPythonBeginner
Practice Now

Introduction

In this project, we will develop a server port scanner using Python to detect open ports on a target server. This tool is crucial for both system administrators, who use it to verify security policies, and potential attackers, who use it to identify operational network services on a host. Our journey will cover the essential aspects of port scanning, including its methodology and implications. We'll delve into creating a simple yet effective port scanner leveraging Python's capabilities, focusing on a multi-threaded approach to enhance efficiency and performance.

Port scanning is the process of sending requests to a range of server ports to determine which ones are open. This step is crucial for both maintaining security and for attackers to find vulnerable services. We'll start by exploring the basics of port scanning and its implications.

Key Concepts:

  • Port scanners help in detecting open ports on a server or host.
  • They are used for security assessments and by attackers to identify vulnerable services.
  • The simplest form of port scanning involves attempting TCP connections to a range of ports.

๐Ÿ‘€ Preview

Here's a sneak peek at the port scanner tool we'll be building:

python port_scanner.py 127.0.0.1 5000-9000

Output:

Opened Port: 8081
Scanning completed.

๐ŸŽฏ Tasks

In this project, you will learn:

  • How to utilize Python's socket programming capabilities to interact with network ports.
  • How to implement a multi-threaded approach in Python to enhance the efficiency of network scanning tasks.
  • How to develop a command-line tool in Python that takes user inputs for flexible port scanning.

๐Ÿ† Achievements

After completing this project, you will be able to:

  • Use Python's socket library to create network connections, test port availability, and handle network exceptions.
  • Understand and apply multi-threading in Python to perform concurrent tasks, significantly improving the performance of network-intensive operations.
  • Build a practical command-line port scanner tool, enhancing your Python scripting skills and understanding of command-line argument parsing.

Establishing TCP Connection Tests

Our first step involves writing a function to test if a TCP port is open on the target IP. This function will attempt to connect to a specified port and determine its status.

Open the file named port_scanner.py in the /home/labex/project directory and add the following Python function:

import argparse
from queue import Queue
from socket import AF_INET, gethostbyname, socket, SOCK_STREAM
import threading

def tcp_test(port: int, target_ip: str) -> None:
    with socket(AF_INET, SOCK_STREAM) as sock:
        sock.settimeout(1)
        result = sock.connect_ex((target_ip, port))
        if result == 0:
            print(f"Opened Port: {port}")

This code defines a function, tcp_test, which tries to establish a TCP connection to a specified port on the target IP address. It sets a timeout to avoid long waits on each port and uses connect_ex to attempt the connection.

If connect_ex returns 0, it indicates that the connection was successful, meaning the port is open, and the function prints a message indicating the open port.

โœจ Check Solution and Practice

Setting Up Concurrent Port Checks

Next, we'll create a worker function that will be used by each thread to process ports from a queue and utilize the tcp_test function to check each port's status.

Add the following code to your port_scanner.py:

def worker(target_ip: str, queue: Queue) -> None:
    while not queue.empty():
        port = queue.get()
        tcp_test(port, target_ip)
        queue.task_done()

The worker function is designed to be run by multiple threads. It continuously retrieves port numbers from a queue and uses the previously defined tcp_test function to check if those ports are open on the target IP.

After testing a port, it marks the task as done in the queue. This function allows for concurrent scanning of ports, significantly speeding up the process.

โœจ Check Solution and Practice

Orchestrating the Scanning Process

The main function orchestrates the scanning process by setting up the queue, initializing worker threads, and managing the scanning operation over the specified port range.

Continue by adding the main function to your port_scanner.py:

def main(host: str, start_port: int, end_port: int) -> None:
    target_ip = gethostbyname(host)
    queue = Queue()
    for port in range(start_port, end_port + 1):
        queue.put(port)
    for _ in range(100):  ## Adjust the number of threads if necessary
        t = threading.Thread(target=worker, args=(target_ip, queue,))
        t.daemon = True
        t.start()
    queue.join()
    print("Scanning completed.")

The main function orchestrates the entire port scanning process.

It first resolves the target hostname to an IP address, then creates a queue for the port numbers to be scanned. It populates the queue with all the ports in the specified range and spawns multiple worker threads (100 in this example) that concurrently process the queue by checking each port. The function waits for all ports to be processed (queue.join()) and then prints a message indicating that the scanning has been completed.

โœจ Check Solution and Practice

Running the Port Scanner

Finally, it's time to run our port scanner. We'll scan a range of ports on the localhost to test our tool.

Add the following lines at the end of your port_scanner.py to make the script executable:

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='TCP Port Scanner')
    parser.add_argument('host', help='Host to scan')
    parser.add_argument('ports', help='Port range to scan, formatted as start-end')
    args = parser.parse_args()

    start_port, end_port = map(int, args.ports.split('-'))
    main(args.host, start_port, end_port)

This code uses command-line argument parsing to the port scanner script, making it more versatile and user-friendly.

By using the argparse library, it defines two required arguments: host and ports.

  • The host argument is for specifying the target host's address or hostname that you wish to scan.
  • The ports argument expects a string that defines the range of ports to scan, formatted as "start-end" (e.g., "5000-8000"), which the script splits into start_port and end_port by using the - delimiter and then converts them into integers. This allows users to easily specify the target and port range when running the script from the command line.

Now, execute the scanner by running the following command in the terminal:

python port_scanner.py 127.0.0.1 5000-9000

Output:

Opened Port: 8081
Scanning completed.

Now that you have identified an open port (8081) using the port scanner, there's a web server running on it, you can explore what's being hosted there.

To do this, first click the add button and select the "Web Service" option.

step1

Then enter the port 8081 and click the "Access" button.

step2

You will find the web server running in your system, listening on port 8081.

step3
โœจ Check Solution and Practice

Summary

In this project, we constructed a basic port scanner with Python, starting from the fundamental tcp_test function to a multi-threaded scanning approach. We dissected the process into manageable steps, culminating in a functional tool capable of identifying open ports on a target server. Through this hands-on experience, we not only deepened our understanding of network security but also honed our skills in Python programming and concurrency management.

Other Python Tutorials you may like