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);
}