3 mai 2008

Coder un sniffer sous Linux

Cela faisait un moment que je voulais me coder un petit sniffer basique sous Gnu/Linux. C'est chose faite avec le petit code suivant.
Il reconnaît pour l'instant les trames TCP, UDP et ARP mais est simple à faire évoluer.
Le principe est simple, on ouvre une raw socket (SOCK_RAW) puis on la passe en mode promiscuous ("accepter toutes les données qui passent par l'interface réseau choisie"). Ensuite, plus qu'à lire paquet par paquet, puis parser les headers pour déterminer le protocole auquel nous avons à faire.

Voici le code. (Fichier .c)

/**
* @File sniffer.c
* @brief sniff network traffic
*
* @author Hth http://hatch-the-hitch.blogspot.com
* @date samedi 3 mai 2008, 00:31:21 (UTC+0200)
*
* version : <0.1 alpha
*
* gcc -o sniff sniffer.c -pedantic
*
*/

/* std */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* usual networking */
#include <sys/socket.h>
#include <sys/types.h>
/* low level networking */
#include <sys/ioctl.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
/* headers paquets */
#include <netinet/in.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>



typedef struct {
char * ifaceName;
int sock;
unsigned long max_recv;
struct sockaddr_ll device;
} param_t;


/* --- PROTOTYPES --- */
char argumentManagement ( int argc, char **argv, param_t * parameters );
void initParams ( param_t * parameters );
char checkParams ( param_t * parameters );
void usage ( void );
char initSniff ( param_t * parameters );
void loop ( param_t * parameters );
int getPacketType ( unsigned char * packet );
void printOutTCP ( unsigned char * packet );
void printOutUDP ( unsigned char * packet );
/* ------------------ */


int main ( int argc, char **argv )
{
param_t parameters;

/* Parse and check command line */
if (argumentManagement(argc, argv, &parameters) < 0)
{
usage();
exit(EXIT_FAILURE);
}
else
{
/* Prepare system for sniffing */
if ( initSniff(&parameters) < 0 )
{
fprintf(stderr, "Can't prepare to sniff!\n");
exit(EXIT_FAILURE);
}
else
{
/* Sniffing loop : May never end */
loop(&parameters);
}
}

return 0;
}


char argumentManagement ( int argc, char **argv, param_t * parameters )
{
int opt;

while ( (opt = getopt(argc, argv, "hc:")) != -1 )
{
switch(opt)
{
/* exit after a specified number of packets received */
case 'c' : parameters->max_recv = atol(optarg);
break;

case 'h' :
case '?' :
default : return -1;
}
}
/* Non option arguments */
/* IFace name */
if (argv[optind] == NULL)
return -2;
else
parameters->ifaceName = argv[optind];

/* Checkif required parameters have been given etc... */
return checkParams(parameters);
}


void initParams ( param_t * parameters )
{
parameters->ifaceName = NULL;
parameters->sock = -1;
parameters->max_recv = 0;
memset(&parameters->device, 0x00, sizeof(struct sockaddr_ll));
}


char checkParams ( param_t * parameters )
{
/* Check if required arguments have been specified */
if ( (parameters->ifaceName == NULL) )
{
fprintf(stderr, "No interface selected!\n");
return -1;
}
else
return 0;
}


void usage ( void )
{
fprintf(stderr, "Usage : ./sniff [options] [interface]\n");
fprintf(stderr, "options\n");
fprintf(stderr, "\t-c [count]\t: stop after [count] packets received\n");
fprintf(stderr, "\t[interface]\t: network iface from which to sniff (required!)\n");
}


char initSniff ( param_t * parameters )
{
struct ifreq ifr;

/* Open raw socket, exit on failure */
parameters->sock = openRawSock();
/* Folowing instructions set selected iface into promisc mode */
strncpy(ifr.ifr_name, parameters->ifaceName, IFNAMSIZ-1);

if ( ioctl(parameters->sock, SIOCGIFFLAGS, &ifr) < 0 )
{
close(parameters->sock);
perror("Flags");
return -1;
}
ifr.ifr_flags |= IFF_PROMISC;

if ( ioctl(parameters->sock, SIOCSIFFLAGS, &ifr) < 0 )
{
perror("Can't turn into promiscuous mode");
close(parameters->sock);
return -2;
}
/* --- -- --- */
return 0;

}


void loop ( param_t * parameters )
{
unsigned long nb_recv;
unsigned char packet[4096];

/* This loop will never end if no max_recv specified */
for (nb_recv=1; ((nb_recv <= parameters->max_recv) || !(parameters->max_recv)); nb_recv++)
{
/* recv a packet */
read(parameters->sock, (char *)&packet, 4096);

fprintf(stdout, "\n--] Packet #%lu\n", nb_recv);
/* Add protocols and parsing functions here */
switch(getPacketType(packet))
{
case IPPROTO_TCP : printOutTCP(packet);
break;

case IPPROTO_UDP : printOutUDP(packet);
break;

case ETH_P_ARP : fprintf(stdout, "ARP request\n");
break;

default : fprintf(stdout, "Unrecognized packet\n");
break;
}
}

close(parameters->sock);
}


int getPacketType ( unsigned char * packet )
{
/* Read type of packet from ethernet header */
struct ethhdr * eth;

eth = (struct ethhdr *)packet;

/* If protocol is IP : we select between TCP and UDP */
if ( ntohs(eth->h_proto) == ETH_P_IP )
return getIPproto(packet);
else
return ntohs(eth->h_proto);
}


int getIPproto ( char * packet )
{
/* Look into IP header to know if this is UDP or TCP */
struct iphdr * ip=(struct iphdr *)(packet + sizeof(struct ethhdr));

return (ip->protocol);
}


/* This function prints out informations from a TCP packet */
void printOutTCP ( unsigned char * packet )
{
unsigned char *src, *dst;
struct iphdr * ip;
struct tcphdr * tcp;

/* put a pointer on IP header */
ip = (struct iphdr *)(packet + sizeof(struct ethhdr));
/* the same way for TCP */
tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr));

/* Used to print out IP addresses */
src = (unsigned char *)&(ip->saddr);
dst = (unsigned char *)&(ip->daddr);
/* --- */
fprintf(stdout, "TCP frame\n");
fprintf(stdout, "From\t: %u.%u.%u.%u (port : %u)\n", src[0], src[1], src[2], src[3], ntohs(tcp->source));
fprintf(stdout, "To\t: %u.%u.%u.%u (port : %u)\n", dst[0], dst[1], dst[2], dst[3], ntohs(tcp->dest));
fprintf(stdout, "TTL\t: %d\n", ip->ttl);
fprintf(stdout, "Flags\t: SYN=%d | ACK=%d | RST=%d | URG=%d | FIN=%d\n",tcp->syn, tcp->ack, tcp->rst, tcp->urg, tcp->fin);
}


/* This function prints out informations from a UDP packet */
void printOutUDP ( unsigned char * packet )
{
unsigned char *src, *dst;
struct iphdr * ip;
struct udphdr * udp;

/* put a pointer on IP header */
ip = (struct iphdr *)(packet + sizeof(struct ethhdr));
/* the same way for UDP */
udp = (struct udphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr));

/* Used to print out IP addresses */
src = (unsigned char *)&(ip->saddr);
dst = (unsigned char *)&(ip->daddr);
/* --- */
fprintf(stdout, "UDP frame\n");
fprintf(stdout, "From\t: %u.%u.%u.%u (port : %u)\n", src[0], src[1], src[2], src[3], ntohs(udp->source));
fprintf(stdout, "To\t: %u.%u.%u.%u (port : %u)\n", dst[0], dst[1], dst[2], dst[3], ntohs(udp->dest));
fprintf(stdout, "TTL\t: %d\n", ip->ttl);
}


/* Open RAW socket, receive every packet that crosses our netdevice */
int openRawSock ( void )
{
int fd;

if ( (fd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL))) < 0 )
{
perror("Socket");
exit(EXIT_FAILURE);
}
else
return fd;
}


Voilà!! En espérant qu'il pourra servir à quelqu'un. Je vous laisse le soin de refaire l'indentation détruite par l'éditeur wysiwyg! :)
Il aurait été possible d'utiliser la libPCAP (Packet CAPture) pour coder quelque chose de bien plus puissant, elle sert aussi à accéder aux basses couches du réseau, pas très utile sous Gnu/Linux où cela se fait bien "à la main", mais indispensable sous Windows!!

1 commentaire:

Unknown a dit…

Merci henri pour le petit sniffer.

Je l'ai quelque peu adapter (je n'ai que besoin du tcp )

Il ne me reste plus que la resolution du nom de domaine.