lunes, 23 de agosto de 2010

SniperlCat, un guardian de red

Hoy traigo un script en perl que vigila la red en busca de nuevos hosts o de ataques de ARP spoofing (buscando cambios en la tabla arp). El nombre, Sniperlcat es (obviamente) una mezcla entre snipercat y perl, lenguaje en el que esta programado.

El script se puede descargar aquí [sniperlcat.pl], o al final coloreado.

Simplemente hay que ejecutarlo y avisa de los cambios usando el libnotify de GTK2 (puede que sea necesario instalar las librerias Gtk para perl, libdesktop-notify-perl, o libgtk2-notify-perl ).
Por ejemplo, para un nuevo host en la red:

Si el nuevo host está spoofeado desde otro (si coinciden sus MAC):

Si cambia una MAC:


O si cambia la MAC de uno a través de un ARP spoof:

Hay que tener en cuenta algunas cosas:

  • El script no hace nada para evitar los ataques ARP spoof, solo los detecta
  • Si no se lanza con una configuración, utilizará como tal la tabla ARP en ese momento, si hay entonces un ARP spoof en marcha, lo tomará como el estado base.
Los parámetros aceptados son:

./sniperlcat [-h]|[-d | -v ] [-nf] [-c] [-n <red>] [-f <descriptor de red>] [-s <tiempo>]
-h  (--help): Muestra este mensaje
-d  (--daemonize): Se ejecuta de fondo
-nf (--no-fill): No llena la tabla de hosts (con nmap) antes de leerla
-c  (--cansino): Repite los avisos, aun los ya emitidos, en cada iteración
-v  (--verbose): Muestra más información por pantalla
-n  (--network): Especifica la red donde se ejecuta, por defecto 192.168.1.0/24
-f  (--file): Especifica el archivo de red inicial (se obtiene con arp -an)
-s  (--sleep): Especifica el tiempo en segundos de "descanso" entre iteraciones (por defeto 60)



Creo que solo necesitan explicación un par de cosas:

  • Antes de leer la tabla ARP, para llenarla con todos los host de la red se hace un `nmap $red -sP` , si consideras que no es necesario, usa -nf
  • Si la red es distinta de 192.168.1.0/24 (o 192.168.1.*), la puedes cambiar con -n red, como solo lo usa nmap, la sintaxis es la misma que este.
Hasta otra.

El script [sniperlcat.pl] :

#!/usr/bin/env perl
#
# SniperlCat
#
##############################################################################
#  Copyright (C) 2010 Kenkeiras <kenkeiras (arroba) gmail (punto) com>
#
#  This program is free software. It comes without any warranty, to
#  the extent permitted by applicable law. You can redistribute it
#  and/or modify it under the terms of the Do What The Fuck You Want
#  To Public License, Version 2, as published by Sam Hocevar.
#
#  See http://sam.zoy.org/wtfpl/COPYING for more details.
##############################################################################

my $appname = "Sniperlcat";

$app_icon = "";
$network = "192.168.1.*";
$verbose = 0;
$cansino = 0;

my $go_back = 0;
my $arp_fill = 1;
my $file = "";
my $sltime = 60;

use Gtk2::Notify -init, "Sniperlcat";

# Se va al fondo
sub daemonize{
    $verbose = 0;
    umask 0;
    open STDIN, "</dev/null" || die $!;
    open STDOUT,">>/dev/null" || die $!;
    open STDERR, ">>/dev/null" || die $!;
    defined ($pid=fork) || die $!;
    exit if $pid;
    setsid || die $!;
}

sub show_help{
        print "$appname\n";
        print "./sniperlcat [-h]|[-d | -v ] [-nf] [-c] [-n <red>] [-f <descriptor de red>] [-s <tiempo>]\n";
        print "-h  (--help): Muestra este mensaje\n";
        print "-d  (--daemonize): Se ejecuta de fondo\n";
        print "-nf (--no-fill): No llena la tabla de hosts (con nmap) antes de leerla\n";
        print "-c  (--cansino): Repite los avisos, aun los ya emitidos, en cada iteración\n";
        print "-v  (--verbose): Muestra más información por pantalla\n";
        print "-n  (--network): Especifica la red donde se ejecuta, por defecto 192.168.1.0/24\n";
        print "-f  (--file): Especifica el archivo de red inicial (se obtiene con arp -an)\n";  
        print "-s  (--sleep): Especifica el tiempo en segundos de \"descanso\" entre iteraciones (por defeto 60)\n";  
}

# Comprueba los parámetros

my $i = 0;
while ($i <= $#ARGV){
    if (($ARGV[$i] eq "-d") || ($ARGV[$i] eq "--daemonize")){
        $go_back = 1;
    }
    elsif (($ARGV[$i] eq "-h") || ($ARGV[$i] eq "--help")){
        show_help;
        exit 0;
    }
    elsif (($ARGV[$i] eq "-v") || ($ARGV[$i] eq "--verbose")){
        $verbose = 1;
    }
    elsif (($ARGV[$i] eq "-c") || ($ARGV[$i] eq "--cansino")){
        $cansino = 1;
    }
    elsif (($ARGV[$i] eq "-nf") || ($ARGV[$i] eq "--no-fill")){
        $arp_fill = 0;
    }
    elsif (($ARGV[$i] eq "-n") || ($ARGV[$i] eq "--network")){
        $i++;
        if ($i > $#ARGV){
            print "No se ha especificado la red\n";
            show_help;
            exit 1;
        }
        $network = $ARGV[$i];
    }
    elsif (($ARGV[$i] eq "-f") || ($ARGV[$i] eq "--file")){
        $i++;
        if ($i > $#ARGV){
            print "No se ha especificado el archivo de red\n";
            show_help;
            exit 1;
        }
        $file = $ARGV[$i];
    }
    elsif (($ARGV[$i] eq "-s") || ($ARGV[$i] eq "--sleep")){
        $i++;
        if ($i > $#ARGV){
            print "No se ha especificado el tiempo\n";
            show_help;
            exit 1;
        }
        $sltime = $ARGV[$i];
    }
    $i++;
}

daemonize if $go_back;

# LLena la tabla arp con nmap
sub fill_arp_table{
    `nmap $network -sP 2>/dev/null 1>/dev/null`;
}

# Carga la tabla arp de un archivo
sub load_arp_desc{
    my %tmplist = ();
    my $arp = $_[0];
    my @lines = split(/\n/,$arp);
    my $ip,$mac,$i = 0;
    my $max = @lines;

    while ($i < $max){
        # Extrae la IP
        @line = split(/ /,@lines[$i]);
        @ip = split(/\(/,$line[1]);
        @ip = split(/\)/,@ip[1]);
        $ip = @ip[0];


        # Y la MAC
        $mac = $line[3];

        # Y se introduce en la lista si es una MAC válida
        if (substr("$mac", 0, 1) ne "<"){
            $tmplist{"$ip"} = "$mac";
        }
        $i++;
    }
    return %tmplist;
}

# Carga la tabla arp
sub load_arp_list{
    my $arp = `arp -an`;
    return load_arp_desc($arp);
}

# Hace las comprobaciones
sub check_list{
    my $ip_list = $_[0];
    my $tmplist = $_[1];
    my $lastlist = $_[2];

    foreach my $ip (keys %$tmplist){
        my $mac = $tmplist->{$ip};
        # Si es un host nuevo
        if (!exists $ip_list->{$ip} ) {
            if ((!exists $lastlist->{$ip}) || ($cansino)){
                my $message = "Equipo desconocido en la red: $ip [$mac]";
                if ($mac ne "00:00:00:00:00"){ # Se suele utilizar para tapar
                                                                  # despues de arp spoofing.
                                                                  # No aporta nada
                    # Si la MAC está repetida, probablemente haya spoofing
                    foreach my $tmpip (keys %$ip_list){
                        if (($ip_list->{$tmpip} eq $mac) && ($tmpip ne $ip)){
                            $message .= ", posiblemente spoofeado desde $tmpip";
                        }
                    }
                }
                print "$message\n" if $verbose;
                my $notification = Gtk2::Notify->new("$appname", "$message","$app_icon");
                $notification->show();
            }
        }
        else{
            # Si cambio la MAC
            if ($ip_list->{$ip} ne $mac){
                if (($lastlist->{$ip} ne $mac)||($cansino)){
                    my $message = "La MAC de $ip ha cambiado de [".$lastlist->{$ip}."] a [".$mac."]";
                    if ($mac ne "00:00:00:00:00"){ # Se suele utilizar para tapar
                                                   # despues de arp spoofing.
                                                   # No aporta nada
                        # Si la MAC está repetida, probablemente haya spoofing
                        foreach my $tmpip (keys %$ip_list){
                            if (($ip_list->{$tmpip} eq $mac) && ($tmpip ne $ip)){
                                $message .= ", posiblemente spoofeado desde $tmpip";
                            }
                        }
                    }
                    print "$message\n" if $verbose;
                    my $notification = Gtk2::Notify->new("$appname", "$message","$app_icon");
                    $notification->show();
                }
            }
        }
    }
}

my %ip_list;
if ($file eq ""){
    if ($arp_fill){
        print STDERR "LLenando lista arp... " if $verbose;
        fill_arp_table;
        print STDERR "[OK]\n" if $verbose;
    }
    print STDERR "Leyendo tabla arp... " if $verbose;
    %ip_list = load_arp_list;
}
else{
    local $/=undef;
    open MYFILE, "$file" or die "Couldn't open file: $!";
    binmode MYFILE;
    $arp = <MYFILE>;
    close MYFILE;
    %ip_list = load_arp_desc("$arp");
}
print STDERR "[OK]\n" if $verbose;

my $lastlist = \%ip_list;
while (1){
    if ($arp_fill){
        fill_arp_table;
    }
    my %tmplist = load_arp_list;
    check_list(\%ip_list,\%tmplist,$lastlist);
    $lastlist = \%tmplist;

    sleep $sltime;
}

No hay comentarios:

Publicar un comentario