Skip to content

Instantly share code, notes, and snippets.

@Caesurus
Last active January 6, 2024 18:49
Show Gist options
  • Save Caesurus/882a568023a0627dcf9bac6cd863eed0 to your computer and use it in GitHub Desktop.
Save Caesurus/882a568023a0627dcf9bac6cd863eed0 to your computer and use it in GitHub Desktop.
Unlocking "Partial" TTY Interactivity in Basic Reverse Shell Environments

Revconsole

We all know that feeling, your exploit finally lands and you pop a /bin/sh shell. You're IN, but now you have to deal with interacting with a dumb shell. Your muscle memory kicks in, and you press the up arrow to rerun a command, and are faced with a ^[[A on the screen. Frustrating, right? It's like stepping back in time to an era before the comforts of modern shells. No command history, no stderr visibility - just you and a bare-bones command line that doesn't understand your shortcuts or needs. It's enough to make you miss the slick, feature-rich terminals you're used to.

Now for a slightly better approach. We can use our trusty pwntools to add some additional functionality:

python3 -c "from pwn import*;p=remote('192.168.1.123',31337);p.interactive()"

We've upgraded and have command history, and we can edit commands before we send them, making the remote interaction less of a headache. It's not perfect, but it's an improvment. If we send a command that results in output to stderr, we still won't see it.

But we can do better. While pwntools gives us a leg up, we may not want to have to install pwntools at all, and we want to still see our stderr output.

I did a couple of searches out there to see what other people suggest. There are plenty of articles like this excellent one: https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/ They highlight how to get a fully interactive tty, but they require you to have certain things on the server side. I don't want to have to deal with finding (or compiling) binaries, or even having to upload them, trying various commands only to find that they don't work. And then having to go through all that effort time and again on different environments.

So I created a script to implement a bunch of functionality that I wanted to have

  • command history
  • ability to preload command history with useful commands I'd like to run
  • wrap everything with stderr to stdout redirection, so that we can see stderr output

See revconsole.py below. Hopefully this is helpful to someone else out there that is annoyed by this problem as well.

#!/usr/bin/env python3
import argparse
import socket
import threading
from time import sleep
from prompt_toolkit import PromptSession
from prompt_toolkit.history import InMemoryHistory
# Function to handle incoming messages
def receive_messages(sock):
while True:
try:
data = sock.recv(1024*4)
if data:
print(data.decode('latin'))
else:
break
except socket.error as e:
print(f"Error receiving data: {e}")
break
# Parse command line arguments
parser = argparse.ArgumentParser(description='Connect to a socket and send commands.')
parser.add_argument('ip', help='IP address to connect to')
parser.add_argument('port', type=int, help='Port number to connect to')
args = parser.parse_args()
# Initialize history with preloaded commands
history = InMemoryHistory()
preloaded_commands = ['ls -la',
'uname -a',
'whoami',
]
for cmd in preloaded_commands:
history.append_string(cmd)
# Create a prompt session with the preloaded history
session = PromptSession(history=history)
try:
# Create socket and connect to the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((args.ip, args.port))
# Start the receiving thread
recv_thread = threading.Thread(target=receive_messages, args=(s,))
recv_thread.daemon = True # This thread dies when the main thread exits
recv_thread.start()
while True:
try:
# Prompt user for command with history support
command = session.prompt('# ')
# Append 2>&1 to redirect stderr to stdout, then append newline
full_command = f"{command} 2>&1\n"
# Send the command
try:
s.sendall(full_command.encode())
except socket.error as e:
print(f"Error sending data: {e}")
except KeyboardInterrupt:
# Exit on Ctrl-C
print('Exiting...')
break
except EOFError:
# Exit on Ctrl-D
print('Exiting...')
break
sleep(0.5)
except socket.error as e:
print(f"Could not connect to server: {e}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment