#!/usr/bin/perl

#
#	Name: 		Sebastian Enger / B. Sc.
#	Contact: 	bigfish82@gmail.com
# 	Contrib: 	Net::Pcap -> pcapdump
#

# perl -MCPAN -e 'install "Net::Pcap"'
# perl -MCPAN -e 'install "NetPacket::Ethernet"'
# perl -MCPAN -e 'install "NetPacket::IP"'
# perl -MCPAN -e 'install "NetPacket::TCP"'
# perl -MCPAN -e 'install "NetPacket::TCP"'
# perl -MCPAN -e 'install "Data::Hexdumper"'

# root: -20 <> 20 | normal: 0 <> 20
my $ServicePriority = "-5";				

system("renice $ServicePriority $$");
system("clear");


# $SIG{__WARN__} = sub{};

use Socket              qw(inet_ntoa);
use strict;
no strict "subs";
use Net::Pcap           qw(:functions);
use Net::RawIP;
use Pod::Usage;
use Getopt::Long        qw(:config no_auto_abbrev);
use NetPacket::IP       qw(:protos);
use Data::Dumper;
use Fcntl ':flock';
use NetPacket::TCP;
use NetPacket::UDP;
use NetPacket::ICMP;
use NetPacket::ARP;
use Data::Hexdumper;
use NetPacket::Ethernet qw(:types);
# use Time::HiRes qw( usleep ualarm gettimeofday tv_interval nanosleep clock_gettime clock_getres clock_nanosleep clock stat );

use constant VERSION       		=> "$0 20080215 @ 8:30 - version 0.1.c";
my $TODO				= "Code Cleanup / Code Beautifier | IPFILTER.DAT SUPPORT";
my $debug; # 				= 0;	# 0=false / 1=true
my $dev;

# more than $IsFloodingAttack packets a second are indication flood attack
# Later dynamically change that value
my $PacketsPerSecondIndicatingAttack	= 70; 	# 50 packets / sec and more are attacks	
my $DeleteConnectionsTimeout		= 1;	# delete hash from connection manager after n seconds
my $MaximumTcpKillInstances		= 5;

# my $ConnectionManagerICMP 		= {};
my %ConnectionManagerICMP		= ();	# icmp connection tracking
my %ConnectionManagerTCP_SYN		= ();	# tcp syn flooding checking
my %ConnectionManagerTCPKILL		= ();	# tcpkill syn flooding checking

my $IPTABLES				= "/sbin/iptables";
my $SYSCTL				= "/sbin/sysctl -w";
my $TCPKILL				= "/usr/sbin/tcpkill";	# apt-get install dsniff
my $KILLALL				= "/usr/bin/killall";


my %icmp = (
    8	=> "echo",
    0	=> "echo-reply",
    15	=> "ireq",
    16	=> "ireq-reply",
    17	=> "mask",
    18	=> "mask-reply",
    12	=> "param-prob",
    5	=> "redirect",
    9	=> "router-advert",
    10	=> "router-solicit",
    4	=> "source-quench",
    11	=> "time-exceeded",
    13	=> "timestamp",
    14	=> "timestamp-reply",
    3	=> "unreachable",
); # my %icmp = (



MAIN: {
	
	print "Setting Up SYN FLood Protection \n";
	SetSynFloodProtection();	# kernel tweak and iptables update

	print "Setting Up ICMP FLood Protection\n";
	IcmpFloodProtection();		# iptables icmp burst limit
	
	select (undef, undef, undef, rand(1) );	# sleep max 1 sec
	system("clear");

	run();
}


sub run() {
	$|++;
	
	# get options
	my %options = (
		count   =>    -1,	# loop forever 
		promisc =>     1, 
		snaplen =>   65535, 
		timeout =>    10,
		debug	=>    0,
	);
	
	GetOptions(\%options, qw{
		count|c=i  interface|i=s  promisc|p!  snaplen|s=i  writeto|w=s debug|d=i
	});
	
	$debug 	= $options{debug};
	

	my ($err, $net, $mask, $filter, $dumper);
	$dev = $options{interface} || pcap_lookupdev(\$err);
	my $filter_str = join " ", @ARGV;
	
	# open the interface
	my $pcap = pcap_open_live($dev, @options{qw(snaplen promisc timeout)}, \$err)
		or die "fatal: can't open network device $dev: $err ", "(do you have the privileges?)\n";
	
	if ($filter_str) {
		# compile the filter
		pcap_compile($pcap, \$filter, $filter_str, 1, 0) == 0
		or die "fatal: filter error\n";
		pcap_setfilter($pcap, $filter);
	}
	
	if ($options{writeto}) {
		$dumper = pcap_dump_open($pcap, $options{writeto}) 
		or die "fatal: can't write to file '$options{writeto}': $!\n";
	}
	
	# print some information about the interface we're currently using
	pcap_lookupnet($dev, \$net, \$mask, \$err);
	# print "listening on $dev (", dotquad($net), "/", dotquad($mask), ")", 
	
	
	print VERSION ." listening on $dev (", dotquad($net), "/", dotquad($mask), ")", 
		", capture size $options{snaplen} bytes";
	print ", filtering on $filter_str"  if $filter_str;
	print $/;
	
	# enter the main loop
	pcap_loop($pcap, $options{count}, \&process_packet, '');
	pcap_close($pcap);

}; # sub run {


sub process_packet() {

	# kill tcpkill if more than $MaximumTcpKillInstances are reached
	if ( keys(%ConnectionManagerTCPKILL) >= $MaximumTcpKillInstances ){
		
		# this select sleep here to give the last created tcpkill some little time to do its work before being killed
		select (undef, undef, undef, rand(1) );	# sleep max 1 sec
		%ConnectionManagerTCPKILL = ();	# hash reset
		system("$KILLALL -q -9 tcpkill");
	
	}; # if ( keys(%ConnectionManagerTCPKILL) >= $MaximumTcpKillInstances ){


	my ($user_data, $header, $packet) = @_;
#	my ($proto, $payload, $src_ip, $src_port, $dest_ip, $dest_port, $flags, $seqnum, $acknum, $icmptype);
	

	printf "packet: len=%s, caplen=%s, tv_sec=%s, tv_usec=%s\n", map { $header->{$_} } qw(len caplen tv_sec tv_usec) if ( $debug == 1);
	

	# decode the Ethernet frame
	my $ethframe 	= NetPacket::Ethernet->decode($packet);
	my $src_mac	= $ethframe->{src_mac};
	my $dest_mac	= $ethframe->{dest_mac};
	my $payload;

	if ($ethframe->{type} == ETH_TYPE_IP) {			# IP
	
		# decode the IP payload
		my $ipframe = NetPacket::IP->decode($ethframe->{data});
		
		my $src_ip  = $ipframe->{src_ip};
		my $dest_ip = $ipframe->{dest_ip};


		############
		#### ICMP FLOOD DETECTION AND COUNTER MESSURES
		###########

		if ($ipframe->{proto} == IP_PROTO_ICMP) {	# ICMP
	
			my $icmpframe 	= NetPacket::ICMP->decode($ipframe->{data});
			$payload 	= $icmpframe->{data};
			my $icmptype	= $icmpframe->{type};
			# my $icmpcode	= $icmpframe->{code};
			

			# i got one more icmp packet from $src_ip
			$ConnectionManagerICMP{$src_ip}{COUNT}++;

			if ( !exists( $ConnectionManagerICMP{$src_ip}{STARTPACKET} ) ) {
				$ConnectionManagerICMP{$src_ip}{STARTPACKET} = time();
			}; # if ( !exists( $ConnectionManagerICMP->{$src_ip}->{STARTPACKET}) {

			
			# get timestamp of first and last packet && count packets already send
			my $icmp_start 	= $ConnectionManagerICMP{$src_ip}{STARTPACKET};
			# my $icmp_last	= $ConnectionManagerICMP{$src_ip}{LASTPACKET};
			my $icmp_last	= time();
			my $icmp_count	= $ConnectionManagerICMP{$src_ip}{COUNT};
			my $RunningSecs	= $icmp_last - $icmp_start || 1; # to get no Division by Zero
			my $PktsPerSec	= sprintf("%.1f", ( $icmp_count / $RunningSecs ) );


			
			if ( $PktsPerSec >= $PacketsPerSecondIndicatingAttack ){ # ICMP Attack indication
			
				print "Warning Indication of ICMP FLOOD from $src_ip \a\n";
				
				# counter messures
				
				# drop all icmp packets from attacker:				
				system("$IPTABLES -A INPUT -i $dev -p icmp --icmp-type 255 -s $src_ip -d $dest_ip -j DROP");
				
				# destination unreachable
				IcmpFloodPacketResponse($src_ip);
	
				
				# max one instance of tcpkill per ip
				if ( !exists( $ConnectionManagerTCPKILL{$src_ip} ) ) {
					$ConnectionManagerTCPKILL{$src_ip} = "";
					# kill connections from Attacker
					system("$TCPKILL -9 -i $dev host $src_ip &");	
				}; # if ( !exists( $ConnectionManagerTCPKILL{$src_ip} ) ) {

			
			} ; # if ( $PktsPerSec >= $PacketsPerSecondIndicatingAttack ){

		
			# if more than $DeleteConnectionsTimeout seconds have passed -> delete Entrie for IP
			if ( $RunningSecs > $DeleteConnectionsTimeout ) {	
				# reset connection manager hash for ip $src_ip
				delete $ConnectionManagerICMP{$src_ip};
			}; # if ( $ConnectionManagerICMP{$src_i ....

			print "IP:ICMP [$PktsPerSec pkt/s] src:$src_ip ---> dest:$dest_ip - " . %icmp->{$icmptype} . " \n";	


		############
		#### TCP SYN FLOOD DETECTION AND COUNTER MESSURES
		###########

		} elsif ($ipframe->{proto} == IP_PROTO_TCP) {	# TCP
	
			my $tcpframe 	= NetPacket::TCP->decode($ipframe->{data});
			my $src_port  	= $tcpframe->{src_port};
			my $dest_port 	= $tcpframe->{dest_port};
			$payload   	= $tcpframe->{data};
			my $seqnum    	= $tcpframe->{seqnum};
			my $acknum    	= $tcpframe->{acknum};
			my $flags     	= flags_of($tcpframe->{flags});
			
			# i got one more icmp packet from $src_ip
			$ConnectionManagerTCP_SYN{$src_ip}{COUNT}++;

			# Start TCP Syn Connection Manager
			if ( !exists( $ConnectionManagerTCP_SYN{$src_ip}{STARTPACKET} ) && $flags eq "syn" ) {
				$ConnectionManagerTCP_SYN{$src_ip}{STARTPACKET} = time();
			}; # if ( !exists( $ConnectionManagerTCP_SYN->{$src_ip}->{STARTPACKET}) {

			# get Syn Counts
			my $syn_count	= $ConnectionManagerTCP_SYN{$src_ip}{COUNT};
			
			# Save ACK Number for each ncoming SYN request
			$ConnectionManagerTCP_SYN{$src_ip}{$syn_count}{COUNT_ACK} = $acknum;

			my $syn_start	= $ConnectionManagerTCP_SYN{$src_ip}{STARTPACKET};
			my $syn_last	= time();
			my $syn_count	= $ConnectionManagerTCP_SYN{$src_ip}{COUNT};
			my $RunningSecs	= $syn_last - $syn_start || 1; # to get no Division by Zero
			my $PktsPerSec	= sprintf("%.1f", ( $syn_count / $RunningSecs ) );


			if ( $PktsPerSec >= $PacketsPerSecondIndicatingAttack ){ # TCP SYN FLOOD Attack indication

				print "Warning Indication of SYN FLOOD from $src_ip \a\n";
				
				for( my $i=0; $i<=$syn_count; $i++ ) {

					my $seq = $ConnectionManagerTCP_SYN{$src_ip}{$i}{COUNT_ACK};
					next if ( $seq !~ /\d/ ); 
					SynFloodPacketResponse($src_ip, $dest_ip, $seq, $src_port, $dest_port);	# Attacker, MyIP, NewSeQ, AttackerPort, MyPort

				}; # for( my $i=0; $i<=$syn_count; $i++ ) {

	
				# Kill Attacker Connection
				# max one instance of tcpkill per ip
				if ( !exists( $ConnectionManagerTCPKILL{$src_ip} ) ) {
					$ConnectionManagerTCPKILL{$src_ip} = "";
					# kill connections from Attacker
					system("$TCPKILL -9 -i $dev host $src_ip &");	
				}; # if ( !exists( $ConnectionManagerTCPKILL{$src_ip} ) ) {


			}; # if ( $PktsPerSec >= $PacketsPerSecondIndicatingAttack ){


			if ( $debug == 1) { 
				print "IP:TCP [$PktsPerSec pkt/s] ($flags) seq:$seqnum ack:$acknum $src_ip:$src_port [$src_mac] ---> $dest_ip:$dest_port [$dest_mac] ($flags)\n";
			} else {
				print "IP:TCP [$PktsPerSec pkt/s] ($flags) $src_ip:$src_port ---> $dest_ip:$dest_port\n";
			}; # if ( $debug == 1) { 



		} elsif ($ipframe->{proto} == IP_PROTO_UDP) {	# UDP
	
			my $udpframe = NetPacket::UDP->decode($ipframe->{data});
			my $src_port  = $udpframe->{src_port};
			my $dest_port = $udpframe->{dest_port};
			$payload   = $udpframe->{data};

			print "IP:UDP $src_ip:$src_port ---> $dest_ip:$dest_port\n";
	
		}; # if ($ipframe->{proto} == IP_PROTO_ICMP) {

	#	print "IP:$proto seq:$seqnum $src_ip:$src_port ---> $dest_ip:$dest_port ($flags)\n";
		print hexdump(data => $payload, start_position => 0) if ( length $payload && $debug == 1 );
	#	print $/;

	} elsif ( $ethframe->{type} == ETH_TYPE_ARP ) {		# ARP

		my $arp_obj = NetPacket::ARP->decode($ethframe->{data}, $ethframe);
	#	print Dumper $ethframe;
	#	my $proto 	= $arp_obj->{proto};
	#	my $htype 	= $arp_obj->{htype};
	#	my $opcode	= $arp_obj->{opcode};
	#	my $RawSrcMAC	= $arp_obj->{sha};
	#	my $RawDestMAC	= $arp_obj->{tha};
	
		# print "IP:ARP $arp_obj->{spa}:$arp_obj->{sha} ---> $arp_obj->{tpa}:$arp_obj->{tha} ($proto,$htype,$opcode)\n";
		print "IP:ARP $src_mac ---> $dest_mac\n";
	
	}; # if ($ethframe->{type} == ETH_TYPE_IP) {	# IP

	#return;

}; # sub process_packet {




sub SetSynFloodProtection(){
	
	open(N,">/proc/sys/net/ipv4/tcp_syncookies");
	flock(N,LOCK_EX);
		print N 1;
	flock(N,LOCK_UN);
	close N;

	system("$IPTABLES -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j RETURN");

	# Limit the number of incoming tcp connections
	# Interface 0 incoming syn-flood protection
	system("$IPTABLES -N syn_flood");
	system("$IPTABLES -A INPUT -p tcp --syn -j syn_flood");
	system("$IPTABLES -A syn_flood -m limit --limit 1/s --limit-burst 3 -j RETURN");
	system("$IPTABLES -A syn_flood -j DROP");

	IcmpFloodProtection();
	return 1;

}; # sub SetSynFloodProtection(){


sub IcmpFloodProtection(){

	# /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses = 1
	# /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts = 1
	# /proc/sys/net/ipv4/icmp_echo_ignore_all = 1
	
	open(N,">/proc/sys/net/ipv4/icmp_ignore_bogus_error_responses");
	flock(N,LOCK_EX);
		print N 1;
	flock(N,LOCK_UN);
	close N;

	open(N,">/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts");
	flock(N,LOCK_EX);
		print N 1;
	flock(N,LOCK_UN);
	close N;

	#Limiting the incoming icmp ping request:
	system("$IPTABLES -A INPUT -p icmp -m limit --limit  1/s --limit-burst 1 -j ACCEPT");
	system("$IPTABLES -A INPUT -p icmp -j DROP");
	system("$IPTABLES -A OUTPUT -p icmp -j ACCEPT");
	
	return 1;

}; # sub _IcmpFloodProtection(){



sub IcmpFloodPacketResponse(){

	my $daddr = shift;
	
	# Packet preparation - are these settings ok?
	my $PacketResponseICMP = Net::RawIP->new({
		ip => {
			ihl 		=> 4,
			# tot_len 	=> 1024,
			# id 		=> 1,
			ttl 		=> 10,
			# frag_off 	=> 0,
			daddr 		=> $daddr,
			# saddr 	=> $dest_ip,
		},
		icmp => { # http://mandalex.manderby.com/i/icmp.php
			# id 		=> 2650, 
			# data 		=> $data 
			type 		=> 3,	# t=3 +	c=1 -> destination host unreachable (Host nicht erreichbar)
			code 		=> 1,
			# sequence 	=> int rand(255)
			}
		});

	print "Sending ICMP Packet 'destination host unreachable' to $daddr\n";
	$PacketResponseICMP->send;

	return 1;

}; # sub IcmpFloodPacketResponse(){



sub SynFloodPacketResponse(){
	# AttackerIP, MyIP, NewSeQ, AttackerPort, MyPort
		
	my $daddr	= shift;
	my $saddr	= shift;
	my $seq		= shift;
	my $dest	= shift;
	my $source	= shift;

	my $PacketResponseSYN = Net::RawIP->new({
		ip => {
			# ihl 		=> 4,
			tot_len 	=> 1024,
			id 		=> 1,
			ttl 		=> 10,
			frag_off 	=> 1,
			daddr 		=> $daddr,
			saddr 		=> $saddr,
			
		},
		tcp => {
			source => $source,
			dest   => $dest,
			seq	=> $seq,
			rst	=> 1,	# Reset Connection
		},
	});

	print "Sending RST Packet from my $saddr to attacker $daddr\n";
	$PacketResponseSYN->send;

	return;

}; # sub SynFloodPacketResponse(){




sub flags_of() {

	my ($flags) = @_;
	my @strarr = ();
	push @strarr, "urg" if $flags & URG;
	push @strarr, "ack" if $flags & ACK;
	push @strarr, "psh" if $flags & PSH;
	push @strarr, "fin" if $flags & FIN;
	push @strarr, "syn" if $flags & SYN;
	push @strarr, "rst" if $flags & RST;
	push @strarr, "ece" if $flags & ECE;
	push @strarr, "cwr" if $flags & CWR;
	return join ",", @strarr;

}; # sub flags_of() {


sub dotquad() {

	my $string = inet_ntoa( pack("I", $_[0]) );
	my ($d,$c,$b,$a) = split(/\./, $string);
	return "$a.$b.$c.$d";
	
}; # sub dotquad() {



sub ip2name {
my $addr = shift;

no strict 'subs';
(gethostbyaddr(pack("N",$addr),AF_INET))[0] || ip2dot($addr);
use strict;
}

sub ip2dot {
sprintf("%u.%u.%u.%u",unpack "C4", pack "N1", shift);
}

sub quit {
	exit(0);
}




# ICMP
# my $ConnectionManagerICMP = {};
# $ConnectionManagerICMP->{$src_ip}->{ICMP}->{START} = time();
# if ( !exists( $ConnectionManagerICMP->{$src_ip}->{ICMP}->{START}) {
#  $ConnectionManagerICMP->{$src_ip}->{ICMP}->{START} = time();
# };
# $ConnectionManagerICMP->{$src_ip}->{ICMP}->{LASTPACKET} = time();
# $ConnectionManagerICMP->{$src_ip}->{ICMP}->{COUNT}++;






# ICMP Types
#use constant ICMP_ECHOREPLY       => 0;
#use constant ICMP_UNREACH         => 3;
#use constant ICMP_SOURCEQUENCH    => 4;
#use constant ICMP_REDIRECT        => 5;
#use constant ICMP_ECHO            => 8;
#use constant ICMP_ROUTERADVERT    => 9;
#use constant ICMP_ROUTERSOLICIT   => 10;
#use constant ICMP_TIMXCEED        => 11;
#use constant ICMP_PARAMPROB       => 12;
#use constant ICMP_TSTAMP          => 13;
#use constant ICMP_TSTAMPREPLY     => 14;
#use constant ICMP_IREQ            => 15;
#use constant ICMP_IREQREPLY       => 16;
#use constant ICMP_MASKREQ         => 17;
#use constant ICMP_MASKREPLY       => 18;
#
#my %icmp = (
#    ICMP_ECHO           => "echo",
#    ICMP_ECHOREPLY      => "echo-reply",
#    ICMP_IREQ           => "ireq",
#    ICMP_IREQREPLY      => "ireq-reply",
#    ICMP_MASREQ         => "mask",
#    ICMP_MASKREPLY      => "mask-reply",
#    ICMP_PARAMPROB      => "param-prob",
#    ICMP_REDIRECT       => "redirect",
#    ICMP_ROUTERADVERT   => "router-advert",
#    ICMP_ROUTERSOLICIT  => "router-solicit",
#    ICMP_SOURCEQUENCH   => "source-quench",
#    ICMP_TIMXCEED       => "time-exceeded",
#    ICMP_TSTAMP         => "timestamp",
#    ICMP_TSTAMPREPLY    => "timestamp-reply",
#    ICMP_UNREACH        => "unreachable",
#);


#				my $pid = fork();
#				if ($pid == 0) {
#					# print “IM THE CHILD\n”;
#					exec("$TCPKILL -9 -i $dev host $src_ip");	# kill connections from Attacker
#					exit(0);
#				} else {
#					# print “IM THE PARENT\n”;
#					waitpid($pid,0);
#				}; # if ($pid == 0) {

# /sbin/iptables -A INPUT -i eth0 -p icmp --icmp-type 255 -s 192.168.2.23 -d 127.0.0.1 -j DROP

exit(0);