Upgrading reverse shells
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.