next up previous contents index
suivant: À propos de ce monter: 3 Élaboration d'un Outil précédent: 3.9 IDS et les   Table des matières   Index

3.10 Programme d'Exemple (scanlogd)

Le code source suivant est celui de scanlogd pour Linux écrit par SOLAR DESIGNER <solar@false.com>. Il peut-être compilé sur d'autres systèmes, mais il est possible qu'il ne fonctionne pas car il n'y a pas d'accès aux sockets TCP brut sur tout les systèmes. Pour les versions futures voir http://www.openwall.com/scanlogd/.

/*

 * Linux scanlogd v1.3 by Solar Designer. You're allowed to do whatever you

 * like with this software (including re-distribution in any form, with or

 * without modification), provided that credit is given where it is due, and

 * any modified versions are marked as such. There's absolutely no warranty.

 */

 

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

#include <string.h>

#include <ctype.h>

#include <time.h>

#include <syslog.h>

#include <sys/times.h>

#include <sys/types.h>

#include <sys/socket.h>

#define __BSD_SOURCE

#define __FAVOR_BSD

#include <netinet/in_systm.h>

#include <netinet/in.h>

#include <netinet/ip.h>

#include <netinet/tcp.h>

#include <arpa/inet.h>

/* 

 * Seuil de détection d'un port scan: au moins COUNT ports doivent être 

 * scannés venant de la même adresse, sans plus de DELAY entre chaque ports. 

 */

#define SCAN_COUNT_THRESHOLD            10

#define SCAN_DELAY_THRESHOLD            (CLK_TCK * 3)

 

/*

 * Seuil de détection d'un flood de log: arrêter temporairement de loguer

 * si plus de COUNT port scans ont été détectés sans plus de DELAY netre chaque

 */

#define LOG_COUNT_THRESHOLD             5

#define LOG_DELAY_THRESHOLD             (CLK_TCK * 20)

 

/*

 * Vous pouvez modifier ses valeurs pour les logs

 */

#define SYSLOG_IDENT                    "scanlogd"

#define SYSLOG_FACILITY                 LOG_DAEMON

#define SYSLOG_LEVEL                    LOG_ALERT

 

/*

 * Garder jusqu'à LIST_SIZE adresses source, en utilisant une table de hachage

 * de HASH_SIZE enregistrements pour une recherche plus rapide, mais en limitant

 * les collisions jusqu'à HASH_MAX adresses source pour une même valeur de hachage

 */

#define LIST_SIZE                       0x400

#define HASH_LOG                        11

#define HASH_SIZE                       (1 << HASH_LOG)

#define HASH_MAX                        0x10

 

/*

 * L'entête d'un paquet est lu à partir de la socket TCP brute. En réalité,

 * l'entête TCP peut se trouver à un offset diffèrent; ceci simplement pour

 * obtenir la taille totale correct.

struct header {

        struct ip ip;

        struct tcphdr tcp;

        char space[60 - sizeof(struct ip)];

};

 

/*

 * Information que l'on conserve pour chaque adresses source.

 */

struct host {

        struct host *next;              /* Prochain enregistrement avec la même valeur de hachage */

        clock_t timestamp;              /* Dernière date de mise à jour */

        time_t start;                   /* Date de la création de l'enregistrement */

        struct in_addr saddr, daddr;    /* Adresses source et de destination */

        unsigned short sport;           /* Port source, s'il est fixe */

        int count;                      /* Nombre de ports dans la liste */

        unsigned short ports[SCAN_COUNT_THRESHOLD - 1]; /* Liste des ports*/

        short tos;                      /* TOS (Type Of Service), si fixe*/

        unsigned char ttl;              /* TTL, si fixe*/

        unsigned char flags_or;         /* Masque OR sur TCP flags */

        unsigned char flags_and;        /* Masque AND sur TCP flags */

};

 

/*

 * Information de statut.

 */

struct {

        struct host list[LIST_SIZE];    /* List of source addresses */

        struct host *hash[HASH_SIZE];   /* Hash: pointers into the list */

        int index;                      /* Oldest entry to be replaced */

} state;

 

/*

 * Convertit une adresse IP en un index de table de hachage.

 */

int hashfunc(struct in_addr addr)

{

        unsigned int value;

        int hash;

 

        value = addr.s_addr;

        hash = 0;

        do {

                hash ^= value;

        } while ((value >>= HASH_LOG));

 

        return hash & (HASH_SIZE - 1);

}

 

/*

 * Log ce port scan.

 */

void do_log(struct host *info)

{

        char s_saddr[32];

        char s_daddr[32 + 8 * SCAN_COUNT_THRESHOLD];

        char s_flags[8];

        char s_tos[16];

        char s_ttl[16];

        char s_time[32];

        int index, size;

        unsigned char mask;

 

/* Adresse source et numéro du port, s'ils sont fixes */

        snprintf(s_saddr, sizeof(s_saddr),

                info->sport ? "%s:%u" : "%s",

                inet_ntoa(info->saddr),

                (unsigned int)ntohs(info->sport));

 

/* Adresse de destination, si elle est fixe */

        snprintf(s_daddr, sizeof(s_daddr),

                info->daddr.s_addr ? "%s ports " : "ports ",

                inet_ntoa(info->daddr));

 

/* Numéros des ports scannés */

        for (index = 0; index < info->count; index++) {

                size = strlen(s_daddr);

                snprintf(s_daddr + size, sizeof(s_daddr) - size,

                        "%u, ", (unsigned int)ntohs(info->ports[index]));

        }

 

/* Flags TCP : minuscules pour "always clear", majuscules pour "always

 * set", et points d'interrogation pour "sometimes set". */

        for (index = 0; index < 6; index++) {

                mask = 1 << index;

                if ((info->flags_or & mask) == (info->flags_and & mask)) {

                        s_flags[index] = "fsrpau"[index];

                        if (info->flags_or & mask)

                                s_flags[index] = toupper(s_flags[index]);

                } else

                        s_flags[index] = '?';

        }

        s_flags[index] = 0;

 

/* TOS, si fixe */

        snprintf(s_tos, sizeof(s_tos), info->tos != -1 ? ", TOS %02x" : "",

                (unsigned int)info->tos);

 

/* TTL, si fixe */

        snprintf(s_ttl, sizeof(s_ttl), info->ttl ? ", TTL %u" : "",

                (unsigned int)info->ttl);

 

/* Date de début du scan */

        strftime(s_time, sizeof(s_time), "%X", localtime(&info->start));

 

/* On log le tout */

        syslog(SYSLOG_LEVEL,

                "From %s to %s..., flags %s%s%s, started at %s",

                s_saddr, s_daddr, s_flags, s_tos, s_ttl, s_time);

}

 

/*

 * Log ce port scan avant que nous soyons floodé.

 */

void safe_log(struct host *info)

{

        static clock_t last = 0;

        static int count = 0;

        clock_t now;

 

        now = info->timestamp;

        if (now - last > LOG_DELAY_THRESHOLD || now < last) count = 0;

        if (++count <= LOG_COUNT_THRESHOLD + 1) last = now;

 

        if (count <= LOG_COUNT_THRESHOLD) {

                do_log(info);

        } else if (count == LOG_COUNT_THRESHOLD + 1) {

                syslog(SYSLOG_LEVEL, "More possible port scans follow.\n");

        }

}

 

/*

 * Traite un paquet TCP.

 */

void process_packet(struct header *packet, int size)

{

        struct ip *ip;

        struct tcphdr *tcp;

        struct in_addr addr;

        unsigned short port;

        unsigned char flags;

        struct tms buf;

        clock_t now;

        struct host *current, *last, **head;

        int hash, index, count;

 

/* Obtient l'entête IP et TCP */

        ip = &packet->ip;

        tcp = (struct tcphdr *)((char *)packet + ((int)ip->ip_hl << 2));

 

/* Vérifie la taille du paquet*/

        if ((char *)tcp + sizeof(struct tcphdr) > (char *)packet + size)

                return;

 

/* Obtient l'adresse source, le port de destination et les falgs TCP */

        addr = ip->ip_src;

        port = tcp->th_dport;

        flags = tcp->th_flags;

 

/* Nous utilisons l'adresse IP 0.0.0.0 dasn un but spécial, ne nous laissons pas spoofer

 */

        if (!addr.s_addr) return;

 

/* Utilisons times(2) pour ne pas dépendre d'un changement de l'heure pas quelqu'un d'autre 

 * pendant que le programme s'exécute; nous devons faire attention à une possible valeur de 

 * retour trop grande.

 */

        now = times(&buf);

 

/* Est-ce que l'on connaît déjà cette adresse source ? */

        count = 0;

        last = NULL;

        if ((current = *(head = &state.hash[hash = hashfunc(addr)])))

        do {

                if (current->saddr.s_addr == addr.s_addr) break;

                count++;

                if (current->next) last = current;

        } while ((current = current->next));

 

/* Nous connaissons déjà cette adresse, et l'entrée n'est pas trop veille. Mettons la à jour. */

        if (current)

        if (now - current->timestamp <= SCAN_DELAY_THRESHOLD &&

            now >= current->timestamp) {

/* Mettons seulement à jour les flags TCP si nous avons déjà vu ce port. */

                for (index = 0; index < current->count; index++)

                if (current->ports[index] == port) {

                        current->flags_or |= flags;

                        current->flags_and &= flags;

                        return;

                }

 

/* ACK et/ou RST sur un nouveau port ? C'est peut-être une connexion sortante. */

                if (flags & (TH_ACK | TH_RST)) return;

 

/* Paquet sur un nouveau port, et pas de ACK: on met à jour la date */

                current->timestamp = now;

 

/* Ce scan a déjà été logué ? Alors on sort. */

                if (current->count == SCAN_COUNT_THRESHOLD) return;

 

/* Met à jour les flags TCP */

                current->flags_or |= flags;

                current->flags_and &= flags;

 

/* Oublie l'adresse de destination, le port source, et le TOS/ID/TTL s'ils ne sont pas fixés. */

                if (current->daddr.s_addr != ip->ip_dst.s_addr)

                        current->daddr.s_addr = 0;

                if (current->sport != tcp->th_sport)

                        current->sport = 0;

                if (current->tos != ip->ip_tos)

                        current->tos = -1;

                if (current->ttl != ip->ip_ttl)

                        current->ttl = 0;

 

/* Avons nous suffisament de ports de destination pour dire qu'il s'agit d'un port scan ? Alors on le log. */

                if (current->count == SCAN_COUNT_THRESHOLD - 1) {

                        safe_log(current);

                        current->count++;

                        return;

                }

 

/* Se souvenir du nouveau port. */

                current->ports[current->count++] = port;

 

                return;

        }

 

/* Nous connaisons cette adresse, mais l'entrée est périmée. On la marque

 * 'non utilisée', et on la supprime de la table de hachage. Nous allouons

 * une nouvelle entrée au lieu d'être réutilisée trop tôt.

 */

        if (current) {

                current->saddr.s_addr = 0;

 

                if (last)

                        last->next = last->next->next;

                else if (*head)

                        *head = (*head)->next;

                last = NULL;

        }

 

/* Nous n'avons pas besoin d'un ACK venat d'une nouvelle adresse source */

        if (flags & TH_ACK) return;

/* Nous avons trop d'adresses source avec la même valeur hachage? Alors on

 * supprime les plus ancienne de la table de hachage, de cette façon elles

 * ne consommerons pas trop de temps CPU même avec adresses IP habilements 

 * spoofées.

 */

        if (count >= HASH_MAX && last) last->next = NULL;

 

/* Nous allons réutiliser la liste d'entrées la plus ancienne, on la supprime 

 * donc d'abord de la table de hachage (si elle est déjà utilisée, et n'a pas 

 * déjà était supprimée de la table de hachage car HASH_MAX n'était pas encore 

 * atteinte). */

 

/* Recherchons la d'abord. */

        if (state.list[state.index].saddr.s_addr)

                head = &state.hash[hashfunc(state.list[state.index].saddr)];

        else

                head = &last;

        last = NULL;

        if ((current = *head))

        do {

                if (current == &state.list[state.index]) break;

                last = current;

        } while ((current = current->next));

 

/* Puis supprimons la. */

        if (current) {

                if (last)

                        last->next = last->next->next;

                else if (*head)

                        *head = (*head)->next;

        }

 

/* Obtenons notre entrée dans la liste. */

        current = &state.list[state.index++];

        if (state.index >= LIST_SIZE) state.index = 0;

 

/* Lions la dans la table de hachage. */

        head = &state.hash[hash];

        current->next = *head;

        *head = current;

 

/* Et remplissons les champs. */

        current->timestamp = now;

        current->start = time(NULL);

        current->saddr = addr;

        current->daddr = ip->ip_dst;

        current->sport = tcp->th_sport;

        current->count = 1;

        current->ports[0] = port;

        current->tos = ip->ip_tos;

        current->ttl = ip->ip_ttl;

        current->flags_or = current->flags_and = flags;

}

 

/*

 * MAIN

 */

int main()

{

        int raw, size;

        struct header packet;

 

/* Obtenons l'accès à la socket brute. Nous pourrons quitter les droits du root juste après çà. */

        if ((raw = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) {

                perror("socket");

                return 1;

        }

 

/* Devenir un démon */

        switch (fork()) {

        case -1:

                perror("fork");

                return 1;

 

        case 0:

                break;

 

        default:

                return 0;

        }

 

        chdir("/");

        signal(SIGHUP, SIG_IGN);

 

/* Initialise le status. Toutes les adresses IP source sont fixées à 0.0.0.0,

 * ce qui veut dire que les entrées de la liste ne sont pas encore utilisées.

 */

        memset(&state, 0, sizeof(state));

 

/* Ouverture du fichier de log*/

        openlog(SYSLOG_IDENT, 0, SYSLOG_FACILITY);

 

/* On commence */

        while (1)

        if ((size = read(raw, &packet, sizeof(packet))) >= sizeof(packet.ip))

                process_packet(&packet, size);

}


next up previous contents index
suivant: À propos de ce monter: 3 Élaboration d'un Outil précédent: 3.9 IDS et les   Table des matières   Index
Nicolas JUSTIN - nicolas.justin@free.fr - 17/02/2000