Sinistra <- La tecnologia e le scelte dell'umanità - Indice Generale - Copertina - User-Mode Linux -> Destra

PLUTO Ware


Packet CAPture library, programmazione C a basso livello II

di Vincenzo "aspinall" Giacchina

L'articolo

L'articolo tratta la programmazione a basso livello in linguaggio C con la libreria pcap.



La programmazione a datalink, come abbiamo visto nel precedente numero, è tanto affascinante quanto ostica e laboriosa. Lavorare direttamente con le raw socket, affrontate nel precedente articolo, spesso presenta diverse problematiche. Una problematica comune potrebbe essere quella di dover lavorare con gli header del kernel, sottraendo al codice un discreto livello di portabilità, per non parlare poi delle difficoltà che si trovano nel sviluppare routine di parsing dei pacchetti.

Ethereal, tcpdump, ntop sono strumenti molto noti, che da sempre aiutano i sysadmin a tenere sotto controllo la rete. Questi programmi sono scritti con le pcap.
La libreria in questione è nata intorno al 1993 all'Università della California per mano di Van Jacobson. È ora supportata pienamente dalla comunità opensource ed è reperibile al sito http://www.tcpdump.org (tcpdump è lo sniffer per eccellenza che sfrutta appieno la potenza e l'efficacia della libpcap).

Le principali funzioni di questa libreria permettono con una facilità estrema di cercare e trovare un device di rete, inteso come network adapter, impostarlo in modalità promiscua (in modo diverso rispetto alla chiamata ioctl() vista nel precedente articolo), gestire filtri potenti e flessibili, selezionare grazie ai filtri il traffico da analizzare, analizzare pacchetto per pacchetto. Permette inoltre un'ottima gestione degli errori, quindi un buon livello di debug, ed infine ottimi strumenti per statistiche sull'analisi effettuata.

La libpcap è costituita da diverse funzioni che verranno analizzate sequenzialmente facendo riferimento al procedimento logico che porta alla creazione di un primitivo sniffer.

Funzioni principali

char *pcap_lookupdev(char *errbuf);

La funzione pcap_lookupdev cerca e restituisce un puntatore con il primo device di rete che trova. L'argomento errbuf è semplicemente un buffer utilizzato per contenere errori.

Esempio pratico:

char *dev;
char errbuf[PCAP_ERRBUF_SIZE]; // PCAP_ERRBUF_SIZE è una costante già definita in libpcap.h
dev = pcap_lookupdev(errbuf);
printf("%s", dev);
int pcap_lookupnet(char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf);

La funzione pcap_lookupnet si utilizza per rilevare informazioni quali indirizzo e netmask della rete. bpf_u_int32 *netp e bpf_u_int32 *maskp sono i puntatori che indicano rispettivamente quando detto prima.

Esempio pratico:

bpf_u_int32 netmask;
bpf_u_int32 network;

if((pcap_lookupnet(dev, &network, &netmask, errbuf)) < 0)
{
	printf("pcap_lookupnet()");
	return -1;
}
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *errbuf);

La funzione pcap_open_live serve per aprire ed impostare un device in modalità promiscua e restituisce un packet capture descriptor pcap_t *, un descrittore che si utilizzerà per accedere al device. snaplen indica il valore massimo di byte da catturare, to_ms invece è un read timeout.

Esempio pratico:

pcap_t *ptr;

if((ptr = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf)) == NULL) 
{
	perror("pcap_open_live()");
	return -1;
}
int pcap_datalink(pcap_t *p);

La funzione pcap_datalink identifica il tipo di link layer in cui si sta lavorando. L'argomento da passare è il descrittore pcap_t.

Esempio pratico:

int link_layer;
pcap_t *ptr;

link_layer = pcap_datalink(ptr);
switch(link_layer)
{
      case DLT_EN10MB: // Ethernet 10 mb, la nostra tipologia di rete
	offset = 14;
	break;
	...
	....
}
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask);

La funzione pcap_compine permette di impostare dei filtri che discriminando il traffico catturano solo determinati pacchetti. Il puntatore fp alla struttura bpf_program verrà riempita dalla funzione pcap_compile settando i filtri con char *str.

Esempio pratico:

struct bpf_program *fp;
char *filter;

if((pcap_compile(ptr, &fp, filter, 0, netmask)) < 0)
{
	printf("error :%s ", pcap_geterr(ptr)); //pcap_geterr indica il tipo di errore
	return -1;
}
int pcap_setfilter(pcap_t *p, struct bpf_program *fp);

La funzione pcap_setfilter imposta nella struttura bpf_program i filtri.

Esempio pratico:

if((pcap_setfilter(ptr, &fp)) < 0)
{
	perror("pcap_setfilter()");
	return -1;
}
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);

La funzione pcap_next legge pacchetto per pacchetto restituendo un puntatore che riempie la struttura pkthdr.

Esempio pratico:

struct pcap_pkthdr hdr;
...
pkt = pcap_next(ptr, &hdr);

Filtri e Berkeley Packet Filtering

I filtri sono una delle caratteristiche più interessanti ed efficaci che ci offre libpcap. pcap_compile e pcap_set_filter si appoggiano alla struttura bpf_program e compilano i filtri attraverso BPF. Berkeley Packet Filtering è una macchina virtuale gestita a livello del kernel che si divide in due componenti: network tap e packet filter. Network tap è un device che prende in consegna il pacchetto ed il packet filter si occupa di controllare eventuali restrizioni: se i filtri lo consentono BPF consegna il pacchetto al protocollo applicativo.

Abbiamo visto che i filtri vengono gestiti dalle pcap come semplici stringhe. Le espressioni per il matching dei pacchetti sono:

ASFPj 0.1: Another pcap Sniffer for PLUTO Journal

Questo codice racchiude tutto quello che avete letto precedentemente. Buon auditing!

/*  Author : aspinall@oltrelinux.com
 *  License : This source file is under GPL
 *  
 *  gcc sniffer.c -o sniffer -lpcap
 *
 *
 *  Disclaimer:
 * Use of this information constitutes acceptance for use in
 * an AS IS condition.There are NO warranties with regard to
 * this information. In no event shall the author be liable for
 * any damages whatsoever arising out of or in connection with
 * the use or spread of this information. Any use of this
 * information is at the user's own risk.
 */

 
#define _GNU_SOURCE

#include <stdio.h>
#include <pcap.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>

#define  PROMISC   1

struct ipv4_hdr
{

   u_int8_t ip_hl:4, ip_v:4;        
   u_int8_t ip_tos;      
   u_int16_t ip_len;         
   u_int16_t ip_id;          
   u_int16_t ip_off;
   u_int8_t ip_ttl;          
   u_int8_t ip_p;            
   u_int16_t ip_sum;         
   struct in_addr ip_src, ip_dst; 
};

void help(void);
void help(void) 
{
printf("\nusage -h [help] -f [\"filter\"]\n");
printf("ASFPj 0.1 : Another pcap Sniffer for Pluto Journal\n");
printf("Author : Vincenzo 'aspinall` Giacchina\n\n");
exit(1);
}

int main(int argc, char *argv[]) {

struct pcap_pkthdr hdr;
struct bpf_program fp;
struct ipv4_hdr *ip;
bpf_u_int32 netmask;
bpf_u_int32 network;

pcap_t* ptr;

const u_char *pkt;
char *dev; 
char *filter = NULL;
int link_layer, offset, c;

char errbuf[PCAP_ERRBUF_SIZE];

while((c = getopt(argc, argv, "f:h?")) != -1)
{
	switch(c)
	  {
	   case 'f':
	     filter = optarg;
	     break;
	   case 'h':
	   case '?':
	   default:
	     help();
	     break;
	  }
}

dev = pcap_lookupdev(errbuf);

if((pcap_lookupnet(dev, &network, &netmask, errbuf)) < 0)
{
	perror("pcap_lookupnet()");
	return -1;
}

if((ptr = pcap_open_live(dev, BUFSIZ, PROMISC, 0, errbuf)) == NULL) 
{
	perror("pcap_open_live()");
	return -1;
}

link_layer = pcap_datalink(ptr);
switch(link_layer)
{
      case DLT_EN10MB:
	offset = 14;
	break;
      case DLT_NULL:
      case DLT_PPP:
	offset = 4;
	break;
      case DLT_SLIP:
	offset = 16;
	break;
      case DLT_RAW:
	offset = 0;
	break;
      case DLT_SLIP_BSDOS:
      case DLT_PPP_BSDOS:
	offset = 24;
	break;
      case DLT_FDDI:
	offset = 21;
	break;
      default:
	fprintf(stderr, "Error: Unknown Datalink Type: (%d)\n\n", link_layer);
	return -1;
}

if((pcap_compile(ptr, &fp, filter, 0, netmask)) < 0)
{
	printf("error :%s ", pcap_geterr(ptr));
	return -1;
}

if((pcap_setfilter(ptr, &fp)) < 0)
{
	perror("pcap_setfilter()");
	return -1;
}

printf("listen to %s \n", dev);

while(1)
{
	pkt = pcap_next(ptr, &hdr);
	ip = (struct ipv4_hdr *) (pkt + offset);

   	printf("\n---IP header---\n");
   	printf("%s", inet_ntoa(ip->ip_src));
   	printf(" -> ");
   	printf("%s\n", inet_ntoa(ip->ip_dst));
   	printf("Version: %d\t", ip->ip_v);
   	printf("Length IP: %d\t", ntohs(ip->ip_len));
   	printf("TTL: %d\n", ip->ip_ttl);
}

pcap_close(ptr);
fflush(stdin);
return 0;
}

Ultime precisazioni

Il puntatore filter deve sempre contenere o NULL o un filtro: diversamente pcap_compile tornerà sempre -1. Fate attenzione a non dichiarare mai char *filter senza poi riempirlo. La modalità promiscua non è settata tramite la funzione ioctl(), ma con setsockopt() e PACKET_ADD_MEMBERSHIP, il che vuol dire che ifconfig usando ioctl() non sarà in grado di rilevare la modalità promiscua settata sul device.

Questa volta vi lascio con un piccolo giochino, sperando che molti di voi per curiosità si addentrino nella questione.

Lanciamo lo sniffer.

# ./sniffer
listen to eth0

--- IP header ---
10.0.0.76 -> 217.220.29.42
Version: 4      Length IP: 71   TTL: 64

Recuperiamo l'antisniff scritto nel precedente articolo, ricordate?

Lanciamo l'antisniff che dovrebbe rilevare lo sniffer attivo.

# ./antisniff
eth0 non è in promiscuous mode.

Come detto prima, la libpcap ha un suo sistema per impostare la modalità promiscua.

/* enable promiscuous mode */

    memset( &mr, 0, sizeof( mr ) );
    mr.mr_ifindex = ifr.ifr_ifindex;
    mr.mr_type    = PACKET_MR_PROMISC;

    if( setsockopt( raw_sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
                    &mr, sizeof( mr ) ) = -1 )
    {
        perror( "setsockopt" );
        return(1);
    }

Vi invito a studiare e risolvere il dilemma.



L'autore

Vincenzo "aspinall" Giacchina attualmente lavora come sistemista Unix in un' azienda fornitrice Telecom. Studia, nel tempo che trova, informatica all'università di Palermo. Il suo interesse principale è la network security. Collabora con alcune riviste del settore e spesso di notte coltiva il suo hobby: la programmazione.


Sinistra <- La tecnologia e le scelte dell'umanità - Indice Generale - Copertina - User-Mode Linux -> Destra