Authors: TJ O'Connor
In this chapter, we briefly examined the standard library and a few built-in modules in Python by writing a simple vulnerability scanner. Next, we moved on and wrote our first two Python programs—a twenty-year-old UNIX password cracker and a zip-file brute-force password cracker. You now have the initial skills to write your own scripts. Hopefully, the following chapters will prove as exciting to read as they were to write. We will begin this journey by examining how to use Python to attack systems during a penetration test.
1. Floyd, J. (2007). Federal grand jury indicts fireman for production and possession of child pornography. John T. Floyd Law Firm Web site. Retrieved from <
http://www.houston-federal-criminal-lawyer.com/news/april07/03a.htm
>, April 3.
2. McCullagh, D. (2008). Child porn defendant locked up after ZIP file encryption broken.
CNET News
. Retrieved April 7, 2012, from <
http://news.cnet.com/8301-13578_3-9851844-38.html
>, January 16.
3. Stoll C.
The cuckoo’s egg: Tracking a spy through the maze of computer espionage
. New York: Doubleday; 1989.
4. Stoll C. Stalking the Wily Hacker.
Communications of the ACM
. 1988;31(5):484–500.
5. Zatko, P. (2012). Cyber fast track. ShmooCon 2012. Retrieved June 13, 2012. from <
www.shmoocon.org/2012/videos/Mudge-CyberFastTrack.m4v
>, January 27.
Building a Port Scanner
Constructing an SSH Botnet
Mass Compromise with FTP
Replicate Conficker
Your Own Zero Day Attack
To be a warrior is not a simple matter of wishing to be one. It is rather an endless struggle that will go on to the very last moment of our lives. Nobody is born a warrior, in exactly the same way that nobody is born an average man. We make ourselves into one or the other
—Kokoro by Natsume Sōsek, 1914, Japan.
Twenty-two years before the StuxNet worm crippled the Iranian nuclear power plants in Bushehr and Natantz (
Albright, Brannan, & Walrond, 2010
), a graduate student at Cornell launched the first digital munitions. Robert Tappen Morris Jr., son of the head of the NSA’s National Computer Security Center, infected six thousand workstations with a worm aptly dubbed, the Morris Worm (
Elmer-Dewitt, McCarroll, & Voorst, 1988
). While 6000 workstations seem trivial by today’s standards, this figure represents ten percent of all computers that were connected to the Internet in 1988. Rough estimates by the US Government Accountability Office put the cost somewhere between $10 and $100 million dollars to eradicate the damage left by Morris’s worm (
GAO, 1989
). So how did it work?
Morris’s worm used a three-pronged attack in order to compromise systems. It first took advantage of vulnerability in the Unix sendmail program. Second, it exploited a separate vulnerability in the finger daemon used by Unix systems. Finally, it attempted to connect to targets using the remote shell (RSH) protocol using a list of common usernames and passwords. If any of the three attack vectors succeeded, the worm would use a small program as a grappling hook to pull over the rest of the virus (
Eichin & Rochlis, 1989
).
Would a similar attack still work today and can we learn to write something that would be almost identical? These questions provide the basis for the rest of the chapter. Morris wrote the majority of his attack in the C programming language. However, while C is a very powerful language, it is also very challenging to learn. In sharp contrast to this, the Python programming language has a user-friendly syntax and a wealth of third party modules. This provides a much better platform of support and makes it considerably easier for most programmers to initiate attacks. In the following pages, we will use Python to recreate parts of the Morris Worm as well as some contemporary attack vectors.
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.
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’+\
‘
)
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))
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
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.
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
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