Revision [2897]
This is an old revision of ddosDotPl made by JeffTaylor on 2012-05-25 05:38:58.
#!/usr/bin/perl $| = 1; # ddos.pl 2012.05.23 # by Jeff Taylor (Shdwdrgn) #--- REQUIRED PERL LIBRARIES ---# # Net::Pcap (debian: libnet-pcap-perl) # IPTables::ChainMgr (debian: libiptables-chainmgr-perl) #--- EDIT THESE VALUES AS NEEDED ---# # BLOCKFILE: where to store IP and port information use constant BLOCKFILE => "/root/ddos.dns"; # NETDEV: device your DNS service listens on use constant NETDEV => "eth0"; # NETMASK: add a mask for your server's real or local IP use constant NETMASK => [ "127.0.0.0/8", "10.0.0.0/8" ]; # DEBUG: 0=off, 1=show debug info use constant DEBUG => 0; # THRESH: maximum queries on a single port use constant THRESH => 10; # MAXPERSEC: maximum number of queries per second use constant MAXPERSEC => 40; # MAXCOUNT: limits the queries/sec that we observe use constant MAXCOUNT => 64; # MAXMULTI: Limits how high the multiplier can go use constant MAXMULTI => 8; # MAXBLOCKS: If blocking this number of ports or more # for an IP, start blocking all DNS queries from IP use constant MAXBLOCKS => 4; # BASETIME: used to calculate how long an IP is blocked use constant BASETIME => 225/128; # Approx. 2 seconds # Adjust the path to iptables for your system my %opts = ( 'iptables' => '/sbin/iptables', 'iptout' => '/tmp/iptables.out', 'ipterr' => '/tmp/iptables.err', 'debug' => 0, 'verbose' => 0 ); #--- DO NOT EDIT PAST THIS POINT ---# use POSIX qw(strftime); use Switch; use IPTables::ChainMgr; #use Net::Pcap; #use NetPacket::Ethernet; #use NetPacket::IP; #use NetPacket::TCP; #use strict; my $blocked; my @data; my $dom; my @tmp; my $IP; my $port; my $key; my $line; my $hms; my %pkt; my $tmr = time(); my $h; my $m; my $s; my $expire; my $max; my $portcount; my $netrules; my $lastReset; my $ipt_obj = new IPTables::ChainMgr(%opts) or die "[*] Could not acquire IPTables::ChainMgr object"; #---------------------- Error Handlers ----------------------# $SIG{__DIE__} = 'INT_handler'; $SIG{'INT'} = 'INT_handler'; $SIG{'TERM'} = 'INT_handler'; sub INT_handler { if (DEBUG) { print "\nEnding script...\n"; } close(DUMP); foreach $IP (keys %pkt) { foreach $port (keys %{$pkt{$IP}}) { if ($pkt{$IP}{$port}{block} == 1) { if (DEBUG) { print "* Unloading $IP:$port\n"; } iptUpdate($IP, $port, "D"); } } } exit(0); } #--------------------- Begin main loop ---------------------# $netrules=""; foreach $IP (@{+NETMASK}) { if ($netrules) { $netrules .= " and "; } $netrules .= "not src net $IP"; } $netrules = "tcpdump -pnt -i ".NETDEV." $netrules and not udp src port 53 and udp dst port 53 and '(ip[2:2] != 0)' 2>/dev/null |"; open DUMP, "$netrules" or die "Invalid tcpdump defined\n"; while (<DUMP>) { chomp ($line = <DUMP>); # Reload array data every hour $h = strftime("%H", localtime); if ($lastReset != $h) { $lastReset = $h; getBlockfile(%pkt); } # Values for {block} # 0 - Port traffic detected, but no action taken # 1 - IP:port is currently blocked # 2 - IP:port block expired, now monitoring for repeat offense # Record each packet in array @data = split(/ /, $line); @tmp = split(/\./, $data[1]); $IP = "$tmp[0].$tmp[1].$tmp[2].$tmp[3]"; $port = $tmp[4]; #$url = ($data[5] eq "[1au]") ? $data[7] : $data[6]; #@bits = reverse (split(/\./, $url)); if ($IP) { # Add new IP to array if (! $pkt{$IP}) { $pkt{$IP}{0}{block} = 0; $pkt{$IP}{0}{count} = 0; $pkt{$IP}{0}{multi} = 0; } $pkt{$IP}{0}{count}++; # New port number seen for this IP if (! $pkt{$IP}{$port}) { if (($pkt{$IP}{$port}{block} > 0) && (DEBUG)) { print "WARNING! $IP:$port adding existing block\n"; } $pkt{$IP}{$port}{block} = 0; $pkt{$IP}{$port}{count} = 0; $pkt{$IP}{$port}{multi} = 0; } # Do unless port is currently blocked if ($pkt{$IP}{$port}{block} != 1) { $pkt{$IP}{$port}{count}++; if ($pkt{$IP}{$port}{count} > MAXCOUNT) { $pkt{$IP}{$port}{count}=MAXCOUNT; } } # If IP:port exceeds limits while being monitor, prepare to block again if (($pkt{$IP}{$port}{block} == 2) && ($pkt{$IP}{$port}{count} >= THRESH)) { $pkt{$IP}{$port}{block} = 0; } } # If one second has passed since the last check, examine collected data if ($tmr != time()) { $tmr = time(); $h = strftime("%H", localtime); $m = strftime("%M", localtime); $s = strftime("%S", localtime); $hms = "$h:$m:$s"; if (DEBUG) { print "$hms Count: ", scalar(keys %pkt), " \r"; } foreach $IP (keys %pkt) { $pkt{$IP}{0}{portblock} = 0; foreach $port (reverse sort keys %{$pkt{$IP}}) { if ($port == 0) { if (($pkt{$IP}{0}{block} == 2) && ($pkt{$IP}{0}{portblock} >= MAXBLOCKS)) { $pkt{$IP}{0}{block} = 0; } } else { # Count the number of ports blocked for this IP if ($pkt{$IP}{$port}{block} == 1) { $pkt{$IP}{0}{portblock}++; } } # Add new block if ($pkt{$IP}{$port}{block} == 0) { $max = ""; if (($port > 0) && ($pkt{$IP}{$port}{count} >= THRESH)) { $max = "port"; } if (($port == 0) && ($pkt{$IP}{0}{count} >= MAXPERSEC)) { $max = "maxpersec"; } if (($port == 0) && ($pkt{$IP}{0}{portblock} >= MAXBLOCKS)) { $max = "maxblock"; } if ($max) { $pkt{$IP}{$port}{block} = 1; $pkt{$IP}{$port}{start} = $tmr; $pkt{$IP}{$port}{timer} = $pkt{$IP}{$port}{count}; if (DEBUG) { print "$hms [$pkt{$IP}{$port}{multi}] ",$max,"Blocking $IP:$port - $pkt{$IP}{$port}{count} hits\n"; } iptUpdate($IP, $port, "I"); updateBlockfile($IP, $port, %pkt); } # Remove expired block } else { $expire = $pkt{$IP}{$port}{start} + ((($pkt{$IP}{$port}{block} + 1) ** $pkt{$IP}{$port}{multi}) * $pkt{$IP}{$port}{timer} * BASETIME); if ($tmr > $expire) { $blocked = $tmr - $pkt{$IP}{$port}{start}; if ($pkt{$IP}{$port}{block} == 2) { if (DEBUG) { print "$hms Deleting $IP:$port after $blocked seconds\n"; } $pkt{$IP}{$port}{block} = 0; updateBlockfile($IP, $port, %pkt); delete ($pkt{$IP}{$port}); } else { if (DEBUG) { print "$hms Unblocking $IP:$port after $blocked seconds\n"; } iptUpdate($IP, $port, "D"); $pkt{$IP}{$port}{block} = 2; $pkt{$IP}{$port}{start} = $tmr; $pkt{$IP}{$port}{multi}++; if ($pkt{$IP}{$port}{multi}>MAXMULTI) { $pkt{$IP}{$port}{multi}=MAXMULTI; } updateBlockfile($IP, $port, %pkt); $pkt{$IP}{$port}{count} = 0; } } } # If record still exists, make updates if ($pkt{$IP}{$port}) { # Unblocked IPs should decrease their count at a normal rate if ($pkt{$IP}{$port}{block} == 2) { $pkt{$IP}{$port}{count}--; if ($pkt{$IP}{$port}{count} < 1) { $pkt{$IP}{$port}{count} = 0; } } # Remove array element when no longer needed if (($port > 0) && ($pkt{$IP}{$port}{block} == 0)) { $pkt{$IP}{$port}{count}--; if ($pkt{$IP}{$port}{count} <= 0) { if (($pkt{$IP}{$port}{timer} > 0) && (DEBUG)) { print "WARNING! Removed $IP:$port from array [$pkt{$IP}{$port}{block}]\n"; } delete ($pkt{$IP}{$port}); } } } #if exists } #foreach PORT # Clean up unused IPs $portcount = scalar(keys %{$pkt{$IP}}); if (($portcount <= 1) && ($pkt{$IP}{0}{block} == 0)) { if (($pkt{$IP}{0}{portblock} > 0) && (DEBUG)) { print "WARNING! [$pkt{$IP}{0}{portblock}] Cleanup $IP:port\n"; } delete ($pkt{$IP}); } else { $pkt{$IP}{0}{count} = 0; } } #foreach IP } } print "Error: tcpdump not found or bad NETMASK defined\n"; #----------------------- Subroutines ------------------------# sub getBlockfile { my $pkt = shift; if (-e BLOCKFILE) { # Read blockfile into memory open FH, BLOCKFILE; my @LINES = <FH>; close(FH); foreach $line (@LINES) { @data = split(/[ \t]+/, $line); if (($data[0]) && ($data[0] ne "#IP:Port")) { @tmp = split(/:/, $data[0]); $IP = $tmp[0]; $IP =~ s/^\#//; $port = $tmp[1]; $pkt{$IP}{$port}{multi} = $data[1]; if ($pkt{$IP}{$port}{multi} eq "") { $pkt{$IP}{$port}{multi} = 0; } $pkt{$IP}{$port}{count} = $data[2]; if ($pkt{$IP}{$port}{count} == 0) { $pkt{$IP}{$port}{count} = THRESH; } if (! $pkt{$IP}{$port}{timer}) { $pkt{$IP}{$port}{timer} = $pkt{$IP}{$port}{count}; } if (! $pkt{$IP}{$port}{start}) { $pkt{$IP}{$port}{start} = $data[3]; } $pkt{$IP}{$port}{block} = 1; if (substr($data[0], 0, 1) eq "#") { $pkt{$IP}{$port}{block} = 2; $pkt{$IP}{$port}{count} = 0; } else { iptUpdate($IP, $port, "I"); } if (DEBUG) { print "Loading ($pkt{$IP}{$port}{block}) $IP:$port\n"; } } } } else { # Create new blockfile open FH, ">", BLOCKFILE; print FH "#IP:Port #Multi #Count #Start #Expires\n"; close(FH); } } sub updateBlockfile { # IP, port, %pkt my $IP = shift; my $port = shift; my $pkt = shift; my $key = "$IP:$port"; my $found = 0; my $expire = int($pkt{$IP}{$port}{start} + ((($pkt{$IP}{$port}{block} * 2) ** $pkt{$IP}{$port}{multi}) * $pkt{$IP}{$port}{timer} * BASETIME)); open FH, BLOCKFILE; my @LINES = <FH>; close(FH); open FH, ">", BLOCKFILE; foreach $line (@LINES) { switch ($pkt{$IP}{$port}{block}) { case 1 { if (($line =~ /^$key/) || ($line =~ /^\#$key/)) { print FH setLen("$IP:$port"), " $pkt{$IP}{$port}{multi} $pkt{$IP}{$port}{count} $pkt{$IP}{$port}{start} $expire\n"; $found++; } else { print FH $line; } } case 2 { if (($line =~ /^$key/) || ($line =~ /^\#$key/)) { print FH setLen("#$IP:$port"), " $pkt{$IP}{$port}{multi} $pkt{$IP}{$port}{count} $pkt{$IP}{$port}{start} $expire\n"; $found++; } else { print FH $line; } } else { print FH $line unless (($line =~ /^$key/) || ($line =~ /^\#$key/)); } } } if (($pkt{$IP}{$port}{block} == 1) && (!$found)) { print FH setLen("$IP:$port"), " $pkt{$IP}{$port}{multi} $pkt{$IP}{$port}{count} $pkt{$IP}{$port}{start} $expire\n"; } close(FH); } sub setLen { my $str = $_[0]; if (length $str < 16) { $str .= " "; } return $str; } sub iptUpdate { # IP, port, I/D my $ip = $_[0]; if (substr($ip, 0, 1) eq "#") { $ip = substr($ip, 1); } my $port = $_[1]; my $act = $_[2]; if ($ip && $act) { if ($act eq "D") { if ($port > 0) { $ipt_obj->delete_ip_rule($ip, '0.0.0.0/0', 'filter', 'INPUT', 'DROP', {'protocol' => 'udp', 's_port' => $port}); } else { $ipt_obj->delete_ip_rule($ip, '0.0.0.0/0', 'filter', 'INPUT', 'DROP', {'protocol' => 'udp', 'd_port' => 53}); } } if ($act eq "I") { if ($port > 0) { $ipt_obj->add_ip_rule($ip, '0.0.0.0/0', 1, 'filter', 'INPUT', 'DROP', {'protocol' => 'udp', 's_port' => $port}); } else { $ipt_obj->add_ip_rule($ip, '0.0.0.0/0', 1, 'filter', 'INPUT', 'DROP', {'protocol' => 'udp', 'd_port' => 53}); } } } }