Upgrading reverse shells

2020-06-01

So, you’ve managed to get a reverse shell through one means or another. Unfortunately, it’s pretty basic. You can’t use your arrow keys, the line gets overwritten if your command is too long and you can’t backspace properly. All in all, it’s not a nice experience.

Pseudo terminal

The first thing I like to do is spawn a TTY. This tells terminal tools that they can be run interactively. For example, if you run ls in a normal terminal, you are likely to see it output the files in columns:

localhost$ ls
 burppro            Desktop     GitRepos   Pictures
'Calibre Library'   Documents   go         Templates
 Contacts           Downloads   Music      Videos

However, in your reverse shell, you won’t see the output in columns

reverseshell$ ls
burppro
'Calibre Library'
Contacts
Desktop
Documents
Downloads
GitRepos
go
Music
Pictures
Templates
Videos

This is because in the first instance, ls “knows” that it is being used in an interactive session. However, in the reverse shell, ls doesn’t know that it is being used interactively, so it gives the most simple output possible. If you were to pipe ls to another program, it would do the same.

localhost$ ls | cat
burppro
'Calibre Library'
Contacts
Desktop
Documents
Downloads
GitRepos
go
Music
Pictures
Templates
Videos

This is why you can use ls with tools like grep without worrying about joining all the columns.

It is possible to spawn a tty (or at least a pseudo tty) with python2 or 3 with the same command:

python 'import pty; pty.spawn("/bin/bash")'
python2 'import pty; pty.spawn("/bin/bash")'
python3 'import pty; pty.spawn("/bin/bash")'

On most Linux boxes you come across, one of these should work. If /bin/bash isn’t available, you could try /bin/sh, although this is more limited than bash.

Setting the window size

On our reverse shell, the width and height are not set and, as a result, default to 80x24. This is usable, but not particularly nice. To change it, we will background the reverse shell and take a note of the actual terminal size.

To background, simply press ctrl+z.

reverseshell$ ^Z
zsh: suspended  nc -lvnp 4444
localhost$

To get the current size of your terminal window (in rows and columns), you can run the command:

localhost$ stty -a
speed 38400 baud; rows 59; columns 136; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z;
rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

The important part here is on the first line, in this example, I have 59 rows and 136 columns. Your values are likely to be different.

We could foreground the session by typing fg<enter><enter> and running the command

stty rows 59 columns 136

However, while we have the session backgrounded, there is some more stuff we can do.

Special keys and correct size

Special key codes, such as escape and key cords that begin with ctrl won’t work. This makes using programs like vim almost impossible.

In order to rectify this, we need to make our local shell send the raw characters through netcat. Hopefully, you still have the netcat session backgrounded, if not, you can just push ctrl+z again.

We can then run the following command in order to make our shell pass the raw characters and prevent it from echoing those characters.

stty raw -echo

In order to get the reverse shell back, type fg<enter><enter>, although you won’t see this on the screen as we have asked the terminal not echo commands (that is what the -echo does).

If your local shell is zsh, you will have to run the fg command before the prompt is re-drawn, otherwise the -echo option will be reset. If you are interested in why, read about it here.

The remediation is simply this:

stty raw -echo; fg<enter><enter>

We now simply need to tell the shell running on the remote server what the window size is. This is done with another stty command:

stty rows 59 columns 136

By this point, you should have a pretty nice terminal. You can use programs such as vim or tmux that take over the whole terminal and rely on being able to hijack specific keys.

Terminfo

Teaminfo is a database that describes the capabilities of terminals. This covers things like the ability to display colour or italics.

It is unlikely that your specific terminal has entries in the servers terminfo database, so a good choice is to set your TERM variable to xterm-256color.

This is a common setting that is very likely to be on servers, it also enables a lot of the capabilities that most people want out of a terminal, including colour support.

To do so, run:

EXPORT TERM="xterm-256color"

Making it easier

By this point you should have quite a nice terminal to use. You can use your arrow keys, vim, tmux (if it’s installed) and just about anything else. Importantly, you will also be able to use sudo or su which can ask for user input whilst running.

However, I would like to share with you my method that automates a significant part of this. You will need to be using zsh on your host machine for this to work. It may be possible in bash, I have just never spent the time trying to implement it.

Firstly, I took and adapted the widget used by Greg Hurrell uses to foreground tasks with ctrl+z.

function fg-bg() {
    if [[ $#BUFFER -eq 0 ]]; then
            fg
    fi
}
zle -N fg-bg
bindkey '^Z' fg-bg

This means that after pushing ctrl+z to background a task, you can then push ctrl+z again to foreground it.

However, if we have backgrounded netcat, I would like to perform some extra steps. In the code below I check if the program is nc, ncat or netcat.

local backgroundProgram="$(jobs | tail -n 1 | awk '{print $4}')"
case "$backgroundProgram" in
    "nc"|"ncat"|"netcat")
        # Our NC logic needs to go here
        ;;
    *)
        fg
        ;;
esac

Inside the netcat case, I get the rows and columns, put the commands that need running on the server on my clipboard and run the stty raw -echo command on my host. That looks like this:

# Make sure that /dev/tty is given to the stty command by doing </dev/tty
local columns=$(stty -a < /dev/tty | grep -oE 'columns [0-9]+' | cut -d' ' -f2)
local rows=$(stty -a < /dev/tty | grep -oE 'rows [0-9]+' | cut -d' ' -f2)
notify-send "Terminal dimensions" "Rows: $rows\nColumns: $columns\nstty command on clipboard"
echo "stty rows $rows cols $columns
export TERM=\"xterm-256color\"" | xclip -i -selection clipboard
stty raw -echo < /dev/tty; fg

Notice that we need to provide the stty command /dev/tty as we are not running it interactively.

Put that all together:

function fg-bg() {
    if [[ $#BUFFER -eq 0 ]]; then
        local backgroundProgram="$(jobs | tail -n 1 | awk '{print $4}')"
        case "$backgroundProgram" in
            "nc"|"ncat"|"netcat")
                # Make sure that /dev/tty is given to the stty command by doing </dev/tty
                local columns=$(stty -a < /dev/tty | grep -oE 'columns [0-9]+' | cut -d' ' -f2)
                local rows=$(stty -a < /dev/tty | grep -oE 'rows [0-9]+' | cut -d' ' -f2)
                notify-send "Terminal dimensions" "Rows: $rows\nColumns: $columns\nstty command on clipboard"
                echo "stty rows $rows cols $columns
                export TERM=\"xterm-256color\"" | xclip -i -selection clipboard
                stty raw -echo < /dev/tty; fg
                ;;
            *)
                fg
                ;;
        esac
    fi
}
zle -N fg-bg
bindkey '^Z' fg-bg

There we go. Once you have a reverse shell, you still have to manually type the python command to get a tty. I suggest looking into your terminal emulator’s settings to see if you can map the command to a key binding. Once that is done, simply press ctrl+z twice, then enter. You will be able to paste the stty command in order to set the correct width and height.

Reverse shell upgraded quickly