Would you like to build your own portscanner?
One that you can easily take with you via remote shell, using it from inside a linux host without the need of installing anything? Well, then get your engines started, my friends.
What are we doing here?
Short answere: we are writing a python script that let's us scan computer networks. While it would be enough to just show the output in theory, we are going a step further and print a html file that allows us to save the results (if you really plan to hack a remote host and use this program, consider rewriting it and tell it to just print to shell, as writing files onto a remote host is not really stealthy).
What are the requirements?
The program shall
- Perform syn-scans using tcp or udp
- we need to be able to simply specify the port range
- we need a timeout so we are not waiting for an answere that never comes
- We need to be efficient while not overextending our resources
- Thus we are using threads
- Threads should be capped, so we are using a semaphore here
- We want to find out which service listenes on scanned port
- We want to present output in a human friendly way
- Thus writing out everything into a html file
Let's build the program step-by-step, extending it further to our needs. We start with a simple scanning function, only the basics, then moving on from there, since python is not your typical everyday language, I try to explain as much as I can.
Speaking of, the first thing you have to understand is that python code doesn't really look like code you know from other languages, and python can be interpreted. Because this is not a python tutorial, you will have to either figure out the syntax yourself or read some tutorials elsewhere.
A function to check a specific port
The first thing we want to do is check a specific port.
To do so we need to connect to it. Even if the port is rude and doesn't send any output to us, we know it should be open by not getting a timeout! If we get a timeout or something goes wrong, we need to catch an exception, as always in object orientated languages.
So let's get going:
import socket
def returnPortBanner(ip,port):
try:
socket.setdefaulttimeout(2)
s = socket.socket()
s.connect((ip,port))
portbanner = s.recv(1024)
return portbanner
except Exception, e:
print str(e) + " on port " + str(port)
return
def main():
ip = "192.168.0.1"
for x in range (1,30):
banner = returnPortBanner(ip,x)
if banner:
print "[PORT " + str(x) + " ] " + banner
if __name__ == "__main__":
main()
Ok, that is not that much code to begin with. At the top we are importing the socket library, and at the far bottom we are defining an entry point for our program.
At 16 we are writing our main function. Here we just setup the host-ip and counting our port-range, which I held simple in this case. If we found an open port we are reporting this to the user. We do not need to get a banner as long as the port is open since even an empty string as return parameter is more than null. After the if statement, if we found an open port, we happily print this to the shell, adding the grabbed banner, if any.
At 4 we do the actual check. First, we try to open a socket. If this already fails, we might check our privileges on the mashine, but normally there are no errors to this point.
The ip and port have to be passed in double brackets
because python wants them as a tupel (
and this is the way of autocasting them). Then we try to receive the first 1024 bytes the port sends to us (
aka the banner), or nothing if it remains quiet.
Note that at this point we already found an open port! What will much more likely happen is that our socket gets a timeout, leading the flow to the exception block. There we print the catched error (
which is a timeout in 99.9% of all cases).
User input and threading
So, we managed to take the first steps, but this script is far away from beeing an usefull program.
Let's first implement some user input, after all it is the user that wants to specify the port range, not the programmer.
Luckily, we are using python, so somebody at some point had the same problem and decided to write a tool for this, namely Optparse.
import optparse
[ ... ]
def main():
#first get some information from the users command line input
parser = optparse.OptionParser("%prog -H "\
"-T -U ")
parser.add_option("-H", dest = "targetHost", type = "string")
parser.add_option("-T", dest = "tcpPort", type = "string")
parser.add_option("-U", dest = "udpPort", type = "string")
#work work
(options, args) = parser.parse_args()
#parse the target host from the user
targetHosts = str(options.targetHost).split(",")
This example shows us how important an active community is for every language, kudos for whoever wrote this! I already added the flags we need and an output for the user.
I think this part would not need further explanation. The target hosts are split by comma and parsed into an array. Later we will give the user the option to specify host- and port-ranges, but to understand the code this would rather hold us down.
Now let's start with multithreading our scan.
To speed up the scan process and use our resources efficient, we need to split work into threads, like a single bee is not feeding the whole swarm. This part is rather advanced, so you should make sure you really understand multithreading and the use of
semaphores. If you want, use Mutex, Locks or whatever, but you have to do it on your own then.
The semaphore limits the access to network resources so we don't accidently dos ourselfs.
Here is the code:
from threading import *
threadLock = Semaphore(value = 100)
[ ... ]
for port in portRangeTcp:
myThread = Thread(target=connScan,args = (host,port,"tcp"))
myThread.start()
myThread.join()
[ ... ]
def connScan(theHost, thePort, connType):
threadLock.acquire()
try:
if "tcp" in connType:
mySocket = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
elif "udp" in connType:
mySocket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
else:
print "[-] error with connType, returning"
return
#open a connection on the specific port
mySocket.connect((theHost, thePort))
[ ... ]
except:pass
finally:
threadLock.release()
try:
mySocket.close()
except:pass
You should notice the
threadlock variable in the beginning, representing our semaphore. In my case, I limited it to 100 threads, you need to play around with that value and find the optimal value for yourself. The following loop creates threads targeting the
connScan function and is still hardcoded to "tcp". Later we use this to tell the thread whether it should target for an tcp or udp port and call the connect function from here. The join() command makes sure that the program flow waits for threads to terminate so tcp and udp threads aren't blocking each other and the program does not close before everything is finished.
We are dropping all errors but making sure
all threadlocks get released eventually.
Putting it all together
Every good tool gives a nice and clean overview about it's results. Let's try doing this by creating a html page with our scan results. This is not a web-technology tutorial, so I expect you to learn JQuery and html yourself, and if you don't want the output to be so comprehensive, feel free to just take the code snippets from above and create your own command-line portscanner.
Here is a picture of the output that my final version will create (without an actual target):
And here, finally, the code:
# -*- coding: utf-8 -*-
import sys
import os
import optparse
import datetime
import socket
from threading import *
outputArray = []
threadLock = Semaphore(value = 100) #!cc
socket.setdefaulttimeout(1)
def connScan(theHost, thePort, connType):
threadLock.acquire()
try:
if "tcp" in connType:
mySocket = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
elif "udp" in connType:
mySocket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
else:
print "[-] error with connType, returning"
return
#open a connection on the specific port
mySocket.connect((theHost, thePort))
if "tcp" in connType:
outputArray.append("
" + str(thePort) + " open
")
mySocket.send("Knock knock motherfucker")
respond = mySocket.recv(1024)
if respond:
#the best that can happen
if not "udp" in connType:
outputArray.append("
"+ str(respond) + "
")
else:
#only print udp port as open if we get a banner, cause it is too unsafe to predict
outputArray.append("< div class='port'>" + str(thePort) + " open
" + str(respond) + "
")
except:pass
finally:
threadLock.release()
try:
mySocket.close()
except:pass
def main():
#first get some information from the users command line input
parser = optparse.OptionParser("%prog -H "\
"-T -U ")
parser.add_option("-H", dest = "targetHost", type = "string")
parser.add_option("-T", dest = "tcpPort", type = "string")
parser.add_option("-U", dest = "udpPort", type = "string")
#work work
(options, args) = parser.parse_args()
#parse the target host from the user
targetHosts = str(options.targetHost).split(",")
#define some arrays for port numb.3rs
portRangeTcp = []
portRangeUdp = []
#get an array of tcp ports to scan
if str(options.tcpPort) is not None:
if "-" in str(options.tcpPort):
theRange = str(options.tcpPort).split("-")
for x in range(int(theRange[0]),int(theRange[1])):
portRangeTcp.append(x)
else:
portRangeTcp = str(options.tcpPort).split(",")
#same goes for udp if specified
if str(options.udpPort) is not None:
if "-" in str(options.udpPort):
theRange = str(options.udpPort).split("-")
for x in range(int(theRange[0]),int(theRange[1])):
portRangeUdp.append(x)
else:
portRangeUdp = str(options.udpPort).split(",")
#kick user out if he failed input
if(targetHosts[0] == None or (portRangeTcp[0] == None and portRangeUdp[0] == None)):
print "[-] At least specify a target and some ports, exiting..."
exit(0)
#create a html5 page for the output, skip if already present
if not os.path.isfile("results.html"):
outputArray.append("Scan Results"\
""\
"
clockw0rk's portscanner
")
#start worker bees for scan types
for host in targetHosts:
outputArray.append("
"\
"Scan results for " + host + " at " + str(datetime.datetime.now().strftime("%d.%m.%Y @ %H:%M:%S")) + "
")
#start worker bees for tcp scan
if portRangeTcp[0] is not None:
outputArray.append("
TCP Results:
"\
"
PORT
BANNER
")
for port in portRangeTcp:
myThread = Thread(target=connScan,args = (host,port,"tcp"))
myThread.start()
myThread.join()
if portRangeUdp[0] is not None:
outputArray.append("
UDP Results:
"\
"
PORT
BANNER
")
for port in portRangeUdp:
myThread = Thread(target=connScan,args = (host,port,"udp"))
myThread.start()
myThread.join()
#fix contentbox float and close all tags
outputArray.append("
\n")
#if outputfile does not exist, close the html doc
if not os.path.isfile("results.html"):
outputArray.append("")
#
#print the results to a file
#delete last line of html file, since it is our body and html closing tag
if os.path.isfile("results.html"):
file = open("results.html","r+")
file.seek(0,os.SEEK_END)
pos = file.tell()-1
while pos > 0 and file.read(1) != "\n":
pos -= 1
file.seek(pos, os.SEEK_SET)
if pos > 0:
file.seek(pos, os.SEEK_SET)
file.truncate()
file.close()
#append the results to the file
file = open("results.html","a")
for line in outputArray:
file.write(line)
file.write("")
file.close()
if __name__ == "__main__":
main()
Hopefully, blogger will show the code as it is in the future since mixing html into it seemed to give it some sort of headache.
Conclusion
Writing a simple port scanner is not that hard, but of course this program is
lightyears away from nmap (
Fyodor is a real god). If you are interested, feel free to expand and upgrade the code to your needs. Be aware that if you want to perform special scans, like xmas scan, you will have to create own IP-packages, which is not trivial. If you really manage to upgrade this program to that point, pls show me, I would appreciate it.
Also keep in mind that the sole action of scanning a computer network
could be already seen as an act of aggression, and this port scanner is not really stealthy. In fact, most modern firewalls would drop syn packets, which makes tools like nmaps fin- or ack-scan so valuable.
Although this program is rather hobby-level, I hope you could learn something here and you liked it anyway. To further improve the program as it is, you could start building an UI for it, like this one I started but never finished:
That's it, if you have any questions or feedback feel free to leave me a comment!
Everything that has a beginning
must come to an end.
- numb.3rs
Comments
Post a Comment