# \[P1] Simple Python Port Scanner

This guide will walk you through building a **basic port scanner** — a staple tool in any pentester’s arsenal. You’ll learn how to use Python’s standard modules to interact with networks, scan ports, and make your script more flexible and powerful using command-line arguments.

## Our Goal

A **port scanner** checks which ports on a target machine are open and accepting connections. This helps identify running services (e.g., SSH on port 22, HTTP on port 80). It’s a foundational concept in recon and enumeration phases of ethical hacking (more on this later).

## Set Up Project Folder

Create a directory for your tool:

<pre class="language-bash"><code class="lang-bash">mkdir port_scanner &#x26;&#x26; cd port_scanner # make project directory
touch scanner.py # create a python file

code scanner.py # Open with VSCode if you like it. (Recommended)

<strong># VSCode provides code completion, making typing code much smoother.
</strong></code></pre>

## Import Your First Module

Open up `scanner.py`. First thing: let’s import a module that gives us networking power.

<pre class="language-python"><code class="lang-python"><strong>import socket
</strong></code></pre>

<details>

<summary>Why <code>socket</code>? [EXPAND ME]</summary>

Python’s built-in `socket` module lets you talk to other machines. It can open connections, resolve hostnames, and check which ports are open.

</details>

## Ask the User for a Target

Let’s make the scanner interactive by adding `input()` function. This function asks user to input something from keyboard and the user input gets stored as "*STRING*" by default to the variable it's assigned (in our case we are storing the user input to variable `target`). Here we want user to either input a domain name (eg: `ncateam.xyz`) or just a plain IP address (eg: `192.168.1.1`).

<pre class="language-python"><code class="lang-python">import socket

<strong>target = input("Enter a host to scan: ")
</strong></code></pre>

If someone enters a hostname or domain name instead of an IP address, we can convert that hostname to an IP address using a method of **socket** module that we just imported. The method is `socket.gethostbyname(target)`.

In the example below, we also used a `try-except` block to handle errors properly. It's a good practice to learn how to handle errors early when learning to code, and we'll be covering this as part of the project.

<details>

<summary>Learn Try-Except [EXPAND ME]</summary>

In Python, `try-except` is used to handle errors. You put the code that might cause an error inside the `try` block. If an error happens, Python jumps to the `except` block and runs that code instead of crashing.

```python
try:
    x = int(input("Enter a number: "))
except ValueError:
    print("That's not a valid number!")
```

In this example, if you enter something that's not a number, the `except` block will catch the `ValueError` and show a message.

This was a top-level overview of `try-except`. Don’t worry — the basic concept is simple:

* You write the code that may (or may not) cause an error inside the `try` block.\
  (In the example above, it was a `ValueError`.)
* If the code inside the `try` block fails, Python will execute the code in the `except` block.
* We generally use it to handle errors so the program doesn't crash. Instead, it shows a clean, readable message, which also makes debugging easier.

To understand this clearly, you may refer [here](https://www.w3schools.com/python/python_try_except.asp).

</details>

<pre class="language-python"><code class="lang-python">import socket

target = input("Enter a host to scan: ")

<strong>try:
</strong><strong>    target_ip = socket.gethostbyname(target)
</strong><strong>    print(f"\n[+] Scanning target: {target_ip}\n")
</strong><strong>except socket.gaierror:
</strong><strong>    print("[-] Hostname could not be resolved.")
</strong><strong>    exit()
</strong></code></pre>

In this code, inside the `try` block, we’re trying to convert a hostname (like `google.com`) into an IP address using `socket.gethostbyname(target)`. If this works, the program prints the IP address.

But if the hostname is invalid or can’t be resolved, Python will raise an error called `socket.gaierror`. Instead of crashing the program, the `except` block catches that specific error and prints a friendly message: “Hostname could not be resolved.” Then it exits the program using `exit()`.

If you're curious about more methods (`socker.gethostbyname() is a method (some call it function as well)`) available in the `socket` module, you can check the [official Python socket documentation](https://docs.python.org/3/library/socket.html). It lists all functions and tools you can use with it.

## Let’s Start Scanning Ports

Time to see which ports are open.&#x20;

The code below checks whether ports on a target machine are open or not by attempting to connect to each port in the range of **1 to 1024** (*we used the standard range of well-known ports, but you may change it to scan all available port range as well*).&#x20;

For each port, a new socket is created using `socket.socket(socket.AF_INET, socket.SOCK_STREAM)`. This means we're using **IPv4** and **TCP** to attempt the connection. Next, a **timeout** of **0.5 seconds** is set with `s.settimeout(0.5)`. This ensures the connection attempt won't hang for too long if the port isn't responding.

For each port, the code tries to connect to the target using `s.connect_ex((target_ip, port))`. The `connect_ex()` method returns `0` if the connection is successful, which indicates the port is **open**. If the connection attempt is successful, we can print a message like `[+] Port 80 is open`. After checking each port, the socket is closed using `s.close()` to clean up resources (always do this).

In short, the code checks a range of ports on the target machine and reports whether each one is open or closed based on whether the connection attempt succeeds or fails.

<pre class="language-python"><code class="lang-python">import socket

target = input("Enter a host to scan: ")

try:
    target_ip = socket.gethostbyname(target)
    print(f"\n[+] Scanning target: {target_ip}\n")
except socket.gaierror:
    print("[-] Hostname could not be resolved.")
    exit()

<strong>for port in range(1, 1025):  # standard range of well-known ports
</strong><strong>    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
</strong><strong>    s.settimeout(0.5)  # Set timeout to half a second
</strong><strong>
</strong><strong>    result = s.connect_ex((target_ip, port))
</strong><strong>    if result == 0:
</strong><strong>        print(f"[+] Port {port} is open")
</strong><strong>    s.close()
</strong></code></pre>

We are done! We successfully created a simple port scanner.

## Bonus (<kbd>argparse</kbd>)

Typing into the terminal is nice, but command-line flags are cleaner. Let’s use the `argparse` module.

Add the following to the top:

```python
import argparse
```

This will import argparse module for us. Now we can replace the `input()` with:

```python
parser = argparse.ArgumentParser(description="Simple Python Port Scanner")
parser.add_argument("host", help="Target host to scan (domain or IP)")
args = parser.parse_args()

target = args.host
```

This code uses the `argparse` module to handle command-line arguments. First, `argparse.ArgumentParser()` creates a parser with a description ("Simple Python Port Scanner").&#x20;

Then, `add_argument("host")` defines a required argument for the target host (domain name or IP address) to scan.

The `args = parser.parse_args()` line processes the command-line input, and `target = args.host` stores the provided hostname or IP in the variable `target`. This allows you to run the script and specify the target directly from the command line.

Example:

```bash
python3 scanner.py domain.com
```

## Final Code

<figure><img src="/files/BpbGk9PeDrYxvTkyZ837" alt=""><figcaption><p>Final Written Code</p></figcaption></figure>

## Let's test the port scanner

I am not sure if we covered it in previous parts or not but `\n` is escape sequence used to give new line.&#x20;

Let's quickly test our tool by scanning the domain `scanme.nmap.org`, which is provided by Nmap for learning and testing purposes. It was created by Nmap so learners can use their network scanning tool to safely test port scanning. We’ll use the same domain and run our custom script to perform the scan.

<figure><img src="/files/VdTyrJS9vGg5UfWpaHtk" alt=""><figcaption></figcaption></figure>

As you can see, this scan revealed two open ports: **80** (HTTP) and **22** (SSH). This means that on the server with IP address **45.33.32.156**, an HTTP service is running. Therefore, if we visit the IP address in a browser (e.g., `http://45.33.32.156`), we should be able to access the web service hosted on that server. Additionally, port **22** is open, which is the default port for SSH. While we can attempt to SSH into the server, we don’t know the username or password, nor do we have a private key (such as `id_rsa` or `id_ed25519`). This means we can’t log in. However, we can still perform enumeration on the SSH service, but this topic will be covered later.

## Python is SLOW

As you can see in the screenshot below, it took **5 minutes and 1 second** to iterate over integers from 1 to 1024. This is relatively slow. This is one of the reasons many modern tools are written in low-level programming languages like **C**, **C++**, **Golang**, and **Rust**. These languages are faster and excel in terms of performance and concurrency.&#x20;

At this stage, if you understand what this script does and have worked on a few more projects after this (excluding this one), you might consider learning a low-level programming language. By doing so, you’ll be able to write faster and more efficient tools in the future. Who knows? Thousands of people may end up using your tool someday!

<figure><img src="/files/J9iwVX4I3Wll6CucsgiS" alt=""><figcaption></figcaption></figure>

## Future Expansion

This script has alot of additional expansions we can try some important yet powerful functions or features include:

* **Add Threading to Speed Up Scans**\
  Python can be slow when iterating through ports one by one without threads. Scanning ports from 1 to 1024 serially can take a long time. By using **threading**, you can scan multiple ports at the same time, significantly speeding up the process.
* **Try Banner Grabbing to Identify Services on Open Ports**\
  Once you've identified open ports, you can extend the script to grab banners from those ports. Banners are often returned by services running on open ports (like HTTP, SSH, etc.) and can provide useful information about the software version, helping in vulnerability assessments.
* **Export Results to a File**\
  Instead of just printing the results on the screen, you can export the findings to a file (such as a `json` or `csv`). This allows you to store and analyze results later.
* **Add Color Output**\
  You can make the output more visually appealing and easier to interpret by adding color. Python's `colorama` module allows you to add color to terminal output, helping to highlight open ports or errors in different colors for better readability.

## Your Assignment

Use module [colorama](https://pypi.org/project/colorama/) and make the current script colorful and easy to read.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://handbook.ncateam.xyz/fundamentals/python/p1-simple-python-port-scanner.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
