Wiki source for ddosDotPl


Show raw source

%%
#!/usr/bin/perl
$| = 1;

# ddos.pl 2012.06.29
# 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 => 25;

# 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

# CHAIN: Define whic iptables chain to add rules to
use constant CHAIN => "INPUT";

# 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;
# $pkt{$IP}{$port}{start} = $tmr;
}

# Do unless port is currently blocked
if ($pkt{$IP}{$port}{block} != 1) {
$pkt{$IP}{$port}{count}++;
#$pkt{$IP}{$port}{"$bits[1].$bits[0]"}++;
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} eq "") { $pkt{$IP}{$port}{count} = THRESH; }
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) {
if ($pkt{$IP}{$port}{multi} eq "") { $pkt{$IP}{$port}{multi} = 0; }
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', CHAIN, 'DROP', {'protocol' => 'udp', 's_port' => $port});
} else {
$ipt_obj->delete_ip_rule($ip, '0.0.0.0/0', 'filter', CHAIN, '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', CHAIN, 'DROP', {'protocol' => 'udp', 's_port' => $port});
} else {
$ipt_obj->add_ip_rule($ip, '0.0.0.0/0', 1, 'filter', CHAIN, 'DROP', {'protocol' => 'udp', 'd_port' => 53});
}
}
}
}
%%
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki