HomeFAQStatisticsVariousContact

Security : Linux : Killcx (close a TCP connection under Linux)

Killcx is a Perl script to close a TCP connection under Linux, whatever its state is (half-open, established, waiting or closing state).


Latest version : v1.0.2 (10-Jan-2010)



I - Overview :

In the Windows environment, closing a TCP connection, even from a ring3 application, is quite an easy task (see wKillcx), under Linux, it's a bit more complicated : one needs to sniff the connection and extract the magic Acknowlegment and Sequence numbers from a TCP packet.
Killcx works by creating a fake SYN packet with a bogus SeqNum, spoofing the remote client IP/port and sending it to the server. It will fork a process (child) that will capture the server response (ACK), extract the 2 magic values and use them to send another spoofed packet. The connection will then be closed.


II - Parameters :


  - syntax   : killcx [dest_ip:dest_port] {interface}

    dest_ip              : remote IP
    dest_port            : remote port
    interface (optional) : network interface (eth0, lo etc).

  - example  : killcx 120.121.122.123:1234
               killcx 120.121.122.123:1234 eth0




III - Perl modules needed :

You need the following modules to run killcx :




IV - Various :

- interface : the interface parameter is optional. If not given, killcx will use the first one it can find. Note that in many cases, you will get much better results by using 'lo' (loopback interface), specially if the connection is not yet or no longer in the ESTABLISHED state, for instance SYN_RECV or TIME_WAIT.

- closing connection : killcx will close the connection on both sides (your server and the remote IP) only if it is in the ESTABLISHED state. For all other states, the connection will only be closed on your server side. This doesn't matter too much as if the remote client sent another TCP packet your server would reply with a RST one anyway, except for a SYN of course. Also, you may not always be willing to send a RST to the remote IP, for instance if you are trying to close half-open connections (SYN_RECV) during a SYN flood attack as those IP are probably spoofed.

- verboseness : killcx (both the parent and its child) will ouput all operations to the screen.


V - Source :

#!/usr/bin/perl
######################################################################
# killcx :
#
# Close a TCP connection under Linux.
#
# (c) Jerome Bruandet
#
# version 1.0.2
#
# doc : http://spamcleaner.org/en/misc/killcx.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 qw(:strip);
use NetPacket::TCP;
use POSIX qw(setsid);

my $appname = 'killcx';
my $version = 'v1.0.2';
my $copyright = '(c) 2009 Jerome Bruandet

print "$appname $version - $copyright\n\n";
if ( $> ) {
   print "\t[ERROR] : you must be root\n\n";
   exit 1;
} elsif ( $^O ne 'linux' ){
   print "\t[ERROR] : that script is for Linux only\n\n";
   exit 1;
}
# our child signal handler :
$SIG{USR1} = \&check_res;

my ( $dest_ip, $dest_port ) =
   $ARGV[0] =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)$/;
my $interface = $ARGV[1];

if ( ( ! $dest_ip ) || ( ! $dest_port ) ) {
print "- syntax   : $appname [dest_ip:dest_port] {interface}

  dest_ip              : remote IP
  dest_port            : remote port
  interface (optional) : network interface (eth0, lo etc). Note that
                         in many cases, using 'lo' (loopback) will give
                         better results, specially when a connection
                         is not yet or no longer in the ESTABLISHED state
                         (SYN_RECV, TIME_WAIT etc).

- example  : $appname 10.11.12.13:1234
             $appname 10.11.12.13:1234 eth0

- full doc :  http://spamcleaner.org/en/misc/killcx.html

";
   exit 1;
}

my $pid = $$;

my %TCP_STATES = (
'01' => 'ESTABLISHED', '02' => 'SYN_SENT',  '03' => 'SYN_RECV',
'04' => 'FIN_WAIT1',   '05' => 'FIN_WAIT2', '06' => 'TIME_WAIT',
'07' => 'CLOSE',       '08' => 'CLOSE_WAIT','09' => 'LAST_ACK',
'0A' => 'LISTEN',      '0B' => 'CLOSING'
);

# convert to network byte order :
$dest_ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
my $dest_hex = sprintf "%.2X%.2X%.2X%.2X:%.4X",$4,$3,$2,$1,$dest_port;
# check in /proc/net/tcp :
print "[PARENT] checking connection with [$dest_ip:$dest_port]\n";
my ( $local_ip, $local_port, $state) = &check_tcp( $dest_hex );
if ( ! $state ) {
   print "[PARENT] error : unable to find a connection with ".
      "[$dest_ip:$dest_port]\n\n";
   exit 1;
}

print "[PARENT] found connection with [$local_ip:$local_port] ".
   "($TCP_STATES{$state})\n";

# fork our child which will hook the server response to our spoofed
# packet and extract the correct acknum (and seqnum) needed to close
# the connection :
print "[PARENT] forking child\n";
use POSIX 'WNOHANG';
$SIG{CHLD} = sub { while( waitpid( -1,WNOHANG ) > 0 ) {} };
defined ( my $child_pid = fork ) or
   die "[PARENT] error : cannot fork : $!\n";
if ( $child_pid == 0 ) {
   setsid or die "[CHILD]  error : cannot setid : $!";
   my ($err, $filter, $netmask, $address, $pcap);

   # no interface given, let's try to find one :
   if ( ! $interface ) {
      $interface = Net::Pcap::lookupdev( \$err );
      print "[CHILD]  ";
      if ( $interface ) {
         print "interface not defined, will use [$interface]\n";
      } else {
         # switch to loopback if we can't find any interface :
         print "no interface found, switching to loopback\n";
         $interface = 'lo';
      }
   }
   # let's sniff :
   print "[CHILD]  setting up filter to sniff ACK on [$interface]".
      " for 5 seconds\n";
   my $pcap = Net::Pcap::open_live( $interface, 100, 1, 5000, \$err) ||
      die "[CHILD]  error : open_live failed : $err\n";
   # setup filter :
   Net::Pcap::compile( $pcap, \$filter,
      "(dst port $dest_port) && (src port $local_port)",
      0, $netmask) &&
      die "[CHILD]  error : compile failed : $!\n";
   Net::Pcap::setfilter($pcap, $filter) &&
      die "[CHILD]  error : setfilter failed : $!\n";
   # only want to hook 1 packet :
   Net::Pcap::loop($pcap, 1 , \&process_packet, '');
   Net::Pcap::close($pcap);
   # all done, let's inform our parent :
   print "[CHILD]  all done, sending USR1 signal to parent [$pid] ".
      "and exiting\n";
   `kill -s USR1 $pid`;
   exit 0;
}

# wait 0.5 second for our child to be ready :
select( undef, undef, undef, 0.5 );
print "[PARENT] sending spoofed SYN to [$local_ip:$local_port]".
   " with bogus SeqNum\n";

# send spoofed SYN packet :
my $packet = Net::RawIP->new({
      ip => {  frag_off => 0, tos => 0,
               saddr => $dest_ip, daddr => $local_ip
            },
      tcp =>{  dest => $local_port, source => $dest_port,
               seq => 10, syn => 1
            }
   });
   $packet->send;

# wait max 5 seconds :
select( undef, undef, undef, 5 );

# didn't receive any signal from our child, it has probably failed :
print "[PARENT] no response from child, operation may have failed\n";
if ( $interface ne 'lo' ) {
   print "[PARENT] you may try using 'lo' as {interface} parameter\n";
}
print "[PARENT] killing child [$child_pid] and exiting program\n\n";
# kill it and exit :
kill 9, $child_pid;

exit 1;

#######################################################################

sub check_res {

   # received signal from our child :
   print "[PARENT] received child signal, checking results...\n";
   # check whether the operation was successful or not :
   ( $local_ip, $local_port, $state) = &check_tcp( $dest_hex );
   if ( $state ) {
   print "         => error : connection hasn't been closed\n\n";
      exit 1;
   }
   print "         => success : connection has been closed !\n\n";
   exit 0;

}

######################################################################

sub process_packet {

   my( $user_data, $header, $packet ) = @_;
   my $ether_data = NetPacket::Ethernet::strip($packet);

   # decode TCP/IP packet (server response to our spoofed packet) :
   my $ip = NetPacket::IP->decode($ether_data);
   my $tcp = NetPacket::TCP->decode($ip->{'data'});

   print "[CHILD]  hooked ACK from [$local_ip:$local_port]\n";
   # look for the magic acknum :
   print "[CHILD]  ";
   if ( $tcp->{acknum} ) {
      print "found AckNum [$tcp->{acknum}] and SeqNum ".
         "[$tcp->{seqnum}]\n";
      print "[CHILD]  sending spoofed RST to [$local_ip:$local_port]".
         " with SeqNum [$tcp->{acknum}]\n";
      # we have it : spoof another packet (RST) with the correct seqnum
      # to close the connection :
      my $packet = Net::RawIP->new( {
         ip => {  frag_off => 0, tos => 0,
                  saddr => $dest_ip, daddr => $local_ip
               },
         tcp =>{  dest => $local_port, source => $dest_port,
                  seq => $tcp->{acknum}, rst => 1
               }
      } );
         $packet->send;

      # if the connection was in the ESTABLISHED state we close it
      # with the remote host as well, otherwise we don't care
      # (the server would reply with a RST packet anyway) :
      if ( $state == 1 ) {
         print "[CHILD]  sending RST ".
            "to remote host as well with SeqNum [$tcp->{seqnum}]\n";
         my $packet = Net::RawIP->new( {
         ip => {  frag_off => 0, tos => 0,
                  saddr => $local_ip, daddr => $dest_ip
               },
         tcp =>{  dest => $dest_port, source => $local_port,
                  seq => $tcp->{seqnum}, rst => 1
               }
         } );
         $packet->send;
      }
   } else {
      # very unlikely to happen (ACK packets always have acknum) :
      print "error : no AckNum found in packet\n";
      exit 1;
   }
}

######################################################################

sub check_tcp {

   my $hex_rem = shift;
   my ( $li, $lp, $st );
   open TCP, "</proc/net/tcp";
   while ( <TCP> ) {
      if ( /^\s*\d+:\s+(.{8}):(.{4})\s+$hex_rem\s+(.{2})\s/ ) {
         $st = $3;
         $lp = hex( $2 );
         ($li) = $1 =~ /(.{2})(.{2})(.{2})(.{2})/;
         $li = inet_ntoa( pack("N", hex( $4.$3.$2.$1 ) ) );
         last;
      }
   }
   close TCP;
   # if not found, check /proc/net/tcp6 :
   if ( ( ! $st ) && ( -e '/proc/net/tcp6' ) ) {
      open TCP, "</proc/net/tcp6";
      while ( <TCP> ) {
         if ( /^\s*\d+:\s+\d{16}FFFF0000(.{8}):(.{4})\s+
               \d{16}FFFF0000$hex_rem\s+(.{2})\s/x ) {
            $st = $3;
            $lp = hex( $2 );
            ($li) = $1 =~ /(.{2})(.{2})(.{2})(.{2})/;
            $li = inet_ntoa( pack("N", hex( $4.$3.$2.$1 ) ) );
            last;
         }
      }
      close TCP;
   }
   return ( $li, $lp, $st );

}

######################################################################
# EOF





VI - Download :

    killcx.tgz - v1.0.2