| Home | FAQ | Statistics | Various | Contact |
Security : Linux : Jamd
Jamd is a small perl daemon to tarpit port scanners, spammers, script-kiddies and various DoS attacks (slowloris).
Tarpit method is already well-known with tools like LaBrea and iptables tarpit module as well. Jamd uses the same concept but makes it simpler :
To run jamd, you need at least to include your network interface and an IP or port (source or destination) as command line parameters.
The following Perl modules are needed to run jamd :
When using jamd, you must ensure that your server will not reply to any request your are willing to tarpit. This usually implies adding a rule to the server firewall that will drop those packets. Under Linux, iptables can be used with the '-j DROP' action (see below).
Here is a short and non-exhaustive list of jamd usages.
1) block those 3 ports with iptables (Linux) :
2) run jamd (port range):
1) block any access to that IP :
2) run jamd :
1) block any access to those 2 ports :
2) run jamd (multiports) :
1) block his access to port 80 :
2) run jamd :
3) restart Apache to cool it down and close all those open sockets.
Within few second and without even noticing the change, the attacker will become the victim. Those 2000 sockets will remain open on his server only, not yours. The results is more or less equivalent to a reverse "netkill" DoS attack.
1) create a subdomain (ex : jamd.domain.com), point it to your IP and add a MX record :
3) block any access to the port 25 of that IP :
4) run jamd :
Spammers will be pleased to collect all those email addresses and to get jammed on your port 25.
Latest version : v1.0.1 (05-May-2010)
I - Overview :
- portability : it works on any Linux distribution, FreeBSD and probably others *BSD and even possibly Mac.
- plug'n'play : it can be ran and stopped easily, there's nothing to compile and/or patch.
II - Parameters :
options :
--stop : stop the daemon.
--help : display this menu.
--debug : run in debug mode (verbose, no daemon).
--test : simulation (same as debug but doesn't send packets).
--nopromisc : do not use promiscuous mode.
--interface : network interface.
--stats : display stats
TCP parameters :
--source <IP> : source IP (the victim).
--sport <src_port> : source port (single port '--sport 25', multiports
'--sport 25,80,81' or port range '--sport 25-81').
--destination <IP> : destination IP (your server).
--dport <dest_port> : destination port (single port '--dport 25', multiports
'--dport 25,80,81' or port range '--dport 25-81').
'--dport' and '--sport' parameters can be a single port, multiports or port range.
III - Perl modules needed :
IV - Using jamd :
# iptables -I INPUT -i eth0 -d 1.2.3.4 -p tcp --dport 137:139 -j DROP
# jamd --interface eth0 --destination 1.2.3.4 --dport 137-139
# iptables -I INPUT -i eth0 -d 1.2.3.4 -j DROP
# jamd --interface eth0 --destination 1.2.3.4
# iptables -I INPUT -i eth0 -d 1.2.3.4 -p tcp -m multiport --dports 21,110
# jamd --interface eth0 --destination 1.2.3.4 --dport 21,110
# iptables -I INPUT -i eth0 -s 2.2.2.2 -p tcp --dport 80 -j DROP
# jamd --interface eth0 --destination 1.2.3.4 --dport 80 --source 2.2.2.2
jamd.domain.com. IN MX 10 jamd.domain.com.
2) insert / hide tons of fake email addresses inside your HTML pages of your website (ie: hello@jamd.domain.com, eatme@jamd.domain.com, spamsucks@jamd.domain.com etc).
# iptables -I INPUT -i eth0 -d 1.2.3.4 -p tcp --dport 25 -j DROP
# jamd --interface eth0 --destination 1.2.3.4 --dport 25
V - Source :
#!/usr/bin/perl
######################################################################
# jamd :
#
# tarpit daemon to jam ports scanners, script-kiddies,
# spammers and some DoS attacks (eg: slowloris)
#
# (c) Jerome Bruandet - http://spamcleaner.org/
#
# version 1.0.1 - 05-May-2010
#
# doc : http://spamcleaner.org/en/misc/jamd.html
#
######################################################################
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
######################################################################
use strict;
use Socket;
use Net::RawIP;
use Net::Pcap;
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP;
use NetPacket::TCP;
use POSIX qw(setsid);
use Getopt::Long;
# signal handlers :
$SIG{INT} = \&stop;
$SIG{QUIT} = \&stop;
$SIG{TERM} = \&stop;
my $appname = 'jamd';
my $version = '1.01';
my $logfile = '/var/log/jamd.log';
my $pidfile = '/var/run/jamd.pid';
my $copyright = '(c) 2010 Jerome Bruandet - http://spamcleaner.org/';
if ( $> ) {
print "$appname $version - $copyright\n\n";
print "\t[ERROR] : you must be root\n\n";
exit 1;
}
my ( $pid, $res, $date, $error );
my ( $err, $addr, $mask, $filter_t, $pcap, $test, $filter );
my ( $debug, $stop, $help, $interface, $nopromisc,
$source, $sport, $destination, $dport );
my $promisc = 1;
# command line parameters :
GetOptions (
'debug' => \$debug,
'stop' => \&stop,
'test' => \$test,
'help' => \&help,
'nopromisc' => \$nopromisc,
'interface=s' => \$interface,
'source=s' => \$source,
'sport=s' => \$sport,
'destination=s' => \$destination,
'dport=s' => \$dport,
'stats' => \&show_stats,
);
my @tmp_filter;
$promisc = 0 if ( $nopromisc );
# check for port/port range and multiport :
if ( $sport =~ /-/ ) {
push @tmp_filter, 'tcp src portrange ' . $sport . ' ';
} elsif ( $sport =~ /,/ ) {
my @tmp = split( /,/, $sport );
$sport = '';
foreach ( @tmp ) {
goto sp if (! $sport );
$sport.= 'and ';
sp: $sport.= "tcp src port $_ ";
}
push @tmp_filter, $sport;
} elsif ( $sport =~ /^\d+$/ ) {
push @tmp_filter, 'tcp src port ' . $sport . ' ';
} else {
$sport = '';
}
if ( $dport =~ /-/ ) {
push @tmp_filter, 'tcp dst portrange ' . $dport . ' ';
} elsif ( $dport =~ /,/ ) {
my @tmp = split( /,/, $dport );
$dport = '';
foreach ( @tmp ) {
goto dp if (! $dport );
$dport.= 'and ';
dp: $dport.= "tcp dst port $_ ";
}
push @tmp_filter, $dport;
} elsif ( $dport =~ /^\d+$/ ) {
push @tmp_filter, 'tcp dst port ' . $dport . ' ';
} else {
$dport = '';
}
# check IPs :
if ( $source ) {
if ( $source !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : source IP ($source) is not correct\n\n";
exit 1;
} else {
push @tmp_filter, 'src host ' . $source . ' ';
}
}
if ( $destination ) {
if ( $destination !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : destination IP ($destination) is not correct\n\n";
exit 1;
} else {
push @tmp_filter, 'dst host ' . $destination . ' ';
}
}
if ( ( ! $source ) && ( ! $sport ) && ( ! $destination ) && ( ! $dport ) ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : you must select at least a port (or an IP)\n";
print "\tand the network interface\n\nrun jamd --help\n\n";
exit 1;
}
# interface :
if ( ! $interface ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : interface is missing\n\n";
exit 1;
}
`ifconfig | grep '^$interface' > /dev/null`;
if ( $? >> 8) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : cannot find interface ($interface)\n\n";
exit 1;
}
$debug = 1 if $test;
# setup our filter :
foreach ( @tmp_filter ) {
goto filter if (! $filter);
$filter.= 'and ';
filter:
$filter.= $_;
}
chop $filter if ( $filter =~ /\s$/ );
&run;
exit;
######################################################################
sub run {
my $res = &is_running;
if (! $res ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : daemon is already running (PID : $pid)\n\n";
exit 1;
}
if ( ! $debug ) {
print "$appname $version : $copyright\n\n" .
"\t- starting daemon on interface [$interface]\n" .
"\t- filter : $filter\n" .
"\t- DON'T FORGET to drop those packets with your firewall !\n\n";
}
# become a daemon :
&daemonize if ( ! $debug );
# save PID :
open PID, ">$pidfile";
print PID $$;
close PID;
# log file :
open LOG, ">>$logfile";
select((select(LOG), $|=1)[0]);
$date = `date '+%d/%b/%Y %T'`;
chomp $date;
my $temp;
if ( $test ) { $temp = ' in ** test mode **' }
elsif ( $debug ) { $temp = ' in ** debug mode **' }
print LOG "$date [$appname $version] : starting ".
"daemon (PID : $$)$temp\n";
print "$appname $version : $copyright\n\n";
print "\t[OK] : starting daemon (PID : $$)$temp\n\n";
print "- setting up packet sniffer :\n" .
"\tinterface : $interface\n" if $debug;
print LOG "$date [$appname $version] : interface : $interface\n";
if ( Net::Pcap::lookupnet( $interface, \$addr, \$mask, \$err) == -1 ) {
print LOG "$date [$appname $version] : ERROR ".
"Net::Pcap::lookupnet failed ($err)\n";
print "- ERROR : check $logfile\n" if ( $debug );
$error = 1;
&stop;
}
print "\tpromiscuous : $promisc\n" if $debug;
print LOG "$date [$appname $version] : promiscuous mode : $promisc\n";
# we only capture the fisrt 96 bytes of each packet :
my $pcap = Net::Pcap::open_live( $interface, 96, $promisc, 0, \$err);
if (! $pcap ) {
print LOG "$date [$appname $version] : ERROR ".
"Net::Pcap::open_live failed ($err)\n";
print "- ERROR : check $logfile\n" if ( $debug );
$error = 1;
&stop;
}
print "\tfilter : $filter\n" if $debug;
print LOG "$date [$appname $version] : filter : '$filter'\n";
if ( Net::Pcap::compile( $pcap, \$filter_t, $filter, 0, $mask ) == -1 ) {
print LOG "$date [$appname $version] : ERROR ".
"Net::Pcap::compile failed\n";
print "- ERROR : check $logfile\n" if ( $debug );
$error = 1;
&stop;
}
Net::Pcap::setfilter( $pcap, $filter_t);
print "- listening...\n" if $debug;
print LOG "$date [$appname $version] : listening....\n";
# loop forever :
Net::Pcap::loop($pcap, -1, \&process_packet, 0);
}
######################################################################
sub process_packet {
my( $user_data, $header, $packet ) = @_;
# check whether it is a SOCK_RAW packet
# or a SOCK_DGRAM packet (linux cooked) :
if ( $packet =~ /^.{14}\x08\x00/ ) {
print "- linux cooked detected, removing 2 bytes\n" if $debug;
# remove 2 first bytes :
$packet = substr( $packet, 2);
}
# decode packet :
my $ether_data = NetPacket::Ethernet::strip($packet);
my $ip = NetPacket::IP->decode($ether_data);
my $tcp = NetPacket::TCP->decode($ip->{'data'});
# check flags :
if ( $tcp->{flags} == SYN ) {
print "- SYN from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]" .
" => sending SYN/ACK\n" if $debug;
$date = `date '+%d/%b/%Y %T'`;
chomp $date;
print LOG "$date [$appname $version] : connection from " .
"$ip->{src_ip}:$tcp->{src_port} to ".
"$ip->{dest_ip}:$tcp->{dest_port}\n";
# don't do anything if we are running in test mode :
return if $test;
# reply to SYN with a SYN/ACK :
my $seq = int( rand( 5000000 ) );
my $packet = Net::RawIP->new( {
ip => {
frag_off => 0, tos => 0,
saddr => $ip->{dest_ip},
daddr => $ip->{src_ip}
},
tcp =>{
dest => $tcp->{src_port},
source => $tcp->{dest_port},
seq => $seq,
ack => 1,
syn => 1,
ack_seq => $tcp->{seqnum}+1,
window => 0
}
} );
$packet->send;
} elsif ( $tcp->{flags} == ( SYN | ACK ) ) {
print "- SYN/ACK from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]" .
" => sending RST\n" if $debug;
return if $test;
# we don't send SYN, therefore we have no reason
# to receive any SYN/ACK => reset it :
my $packet = Net::RawIP->new( {
ip => {
frag_off => 0,
tos => 0,
saddr => $ip->{dest_ip},
daddr => $ip->{src_ip}
},
tcp =>{
dest => $tcp->{src_port},
source => $tcp->{dest_port},
seq => $tcp->{acknum},
rst => 1
}
} );
$packet->send;
} elsif (( $tcp->{flags} == ACK ) || ( $tcp->{flags} == (PSH | ACK) )) {
print "- ACK from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]" .
" => jamming with 0-byte window !\n" if $debug;
return $test;
# our victim thinks the connection is established,
# let's slow it down with zero-byte window ACKs :
my $packet = Net::RawIP->new( {
ip => {
frag_off => 0,
tos => 0,
saddr => $ip->{dest_ip},
daddr => $ip->{src_ip}
},
tcp => {
dest => $tcp->{src_port},
source => $tcp->{dest_port},
seq => $tcp->{acknum},
ack => 1,
syn => 0,
ack_seq => $tcp->{seqnum}+1,
window => 0
}
} );
$packet->send;
} else {
# don't know what this packet is, let's ignore it :
print "- ignoring TCP flag #" . $tcp->{flags} .
" from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]\n" if $debug;
}
}
######################################################################
sub stop {
my $res = &is_running;
print "$appname $version : $copyright\n\n";
if ( $res ) {
print "\t[ERROR] : daemon is not running\n\n";
exit 1;
}
# we don't have much choice : kill it the hard way otherwise
# we may no be able to break the Net::Pcap::loop :
if ( kill 9, $pid ) {
unlink $pidfile;
$date = `date '+%d/%b/%Y %T'`;
chomp $date;
open LOG, ">>$logfile";
print LOG "$date [$appname $version] : stopping daemon\n";
close LOG;
print "\t[OK] : stopping daemon\n\n";
exit;
# unlikely... :
} else {
print "\t[ERROR] : cannot stop the daemon\n\n";
}
}
######################################################################
sub daemonize {
chdir '/' or die "- [ERROR] : cannot chdir to [/] : $!\n";
umask 0;
open STDIN, '/dev/null' or
die "- [ERROR] : cannot read [/dev/null] : $!\n";
open STDOUT, '>/dev/null' or
die "- [ERROR] : cannot write to [/dev/null] : $!\n";
open STDERR, '>/dev/null'
or die "- [ERROR] : cannot write to [/dev/null] : $!\n";
defined(my $pid = fork) or die "- [ERROR] : cannot fork : $!\n";
exit if $pid;
setsid or die "- [ERROR] : cannot start a session : $!\n";
}
######################################################################
sub is_running{
if ( -e $pidfile ){
open IN, "<$pidfile";
while (<IN>){
chomp;
$pid = $_;
last;
}
close IN;
`ps p $pid >/dev/null`;
$res = ( $? >> 8 );
return $res;
}
return 1;
}
######################################################################
sub help {
print "\n$appname $version - $copyright
options :
--stop : stop the daemon.
--help : display this menu.
--debug : run in debug mode (verbose, no daemon).
--test : simulation (same as debug but doesn't send packets).
--nopromisc : do not use promiscuous mode.
--interface : network interface.
--stats : display stats
TCP parameters :
--source <IP> : source IP (the victim).
--sport <src_port> : source port (single port '--sport 25', multiports
'--sport 25,80,81' or port range '--sport 25-81').
--destination <IP> : destination IP (your server).
--dport <dest_port> : destination port (single port '--dport 25', multiports
'--dport 25,80,81' or port range '--dport 25-81').
";
exit;
}
######################################################################
sub show_stats {
print "\n$appname $version - $copyright\n\n";
if (! -e $logfile ) {
print "\t[ERROR] : you do not have any log yet !\n\n";
exit 1;
}
my ( $start_date, %jammed );
my $today = 0; my $tot = 0;
$date = `date '+%d/%b/%Y'`;
chomp $date;
open IN, "<$logfile";
while ( <IN> ) {
if ( (/^(\d{2}\/.+?\/\d{4})\s+/) && ( ! $start_date ) ){
$start_date = $1;
} else {
if (/^(\d{2}\/.+?\/\d{4})\s+.+?connection from ([\d.]+):/ ) {
if ( $1 eq $date ) {
$today++;
}
$jammed{$2}++;
$tot++;
}
}
}
close IN;
if ( ! $tot ) {
print "- no jammed IPs yet!\n\n";
exit;
}
my $logsize = (stat( $logfile ))[7];
print "- logfile size : $logsize bytes\n";
print "- total jammed connections since $start_date : $tot\n";
print "- total jammed connections today $date : $today\n";
print "- top 15 script-kiddies :\n\t IPs connections\n";
my $count;
foreach my $jamip ( sort { $jammed{$b} <=> $jammed{$a} } keys %jammed ) {
$count++;
print "\t$jamip";
for ( length( $jamip ) .. 15 ) { print " " };
print " $jammed{$jamip}\n";
last if $count == 15;
}
exit;
}
######################################################################
# EOF
VI - Download :
![]()