Open Source im professionellen Einsatz
Linux-Magazin 02/2007
© photocase.com/Pikar

© photocase.com/Pikar

Daemon enttarnt unerwünschte Geräteadressen

Licht ins Dunkel

Im Dunkeln ist gut munkeln - doch damit ist jetzt Schluss: Der hier vorgestellte Perl-Daemon holt verborgene Vorgänge im Netz ans Licht und schlägt Alarm, wenn ihm etwas verdächtig vorkommt.

1161

Was sich unter der Decke des LAN vollzieht, ist für den Anwender normalerweise unsichtbar. Zu den Vorgängen im Verborgenen zählt beispielsweise die Adressierung der Pakete auf ihrer letzten Etappe - inklusive der Ermittlung der nötigen MAC- auf Grundlage der IP-Adressen, für die ARP (Address Resolution Protocol) zuständig ist. Dieser schwer einsehbare Bereich bietet auch Platz für mancherlei fiese Tricks, mit deren Hilfe sich unter anderem ungebetene Gäste einschmuggeln könnten [1].

Zur Abwehr rüstet der Admin mit Werkzeugen auf, die Licht ins Dunkel bringen. Eines davon, Arpalert [2], hat mein Kolumnistenkollege Charly Kühnast vor einiger Zeit vorgestellt [3]. Der Daemon überwacht ARP-Anfragen und vergleicht deren Adressen mit einer Whitelist. Unbekannte MAC-Adressen lösen einen Alarm aus. Allerdings kommt es dabei zuweilen zu Doppel-Alarmen für ein und denselben Vorfall und die beiliegende Dokumentation (teils auf Französisch) ist grottenschlecht.

Schnüffler im Eigenbau

Dank der beiden Module Net::Pcap und NetPacket::Ethernet vom CPAN ist es aber überhaupt nicht schwer, mit einem eigenen Perl-Skript die MAC-Adressen von Paketen herauszufischen, die im LAN herumschwimmen. Und mit Rose, dem kürzlich in einem Snapshot vorgestellten objektorientierten Datenbankmapper [4], lassen sich diese Werte in eine MySQL-Datenbank mit Inno-DB-Tabellen einlesen, in der das Skript später nach Herzenslust stöbern kann.

Um zum Beispiel festzustellen, welche Geräte während der letzten 24 Stunden im LAN aktiv waren, genügt dann einfach ein Aufruf des gegen Ende des Artikels vorgestellten Skripts »lastaccess« (Listing 6). Das Ergebnis zeigt der Screenshot in Abbildung 1 - doch dazu später mehr.

Abbildung 1: Das Skript »lastaccess« listet genau auf, welche Geräte in den vergangenen 24 Stunden im LAN aktiv waren.

Ähnlich wie der einmal an dieser Stelle besprochene grafische Netzwerkschnüffler »capture« [5] schaltet das Skript »arpcollect« in Listing 1 die erste Netzwerkkarte des Rechners in den Promiscuous-Mode. So schnappt sie nicht nur die für den eigenen Rechner bestimmten Pakete, sondern leitet alle, die sie findet, an das Schnüffelskript weiter. Dazu sind Root-Rechte erforderlich, auf die Zeile 9 prüft. Ohne passende Berechtigungen bricht das Skript ab.

Listing 1:
»arpcollect«

01 #!/usr/bin/perl -w
02 use strict;
03 use Net::Pcap;
04 use NetPacket::IP;
05 use NetPacket::Ethernet;
06 use Socket;
07 use WatchLAN;
08 
09 die "You need to be root to run this.n"
10  if $> != 0;
11 
12 my ( $err, $netaddr, $netmask );
13 my $dev = Net::Pcap::lookupdev( $err );
14 
15 Net::Pcap::lookupnet($dev, $netaddr,
16            $netmask, $err) and
17   die "lookupnet $dev failed ($!)";
18 
19 my $object =
20  Net::Pcap::open_live( $dev, 1024, 1,
21             -1, $err );
22 
23 my $db = WatchLAN->new();
24 
25 Net::Pcap::loop( $object, -1, &callback,
26  [ $netaddr, $netmask ] );
27 
28 ###########################################
29 sub callback {
30 ###########################################
31  my ($user_data, $hdr, $raw_packet) = @_;
32 
33  my ($netaddr, $netmask) = @$user_data;
34 
35  my $packet = NetPacket::Ethernet->
36            decode($raw_packet);
37 
38  my $src_mac = $packet->{src_mac};
39   # Add separating colons
40  $src_mac =~ s/(..)(?!$)/$1:/g;
41 
42  my $edata =
43   NetPacket::Ethernet::strip($raw_packet);
44 
45  my $ip = NetPacket::IP->decode($edata);
46 
47   # Package coming from local network?
48  if ((inet_aton( $ip->{src_ip} ) &
49     pack( 'N', $netmask )
50    ) eq pack( 'N', $netaddr )) {
51   $db->event_add( $src_mac,
52           $ip->{src_ip} );
53  }
54 }

Die in Zeile 13 aufgerufene Funktion »lookupdev()« gibt den Namen eines verfügbaren Netzwerkdevice zurück. Bei nur einer angeschlossenen Netzwerkkarte ist das »eth0«. Das anschließende »open_live()« tritt in eine Endlosschleife ein (der Timeout wurde mit »-1« abgeschaltet), in der es jeweils die ersten 1024 Bytes jedes eintreffenden Pakets liest und sofort die Callback-Funktion »callback()« aufruft. Diese erhält nicht nur die vorher ermittelte lokale Netzwerkadresse und -maske als », sondern außerdem die rohen Paketdaten in ».

Das Modul NetPacket::Ethernet dekodiert diesen Ethernet-Frame und stellt unter dem Hash-Key »src_mac« die MAC-Adresse des Senders im Hexformat bereit. Sie enthält noch nicht die typischen Doppelpunkte nach jedem zweiten Zeichen, Zeile 40 setzt diese Trenner mit einem regulären Ausdruck ein.

In den Zeilen 48 bis 50 prüft »arpcollect« anhand der IP-Adresse, ob das Paket von einem Gerät im lokalen Netz stammt. Die Adresse ermittelt es anhand der Nutzdaten des Ethernet-Pakets, die Funktion »strip()« des Moduls NetPacket::Ethernet extrahiert sie. Das Ergebnis ist das rohe IP-Paket, das die Funktion »decode()« des Moduls NetPacket::IP weiter entpackt.

Schließlich und endlich findet sich unter dem Hash-Schlüssel »src_ip« die IP-Adresse des Absenders. Falls ein Bit-weises UND der IP-Adresse mit der Netzwerkadresse wieder die Netzwerkadresse ergibt, wurde das Paket von einem Gerät im lokalen Netzwerk versandt und ist daher für die Weiterverarbeitung relevant. Die Methode »event_add()« des zuvor instantiierten Datenbankobjekts vom Typ »WatchLAN« nimmt sowohl die IP- als auch die MAC-Adresse entgegen und pumpt sie zur späteren Analyse in die Datenbank.

Minutenpuffer

Das Modul »WatchLAN.pm« (Listing 3) implementiert die Speicherungsschicht. Jedes Paket sofort in der Datenbank abzulegen wäre nämlich nicht effektiv, weil damit selbst auf einem nur wenig ausgelasteten Netzwerk mehrere Schreibzugriffe pro Sekunde fällig würden. Außerdem beanspruchen mehrere Millionen Tabellenzeilen viel Plattenplatz und Rechen-Ressourcen.

Listing 2:
»dbinit«

01 #!/bin/sh
02 DBNAME=watchlan
03 mysqladmin -f -uroot drop $DBNAME
04 mysqladmin -uroot create $DBNAME
05 mysql -uroot $DBNAME <sql.txt

Listing 3:
»WatchLAN.pm«

01 ###########################################
02 package WatchLAN;
03 ###########################################
04 use strict;
05 use Apache::DBI;   # share a single DB conn
06 use Rose::DB::Object::Loader;
07 use Log::Log4perl qw(:easy);
08 use DateTime;
09 
10 my $loader = Rose::DB::Object::Loader->new(
11   db_dsn => 'dbi:mysql:dbname=watchlan',
12   db_username  => 'root',
13   db_password  => undef,
14   db_options   => {
15     AutoCommit => 1, RaiseError => 1 },
16   class_prefix => 'WatchLAN'
17 );
18 
19 $loader->make_classes();
20 
21 ###########################################
22 sub new {
23 ###########################################
24   my ($class) = @_;
25 
26   my $self = {
27     cache          => {},
28     flush_interval => 60,
29     next_update    => undef,
30   };
31 
32   bless $self, $class;
33   $self->cache_flush();
34 
35   return $self;
36 }
37 
38 ###########################################
39 sub event_add {
40 ###########################################
41   my($self, $mac, $ip)= @_;
42 
43   $self->{cache}->{"$mac,$ip"}++;
44   $self->cache_flush()
45     if time() > $self->{next_update};
46 }
47 
48 ###########################################
49 sub cache_flush {
50 ###########################################
51   my ($self) = @_;
52 
53   for my $key ( keys %{ $self->{cache} } ){
54     my ($mac, $ip) = split /,/, $key;
55     my $counter = $self->{cache}->{$key};
56 
57     my $minute = DateTime->from_epoch(
58       epoch => $self->{next_update} -
59                $self->{flush_interval},
60       time_zone => "local",
61     );
62 
63     my $activity = WatchLAN::Activity->new(
64               minute => $minute);
65 
66     $activity->device(
67       { mac_address => $mac } );
68     $activity->ip_address(
69       { string => $ip } );
70     $activity->counter($counter);
71     $activity->save();
72   }
73 
74   $self->{cache} = {};
75   $self->{next_update} = time() -
76     ( time() % $self->{flush_interval} ) +
77     $self->{flush_interval};
78 }
79 
80 ###########################################
81 sub device_add {
82 ###########################################
83   my ( $self, $name, $mac_address ) = @_;
84 
85   my $device = WatchLAN::Device->new(
86     mac_address => $mac_address );
87   $device->load( speculative => 1 );
88   $device->name($name);
89   $device->save();
90 }
91 
92 1;

Aus diesem Grund speichert »WatchLAN.pm« die ankommenden Paketadressen zunächst in einem temporären Hash, dessen Inhalt es jeweils zur vollen Minute an die Datenbank überträgt. Ein Zähler wird mit jeder IP/MAC-Kombination um 1 erhöht und mit »cache_flush« später in der Datenbanktabelle »activity« in der Spalte »counter« abgelegt. Der Parameter »flush_interval« im »WatchLAN«-Konstruktor bestimmt, wie oft diese Spülung zu erfolgen hat. Aus der aktuellen Zeit und »flush_interval« berechnet sich der Zeitpunkt des nächsten Spülvorgangs. Er landet in der Instanzvariablen »next_update«.

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Kern-Technik

    Wer per Konsole Dateien manipuliert, erwartet auch, dass das korrespondierende Konqueror-Fenster daneben die mitbekommt. Konqueror pollt dazu nicht das Verzeichnis, sondern setzt das Filemonitoring des Kernels ein. Besonders effizient geht das mit dem neuen Inotify.

  • What about sex?
  • Perl-Snapshot

    Damit ein Anwender schnell über womöglich ungewollte Zu- und Abgänge in seinem Netzwerk informiert wird, speichert ein Perl-Daemon periodisch die Daten von Nmap-Scans und gibt sie über ein eingebautes Webinterface an Nagios weiter.

  • Tierische Kurssprünge

    Ein in Perl geschriebenes Pidgin-Plugin ignoriert dröge Börsentage, an denen die Kurse stillstehen, aber schlägt per Instant Message Alarm, falls die Aktien anfangen Achterbahn zu fahren.

  • Kleiner Lauschangriff

    Ist auf der heimischen Telefonnummer mal wieder kein Durchkommen, lauscht ein Skript an der Leitung und signalisiert dem Wartenden via Web, wann er erneut anrufen kann.

comments powered by Disqus

Ausgabe 06/2017

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.