#!/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 => "###########-advert", 10 => "###########-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_###########ADVERT => 9; #use constant ICMP_###########SOLICIT => 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_###########ADVERT => "###########-advert", # ICMP_###########SOLICIT => "###########-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);