#!/usr/bin/perl
######################################################################
#
# http://www.unspecific.com/spa/
#
#
# Copyright (c) 2005, MadHat (madhat@unspecific.com)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#   * Neither the name of MadHat Productions nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
######################################################################


$sparc = "$ENV{HOME}/.spa/sparc";
$gpgdir = "$ENV{HOME}/.gnupg";

use Getopt::Std; 
use GnuPG::Interface;
use Net::RawIP;
use Socket;

$VERSION = 0.3;

getopts("p:c:D:s:S:i:");
# -D is debug, with values form 1-4 for level of debugging

# we need what to do and server to send it to
if (!$opt_c or !$opt_s) {
  print STDERR "-c for command and -s for server are both required\n";
  &usage;
}

# just look at the first
if ($opt_p =~ /^t/i) {
  $opt_p = 'tcp';
} elsif ($opt_p =~ /^i/i) {
  $opt_p = 'icmp';
  print STDERR "ICMP is not supported at this time\n";
  &usage;
} elsif ($opt_p =~ /^u/i) {
  $opt_p = 'udp';
} else {
  $opt_p = 'tcp';
}

# specify the action to take
if ($opt_c eq 'open') {
  $opt_c = 1;
} elsif ($opt_c eq 'exec') {
  $opt_c = 3;
} elsif ($opt_c eq 'rvsh') {
  print STDERR "Reverse Shell is not supported at this time\n";
  &usage;
} else {
  print STDERR "Your option for -c is not supported at this time\n";
  &usage;
}

# if you don't specify the IP, we use the src ip (send 0)
if (!$opt_i) {
  $opt_i = '0';
}

# load the seq numbers and user to use
&load_conf($sparc);

my @data = @ARGV;
my $data = join(' ', @data);


if (!$conf{$opt_s}) {
  print "The server $opt_s does not appear to be setup.\n";
  print "Please check with the server admin to setup this new server.\n\n";
  exit 1;
}

my $sport = int(rand(64512)) + 1024;
my $dport = int(rand(64512)) + 1024;
my $nseq = int(rand(64512));

my @time = gmtime();
$time[5] = $time[5] + 1900;
for ($i = 0; $i < 5; $i++) {
  if (length($time[$i]) < 2) {
    $time[$i] = "0$time[$i]";
  }
}

my @seq = split(',', $conf{$opt_s}{'seq'});

if ($opt_S =~ /^\d{3,5}$/) {
  unshift(@seq, $opt_S);
}

my $time = "$time[5]$time[4]$time[3]$time[2]$time[1]$time[0]";
my @plaintext = ("$time:$seq[0],$seq[1],$nseq:$opt_i:$opt_c:$data");
print STDERR "DATASENT: $plaintext[0]\n" if ($opt_D);

# reset for saving after run
if ($opt_S) {
  $conf{$opt_s}{'seq'} = "$opt_S,$nseq";
} elsif ($seq[1]) {
  $conf{$opt_s}{'seq'} = "$seq[1],$nseq";
} else {
  $conf{$opt_s}{'seq'} = "$seq[0],$nseq";
}

my $gnupg = GnuPG::Interface->new();
$gnupg->options->default_key( $conf{$opt_s}{user} );
$gnupg->options->push_recipients( "spa\@$opt_s" );
print STDERR "Seting homedir for gnupg\n" if ($opt_D > 1);
$gnupg->options->hash_init( homedir => $gpgdir );
my ( $input, $output, $error, $status_fh ) = ( 
  IO::Handle->new(),
  IO::Handle->new(),
  IO::Handle->new(),
  IO::Handle->new(),
);
print STDERR "seting handles for gnupg\n" if ($opt_D > 1);
my $handles = GnuPG::Handles->new(
  stdin  => $input,
  stdout => $output,
  stderr => $error,
  status => $status_fh,
);
print STDERR "launching gnupg\n" if ($opt_D > 1);
my $pid = $gnupg->sign_and_encrypt( handles => $handles );
print STDERR "launched gnupg $pid\n" if ($opt_D > 1);
print $input @plaintext;
close $input;
my @ciphertext = <$output>;
close $output;
my @error = <$error>;
print "@error\n" if ($opt_D);
close $error;
close $status_fh;

waitpid $pid, 0;

$ciphertext = join('', @ciphertext);

if ($opt_D > 3) {
  open(TMP, ">spa_packet") or die("spa_packet $!");
  print TMP $ciphertext;
  close(TMP);
  open(TMP, ">spa_raw") or die("spa_raw $!");
  print TMP @plaintext;
  close(TMP);
}

print STDERR "Ciphertext created. Length: " . length($ciphertext) . "\n" if ($opt_D > 2);

if ($opt_p eq 'tcp') {
  $send = new Net::RawIP();
  $send = new Net::RawIP({tcp => {}});
  $send->set( { ip => { daddr => $opt_s, ttl => 255,
                    tos => 0x08 , frag_off => 0,
                    id => int(rand(65536)) },
                tcp => { source => $sport,  dest => $dport, syn => 1,
                   window => 16384, ack => 0,
                   doff => 5, urg => 0, }
 	 });  
} elsif ($opt_p eq 'udp') {
  $send = new Net::RawIP({udp => {}});
  $send->set( { ip => { daddr => $opt_s, ttl => 255,
                    tos => 0x08 , frag_off => 0,
                    id => int(rand(65536)) }, 
                udp => { source => $sport, dest => $dport} } );
} elsif ($opt_p eq 'icmp') {
  $send = new Net::RawIP({icmp => {}});
  $send->set( { ip => { daddr => $opt_s, ttl => 255,
                    tos => 0x08 , frag_off => 0,
                    id => int(rand(65536)) }, 
                icmp => {type => 8} } );

}
$send->set( { $opt_p => { data => $ciphertext } } );

print STDERR "Sending message to $opt_s of length " . length($ciphertext) . "\n" if ($opt_D > 2);
$send->send(); # or die ("Can't send packet: $!" . $send->strerror);

if (&save_conf($sparc) == -1) {
  print STDERR "ERROR SAVING CONFIG\n";
}

sub load_conf {
  my ($file) = @_;
  my $group = '';
  undef %conf;
  open(CONF, $file) or die ("Unable to open config($file): $!");
  while(<CONF>) {
    print STDERR "$_" if ($opt_D > 3);
    chomp;
    push @raw_conf, $_;
    next if (/^#/ or /^\s*$/);
    if (/^\[([\w\-\.]+)\]/) {
      $group = $1;
      print STDERR "GROUP: $group\n" if ($opt_D > 2);
    } elsif ($group) {
      my ($key, $value) = split(/ ?= ?/);
      $conf{$group}{$key} = $value;
      print STDERR "VALUE: $group - $key => $value\n" if ($opt_D > 2);
    }
  }
  close(CONF);
}
sub save_conf {
  my ($file) = @_;
  my $group = '';
  open(CONF, ">$file") or return -1;
  for my $group (sort keys %conf) {
    print CONF "[$group]\n";
    print STDERR "save_conf: [$group]\n" if ($opt_D > 2);
    for my $key (sort keys %{$conf{$group}}) {
      print CONF "$key=$conf{$group}{$key}\n";
      print STDERR "save_conf $key=$conf{$group}{$key}\n" if ($opt_D > 2);
    }
  }
  close(CONF);
}



sub usage {
  print "$0 [-p <proto>] [-i <IP>] [-S <seq>] -c <code> -s <server> <details>\n";
  print "  -c <code>  code to send to <server>\n";
  print "     'open' to open a port\n";
  print "     'rvsh' to open a reverse shell\n";
  print "     'exec' to execute a command\n";
  print "  -s <server>  server to send encrypted <code> to\n";
  print "  -S <seq>  sequence number to use overriding config (reset seq from admin)\n";
  print "  -i <IP>  to specify the IP to open a port for\n";
  print "     leave blank to use the IP the packet comes from (good for NAT)\n";
  print "  -p <proto> protocol to use for the spa packet\n";
  print "     tcp, udp or icmp - defaults to tcp\n\n";
  print "     <details>  with 'open' would be the port to open\n";
  print "               with 'exec' is the command to execute on the remote host\n";
  print "\n  Examples: \n";
  print " $0 -c open -i 10.0.0.5 -s spa_server.foocorp.biz 22\n";
  print "     will open port 22 for 10.0.0.5 on spa_server.foocorp.biz\n\n";
  print " $0 -c exec -s spa_server.foocorp.biz /usr/bin/reboot\n";
  print "     will reboot spa_server.foocorp.biz\n\n";
  print "  Expected GPG public key for spa@<server> in your local keyring\n";
  exit 0;
}


