How to make a Port Scanner with python
Port Scanner with python
Reconnaissance serves as the first step in any good cyber assault. An attacker
must discover where the vulnerabilities are before selecting and choosing
exploits for a target. In the following section, we will build a small reconnaissance
script that scans a target host for open TCP ports. However, in order to
interact with TCP ports, we will need to first construct TCP sockets.
Python, like most modern languages, provides access to the BSD socket interface.
BSD sockets provide an application-programming interface that allows
coders to write applications in order to perform network communications
between hosts. Through a series of socket API functions, we can create, bind,
listen, connect, or send traffic on TCP/IP sockets. At this point, a greater understanding
of TCP/IP and sockets are needed in order to help further develop our
own attacks.
The majority of Internet accessible applications reside on the TCP. For example,
in a target organization, the web server might reside on TCP port 80, the email
server on TCP port 25, and the file transfer server on TCP port 21. To connect to
any of these services in our target organization, an attacker must know both the
Internet Protocol Address and the TCP port associated with the service. While
someone familiar with our target organization would probably have access to
this information, an attacker may not.
An attacker routinely performs a port scan in the opening salvo of any successful
cyber assault. One type of port scan includes sending a TCP SYN packet to a series of common ports and waiting for a TCP ACK response that will result in signaling an open port. In contrast, a TCP Connect Scan uses the full three-way handshake to determine the availability of the service or port.
TCP Full Connect Scan
So let’s begin by writing our own TCP port scanner that utilizes a TCP full connect
scan to identify hosts. To begin, we will import the Python implementation
of BSD socket API. The socket API provides us with some functions that
will be useful in implementing our TCP port scanner. Let’s examine a couple
before proceeding. For a deeper understanding, view the Python Standard
Library Documentation at: http://docs.Python.org/library/socket.html.
socket.gethostbyname(hostname) – This function takes a hostname such as www.syngress.com and returns an IPv4 address format such as 69.163.177.2.
socket.gethostbyaddr(ip address) – This function takes an IPv4 address and returns a triple containing the hostname, alternative list of host names, and a list of IPv4/v6 addresses for the same interface on the host.
socket.socket([family[, type[, proto]]]) – This function creates an instance of a new socket given the family.
Options for the socket family are AF_INET, AF_INET6, or AF_UNIX. Additionally, the socket can be specified as SOCK_STREAM for a TCP socket or SOCK_DGRAM for a UDP socket. Finally, the protocol number is usually zero and is omitted in most cases.
socket.create_connection(address[, timeout[, source_address]]) – This function takes a 2-tuple (host, port) and returns an instance of a network socket. Additionally, it has the option of taking a timeout and source address.
In order to better understand how our TCP Port Scanner works, we will
break our script into five unique steps and write Python code for each of
them. First, we will input a hostname and a comma separated list of ports
to scan. Next, we will translate the hostname into an IPv4 Internet address.
For each port in the list, we will also connect to the target address and specific
port. Finally, to determine the specific service running on the port, we
will send garbage data and read the banner results sent back by the specific
application.
In our first step, we accept the hostname and port from the user. For this, our program utilizes the optparse library for parsing command-line options. The
call to optparse. OptionPaser([usage message]) creates an instance of an option
parser. Next, parser.add_option specifies the individual command line options for our script. The following example shows a quick method for parsing the
target hostname and port to scan.
import optparse
parser = optparse.OptionParser('usage %prog –H'+\
'<target host> -p <target port>')
parser.add_option('-H', dest='tgtHost', type='string', \
help='specify target host')
parser.add_option('-p', dest='tgtPort', type='int', \
help='specify target port')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPort = options.tgtPort
if (tgtHost == None) | (tgtPort == None):
print parser.usage
exit(0)
Next, we will build two functions connScan and portScan. The portScan function
takes the hostname and target ports as arguments. It will first attempt to
resolve an IP address to a friendly hostname using the gethostbyname() function.
Next, it will print the hostname (or IP address) and enumerate through
each individual port attempting to connect using the connScan function. The
connScan function will take two arguments: tgtHost and tgtPort and attempt
to create a connection to the target host and port. If it is successful, connScan
will print an open port message. If unsuccessful, it will print the closed port
message.
import optparse
from socket import *
def connScan(tgtHost, tgtPort):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
print '[+]%d/tcp open'% tgtPort
connSkt.close()
except:
print '[-]%d/tcp closed'% tgtPort
def portScan(tgtHost, tgtPorts):
try: tgtIP = gethostbyname(tgtHost)
except:
print "[-] Cannot resolve '%s': Unknown host"%tgtHost
return
try:
tgtName = gethostbyaddr(tgtIP)
print '\n[+] Scan Results for: ' + tgtName[0]
except:
print '\n[+] Scan Results for: ' + tgtIP
setdefaulttimeout(1)
for tgtPort in tgtPorts:
print 'Scanning port ' + tgtPort
connScan(tgtHost, int(tgtPort))
Application Banner Grabbing
In order to grab the application banner from our target host, we must first
insert additional code into the connScan function. After discovering an open
port, we send a string of data to the port and wait for the response. Gathering
this response might give us an indication of the application running on the
target host and port.
------------------
import optparse
import socket
from socket import *
def connScan(tgtHost, tgtPort):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('ViolentPython\r\n')
results = connSkt.recv(100)
print '[+]%d/tcp open'% tgtPort
print '[+] ' + str(results)
connSkt.close()
except:
print '[-]%d/tcp closed'% tgtPort
def portScan(tgtHost, tgtPorts):
try:
tgtIP = gethostbyname(tgtHost)
except:
print "[-] Cannot resolve '%s': Unknown host" %tgtHost
return
try:
tgtName = gethostbyaddr(tgtIP)
print '\n[+] Scan Results for: ' + tgtName[0]
except:
print '\n[+] Scan Results for: ' + tgtIP
setdefaulttimeout(1)
for tgtPort in tgtPorts:
print 'Scanning port ' + tgtPort
connScan(tgtHost, int(tgtPort))
def main():
parser = optparse.OptionParser("usage%prog "+\
"-H <target host> -p <target port>")
parser.add_option('-H', dest='tgtHost', type='string', \
help='specify target host')
parser.add_option('-p', dest='tgtPort', type='string', \
help='specify target port[s] separated by comma')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(', ')
if (tgtHost == None) | (tgtPorts[0] == None):
print '[-] You must specify a target host and port[s].'
exit(0)
portScan(tgtHost, tgtPorts)
if __name__ == '__main__':
main()
-------------------
For example, scanning a host with a FreeFloat FTP Server installed might reveal
the following information in the banner grab:
attacker$ python portscanner.py -H 192.168.1.37 -p 21, 22, 80
[+] Scan Results for: 192.168.1.37
Scanning port 21
[+] 21/tcp open
[+] 220 FreeFloat Ftp Server (Version 1.00).
In knowing that the server runs FreeFloat FTP (Version 1.00) this will prove to
be useful for targeting our application as seen later.
Threading the Scan
Depending on the timeout variable for a socket, a scan of each socket can take
several seconds. While this appears trivial, it quickly adds up if we are scanning
multiple hosts or ports. Ideally, we would like to scan sockets simultaneously
as opposed to sequentially. Enter Python threading. Threading provides a way
to perform these kinds of executions simultaneously. To utilize this in our
scan, we will modify the iteration loop in our portScan() function. Notice how
we call the connScan function as a thread. Each thread created in the iteration
will now appear to execute at the same time.
----------
for tgtPort in tgtPorts:
t = Thread(target=connScan, args=(tgtHost, int(tgtPort)))
t.start()
----------------
While this provides us with a significant advantage in speed, it does present
one disadvantage. Our function connScan() prints an output to the screen.
If multiple threads print an output at the same time, it could appear garbled
and out of order. In order to allow a function to have complete control of the
screen, we will use a semaphore. A simple semaphore provides us a lock to prevent
other threads from proceeding. Notice that prior to printing an output, we
grabbed a hold of the lock using screenLock.acquire(). If open, the semaphore
will grant us access to proceed and we will print to the screen. If locked, we will
have to wait until the thread holding the semaphore releases the lock. By utilizing
this semaphore, we now ensure only one thread can print to the screen at
any given point in time. In our exception handling code, the keyword finally
executes the following code before terminating the block.
-----------------------
screenLock = Semaphore(value=1)
def connScan(tgtHost, tgtPort):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('ViolentPython\r\n')
results = connSkt.recv(100)
screenLock.acquire()
print '[+]%d/tcp open'% tgtPort
print '[+] ' + str(results)
except:
screenLock.acquire()
print '[-]%d/tcp closed'% tgtPort
finally: screenLock.release()
connSkt.close()
---------------------------------
Placing all other functions into the same script and adding some option parsing,
we produce our final port scanner script.
---------------------------
import optparse
from socket import *
from threading import *
screenLock = Semaphore(value=1)
def connScan(tgtHost, tgtPort):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('ViolentPython\r\n')
results = connSkt.recv(100)
screenLock.acquire()
print '[+]%d/tcp open'% tgtPort
print '[+] ' + str(results)
except:
screenLock.acquire()
print '[-]%d/tcp closed'% tgtPort
finally:
screenLock.release()
connSkt.close()
def portScan(tgtHost, tgtPorts):
try:
tgtIP = gethostbyname(tgtHost)
except:
print "[-] Cannot resolve '%s': Unknown host"%tgtHost
return
try:
tgtName = gethostbyaddr(tgtIP)
print '\n[+] Scan Results for: ' + tgtName[0]
except:
print '\n[+] Scan Results for: ' + tgtIP
setdefaulttimeout(1)
for tgtPort in tgtPorts:
t = Thread(target=connScan, args=(tgtHost, int(tgtPort)))
t.start()
def main():
parser = optparse.OptionParser('usage%prog '+\
'-H <target host> -p <target port>')
parser.add_option('-H', dest='tgtHost', type='string', \
help='specify target host')
parser.add_option('-p', dest='tgtPort', type='string', \
help='specify target port[s] separated by comma')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(', ')
if (tgtHost == None) | (tgtPorts[0] == None):
print parser.usage
exit(0)
portScan(tgtHost, tgtPorts)
if __name__ == "__main__":
main()
----------------------------
Running the script against a target, we see it has an Xitami FTP server running
on TCP port 21 and that TCP port 1720 is closed.
-----------------
attacker:!# python portScan.py -H 10.50.60.125 -p 21, 1720
[+] Scan Results for: 10.50.60.125
[+] 21/tcp open
[+] 220- Welcome to this Xitami FTP server
[-] 1720/tcp closed
-----------------------------
Integrating the Nmap Port Scanner
Our preceding example provides a quick script for performing a TCP connect
scan. This might prove limited as we may require the ability to perform additional
scan types such as ACK, RST, FIN, or SYN-ACK scans provided by the
Nmap toolkit (Vaskovich, 1997). The de facto standard for a port scanning
toolkit, Nmap, delivers a rather extensive amount of functionality. This begs
the question, why not just use Nmap? Enter the true beauty of Python. While
Fyodor Vaskovich wrote Nmap and its associated scripts in the C and LUA programming languages, Nmap is able to be integrated rather nicely into Python.
Nmap produces XML based output. Steve Milner and Brian Bustin wrote a
Python library that parses this XML based output. This provides us with the
ability to utilize the full functionality of Nmap within a Python script. Before
starting, you must install Python-Nmap, available at http://xael.org/norman/
python/python-nmap/. Ensure you take into consideration the developer’s
notes regarding the different versions of Python 3.x and Python 2.x.
===========================================
Comments
Post a Comment