A simple port scanner using Python


1. Overview

In the active reconnaissance stage of a pentesting, port scanning help us to understand the processes that are open and target them with the relevant attacking vectors.

When it comes to scripting for security-related stuff, Python is the go-to language. We are going to take a look into a simple port scanner written in Python. The idea here is to understand how a port scanner functions, there are tried and tested tools like Nmap, which you should be using if you are looking for a robust port scanner.

It takes less than ten lines of code to come up with a very basic version of a port scanner in Python. Once I show you how to do it, you should go through the Refactoring section to improve the quality of the program.

2. A very basic version of a port scanner

Create a file named port_scanner.py

Import sys package as we want to read some arguments from the command line. Another module that is the core of this program is socket, it helps to connect two nodes on a network, here it’s our own system and the target node.

2.1 Resolve a hostname to an IP address

Say, you ran this script by providing google.com as the argument, the output will show you the IP address of it.

import sys
import socket

ip = socket.gethostbyname(sys.argv[1])
print("The target IP is %s"%ip)

2.2 Scan for ports in a given range and see if it’s open

The range of the ports to be scanned can be defined in the for loop, then we are creating a socket connection and trying to connect to the target host using it’s IP and port.

for port in range(20, 81):
	sock = socket.socket()
	open = sock.connect_ex((ip, port))
	if open == 0:
	    print("The port %d is open"%port) 
	sock.close()

2.3 Execute the program

python3 port_scanner.py <host>

Your port scanner should be up and running.

2.4 The missing pieces

a) A network socket requires a socket family and a socket type. When we created the socket in section 2.2, we haven’t mentioned these details, shouldn’t we?

The socket function sets AF_INET (IPV4) as the address family and the SOCK_STREAM (TCP) as the type by default.

The modified socket creation code will look like this

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

b) The connect_ex() function takes a single argument - address - and connects to that address and returns error indicators, the value will be 0 if the operation succeeded.

I said one argument, but we are passing IP and port to the connect function, if you take a closer look, it’s a tuple. AF_INET address family is expecting a pair (host, port).

3. Refactoring

I will pose some questions and then will answer them with the solutions.

a) What will happen if I don’t pass the host name while invoking the program?

The program will fail. To fix it we should add some validation before resolving the hostname.

if(len(sys.argv) < 2):
    print("Usage: python3 %s <host>"%sys.argv[0])
    sys.exit(1)

We are expecting two arguments in this program, first one is the file name and second is the hostname, the file name can be accessed using the index 0. We are stopping the execution of the program when the number of arguments is less than two.

b) What if the hostname provided doesn’t exist?

The program will exit by throwing socket.gaierror exception.

socket.gaierror: [Errno -3] Temporary failure in name resolution

We should capture the exception and show some user-friendly messages.

except socket.gaierror:
    print("Cannot resolve hostname!")
    sys.exit(1)

c) The scanning is taking too long to execute, when I interrupt the execution using ctrl+c what will happen?

Catch the built-in KeyboardInterrupt exception.

except KeyboardInterrupt:
    print("I'm interrupted, mayday, mayday..")
    sys.exit(1)

d) The execution is very slow, how can I improve the performance?

This is an I/O bound program. We can use threading to improve performance.

You can read this article to add threading to the I/O task, I’m not going to explain about it in this article.

e) How do I know the execution time of the program?

We can make use of the timestamps. Import datatime module and use now() function to get the current timestamp.

from datetime import datetime

print("Time is ",datetime.now())

4. Conclusion

We saw how to leverage in-built Python modules to create a network port scanner from scratch. This program has a lot of shortcomings, for doing real-world active footprinting use tools such as Nmap. Don’t reinvent the wheel!

The improvements done as part of refactoring can be seen here