Port Scanning with Python

1. Introduction
Port scanning is very important technique to probe a host for open ports. We can define it as trying to connect to the host from a list of ports, with the goal of finding an active ports and identifying running services on a targest to pentest it.

We can connect to any machine attached to the network using TCP/IP Protocol, by providing it's address and port number. Port number varies from 1 to 65536.
As a result of scan on a port, we can get:
[+] Open or Accepted: The host established a connection
[+] Closed, Denied or Not Listening: The specified port is closed

2. Socket Basics
In port scanning there are many techniques, but in this post we will discover the most known one called "TCP Full Connect Scan" which is based on standard socket APIs.
Sockets provides an application-programming interface (API) that can be used in our programs to perform network communications between hosts. we can create socket object, connect to a host, bind our socket to a specified port, listen for connection, send and receive data through TCP/IP sockets.

Python supports sockets as well through it's module called "socket", so as first step to use sockets in our script, we need to import this library
>>> import socket

after that we can use any of the following functions
[+] socket.gethostbyname(hostname): this function returns the IP address of the given domain name
[+] socket.gethostbyaddr(ip): return the domain name of the specified ip address
[+]socket.socket(family, type, proto): this function create an instance of the specified socket family (AF_INET, AF_INET6, AF_UNIX). the type argument can be SOCK_STREAM for TCP socket or SOCK_DGRAM for a UDP socket. the proto is almost set to zero
[+] socket.create((host, port)): this function take a tuple as parameter, this tuple contains the host and the port you want to connect to, and it tries to establish a connection with the target. if it fails, it triggers an exception.
[+] socket.send(data): we use this function to send data to the target after we establish a connection with it. the type of data should be byte.
[+] socket.recv(number of bytes): to receive data from host, we use this function by specifiying the size of data we want to accept.
[+] setdefaulttimeout(timeout): set the default timeout in seconds for the socket.

3. Port Scanning
To build our own port scanner we will use the socket API mentioned above. to be more organized, i splited the script into 4 functions:

[+] h2ip: This function converts a hostname to ip address
[+] connecto: connects to the given (host, port) and returns a socket instance in case of connection established.
[+] bgrabber: This is a useful one, it sends a junk data to the host, and waits to receive banner of the service running on the specified port.
[+] scan: This function is used to call the two recent functions and display informations

3.1. Convert hostname to IP address
This function will take the host submitted by the user and convert it to IP address. we're going to use gethostbyname(), in case the host is not valid, this function triggers an exception.

def h2ip(host):
    try:
        ip=gethostbyname(host)
        return ip # Return IP address of host
    except:
        return None # Return None if the host is not valid

3.2. Establish a connection with the host
First, we create a socket instance of AF_INET address family (host, port) and SOCK_STREAM (TCP Socket) type. Then we try to establish a connection using connect(), if it succeed to connect we return the socket instance for future use, else, it triggers an exception.

def connecto(host, port):
    try:
        s=socket(AF_INET, SOCK_STREAM) # TCP Socket
        s.connect((host, port))
        return s
    except:
        s.close()
        return None


3.3. Banner grabbing
After we connect to the target on the specified port, we try to send a data to the host in order to receive data about the running service. The recv() function kicks an exception when the timeout is reached.

def bgrabber(sock):
    try:
        sock.send("I'm running a port scan on your server for penetration testing\r\n")
        banner=sock.recv(1024)
        return banner
    except:
        return None


3.3. Running the scan
The scan() function is used to call the above functions and display the scan information in a convenient way. It starts with connecto() to connect with the host, if a connection establishd, it calls bgrabber() to grab information on the service listening to the specified port. After done, it closes the socket instance.

def scan(host, port):
    sock=connecto(host, port)
    setdefaulttimeout(5) # set default timeout to 5 sec
    if sock:
        print("[+] Connected to %s:%d"%(host, port))
        banner=bgrabber(sock)
        if banner:
            print("[+] Banner: %s"%banner)
        else:
            print("[!] Can't grab the target banner")
        sock.close() # Done
    else:
        print("[!] Can't connect to %s:%d"%(host, port))


4. The pscan.py script
Putting everything together and adding some option parsing to produce our own port scanner.

#!/usr/bin/env python3
#=========================================================#
# [+] Title: Port Scanning with Python                    #
# [+] Script: pscan.py                                    #
# [+] Blog: pytesting.blogspot.com                        #
#=========================================================#

from optparse import OptionParser
from socket import *

def h2ip(host):
    try:
        ip=gethostbyname(host)
        return ip
    except:
        return None

def connecto(host, port):
    try:
        s=socket(AF_INET, SOCK_STREAM) # TCP Socket
        s.connect((host, port))
        return s
    except:
        s.close()
        return None

def bgrabber(sock):
    try:
        sock.send("I'm running a port scan on your server for penetration testing\r\n")
        banner=sock.recv(1024)
        return banner
    except:
        return None

def scan(host, port):
    sock=connecto(host, port)
    setdefaulttimeout(5) # set default timeout to 5 sec
    if sock:
        print("[+] Connected to %s:%d"%(host, port))
        banner=bgrabber(sock)
        if banner:
            print("[+] Banner: %s"%banner)
        else:
            print("[!] Can't grab the target banner")
        sock.close() # Done
    else:
        print("[!] Can't connect to %s:%d"%(host, port))
       
       

if __name__=="__main__":
    parser=OptionParser()
    parser.add_option("-t", "--target", dest="host", type="string",
                      help="enter host name", metavar="exemple.com")
    parser.add_option("-p", "--port", dest="ports", type="string",
                      help="port you want to scan separated by comma", metavar="PORT")

    (options, args)=parser.parse_args()
   
    if options.host==None or options.ports==None:
        parser.print_help()
    else:
        host=options.host
        ports=(options.ports).split(",")
        try: 
            ports=list(filter(int, ports)) # Store ports into list
            ip=h2ip(host) # Domain name to IP
            if ip:
                print("[+] Running scan on %s"%host)
                print("[+] Target IP: %s"%ip)
                for port in ports:
                    scan(host, int(port))
            else:
                print("[!] Invalid host")
        except:
            print("[!] Invalid port list (e.g: -p 21,22,53,..)")

Comments