Sinistra <- Mozilla Firefox - Indice Generale - Copertina - DaDaBIK -> Destra

PLUTO Ware


Sniffing e programmazione C a basso livello

di Vincenzo "aspinall" Giacchina

L'articolo

In questo articolo l'autore descrive sinteticamente cosa sia uno sniffer, illustrando con un esempio pratico come se ne possa scrivere uno.



Con il termine sniffing si intende quella tecnica che permette ad un utente di poter leggere nel dettaglio tutto il traffico che transita nella rete in modalità passiva, con scopi legittimi (ad esempio verificare se vi sono state eventuali intrusioni nel proprio sistema) o illegittimi (ad esempio tentare di intercettare informazioni sensibili provenienti da terze parti).
Quando uno sniffer viene eseguito su una macchina, solitamente si mette in background ad intercettare tutti i pacchetti che transitano per un'interfaccia di rete, sia in ingresso che in uscita, salvando il risultato su file o rendendolo semplicemente a video.

Avendo usato il termine "intercettare", è doveroso spiegare come questo avvenga.
Quando più pc si trovano nello stesso segmento di rete capita, ad esempio a causa dell'utilizzo di un hub, che i dati che viaggiano sulla rete vengano replicati.

          D
          ^
          |
          |
          |
B<------ HUB ------>C: riceve il traffico che A e D si scambiano, ma il suo kernel
          ^            lo rifiuta, perché non è indirizzato alla sua interfaccia di rete.
          |
          |
          |
          A: vuole comunicare con D, ma l'hub invia il traffico anche a tutte le altre macchine.

In una rete dove le comunicazioni non vengono crittografate, uno sniffer è davvero uno strumento potenzialmente pericoloso. Impostando la scheda di rete di una macchina in modalità promiscua, si dice al kernel di accettare tutti i pacchetti, anche quelli non destinati a quella determinata macchina.

Ogni applicazione che usa il protocollo TCP per trasmettere dei dati crea un "canale di comunicazione", definito socket. La socket si colloca ad un livello più alto rispetto ad una socket raw utilizzata da uno sniffer, semplicemente perché con uno sniffer dobbiamo andare a leggere tutti i dati trasmessi da altri programmi, quindi altre socket.
Una socket di tipo raw non solo permette di leggere tutti i pacchetti ma anche di forgiarne altri a nostro piacere.
Con una socket di tipo raw riusciamo a leggere l'header di ogni pacchetto ricevuto, inteso come intestazione di ogni pacchetto TCP, e in diversi casi (vedi hub) anche i pacchetti scambiati da altri pc.

Una volta chiaro cosa sia e cosa faccia uno sniffer, vediamo di scriverne uno. Munitevi di un compilatore e di buona pazienza.
Eccovi in pasto un po' di codice :

sock_ioctl = socket(PF_INET, SOCK_DGRAM, 0);

Con questa chiamata creiamo la socket per la comunicazione con la ioctl().

/* codice dell' header sockios.h
#define SIOCGIFFLAGS    0x8913          /* get flags                    */
#define SIOCSIFFLAGS    0x8914          /* set flags                    */
*/

ioctl(sock, SIOCGIFFLAGS, &ifr);

In questo modo leggiamo i flag del device e modifichiamo i campi che ci interessano.

ifr.ifr_flags |= IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, &ifr);

/* codice tratto da if.h
# define ifr_flags      ifr_ifru.ifru_flags     /* flags                */
IFF_PROMISC = 0x100,        /* Receive all packets.  */
# define IFF_PROMISC    IFF_PROMISC
*/

Modifichiamo il campo ifr.ifr_flags con IIF_PROMISC e con la funzione ioctl andiamo a scrivere tramite il flag SIOCSIFFLAGS nella struttura ifreq.
Fatto questo abbiamo modificato il nostro device impostandolo in modalità promiscua.

Dopo aver costruito il nostro pacchetto,

ethernet = (struct ethhdr *)buffer;
ip = (struct iphdr *) (buffer + sizeof(struct ethhdr));
tcp = (struct tcphdr *) (buffer + sizeof(struct ethhdr) + sizeof(struct iphdr));

inserendo ad ogni livello l'header precedente, dobbiamo semplicemente creare un ciclo infinito e tenerci in ascolto sulla socket.

while(1)
{
recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr *)&from, &addr_len);
printf(flag tcp vari ed eventuali....)

Strano ma vero, abbiamo già scritto un codice che analizza nel dettaglio l'header di ogni pacchetto che arriva alla recvfrom(), posta in ascolto sul nostro device.

/*  Author : aspinall@oltrelinux.com
 *  License : This source file is under GPL
 *  Only for Linux kernel
 *
 *
 *  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.
 */


#include <net/if.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>

int sock_ioctl;
int fd;
int addr_len;

char buffer[1500]; //1500 da RFC è il massimo MTU per Ethernet (più grosso non serve)

struct ifreq		ifr;
struct ethhdr		*ethernet	= (struct ethhdr *)&buffer[0];
struct iphdr		*ip		= (struct iphdr *)&buffer[sizeof(struct ethhdr)];;
struct tcphdr		*tcp;
struct udphdr		*udp;
struct icmphdr		*icmp;
struct sockaddr		from;
struct sockaddr_in 	sin_in;


void end() {
	ifr.ifr_flags &=~ IFF_PROMISC;	//toglie il flag IFF_PROMISC
	ioctl(sock_ioctl, SIOCSIFFLAGS, &ifr);
	close(sock_ioctl);
	exit(0);
}


int main(int agc,char *agv[]) {

(void) signal(SIGINT, end); //Intercetta CTRL+C e chiama la funzione end

if (geteuid ()) {
	fprintf (stderr, "Devi essere root\n");
	exit(1);
 }

if (agc < 2) {
	printf("usare: %s <device>\n", agv[0]);
	exit(1);
 }


if ((sock_ioctl = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
	perror("Errore socket()");
	exit(1);
 }


addr_len = sizeof(struct sockaddr);
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, agv[1], sizeof(ifr.ifr_name));
printf("\nSniffing sul device : ");
printf("%s\n", ifr.ifr_name);

if ((ioctl(sock_ioctl, SIOCGIFFLAGS, &ifr)) < 0) {
	fprintf(stderr, "%s", "Errore ioctl()");
	exit(1);
 }

ifr.ifr_flags |= IFF_PROMISC;
ioctl(sock_ioctl, SIOCSIFFLAGS, &ifr);

strncpy(from.sa_data, agv[1], sizeof(from.sa_data));
if ((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
	perror("Errore socket()");
	exit(1);
 }

while(1) {
	recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr *)&from,&addr_len);
	switch (ip->protocol) {
		case IPPROTO_TCP:
			printf("\n----IP----\n");
			sin_in.sin_addr.s_addr = ip->daddr;
			printf("ip destinazione : %s\n", inet_ntoa(sin_in.sin_addr));
			sin_in.sin_addr.s_addr = ip->saddr;
			printf("ip sorgente : %s\n", inet_ntoa(sin_in.sin_addr));
			
			printf("----TCP----\n");
			tcp = (struct tcphdr *)&buffer[sizeof(struct ethhdr) + sizeof(struct iphdr)];
			printf("porta sorgente : %u\n", ntohs(tcp->source));
			printf("porta destinazione : %u\n", ntohs(tcp->dest));
			printf("sequenze number : %u\n", ntohl(tcp->seq));
			printf("ACK-SEQ : %u\n", ntohl(tcp->ack_seq));
			printf("flag syn : %u\n", tcp->syn);
			printf("flag ack : %u\n", tcp->ack);
			printf("flag fin : %u\n", tcp->fin);
			printf("ttl : %u\n", ip->ttl);
			break;
		case IPPROTO_UDP:
			printf("\n----IP----\n");
			sin_in.sin_addr.s_addr = ip->daddr;
			printf("ip destinazione : %s\n", inet_ntoa(sin_in.sin_addr));
			sin_in.sin_addr.s_addr = ip->saddr;
			printf("ip sorgente : %s\n", inet_ntoa(sin_in.sin_addr));

			printf("----UDP----\n");
			udp = (struct udphdr *)&buffer[sizeof(struct ethhdr) + sizeof(struct iphdr)];
			printf("porta sorgente : %u\n", ntohs(udp->source));
			printf("porta destinazione : %u\n", ntohs(udp->dest));
			break;
		case IPPROTO_ICMP:
			printf("\n----IP----\n");
			sin_in.sin_addr.s_addr = ip->daddr;
			printf("ip destinazione : %s\n", inet_ntoa(sin_in.sin_addr));
			sin_in.sin_addr.s_addr = ip->saddr;

			printf("----ICMP----\n");
			icmp = (struct icmphdr *)&buffer[sizeof(struct ethhdr) + sizeof(struct iphdr)];
			printf("ip sorgente : %s\n", inet_ntoa(sin_in.sin_addr));
			printf("type : %u\n", icmp->type);
			printf("code : %u\n", icmp->code);
			break;
	}
 }
}

Lo sniffer, come abbiamo visto, impone alla scheda di rete la modalità promiscua. Partendo da questo presupposto, per poter vedere se qualche potenziale sniffer o comunque qualche altro strumento affine è avviato nella nostra macchina, dovremo andare a controllare il campo ifr.ifr_flags e vedere se è impostato il flag IFF_PROMISC.

if((ifr.ifr_flags & IFF_PROMISC) != 0)
fprintf(stdout, "%s promiscuos mode: Attenzione possibile sniffing.\n", ifr.ifr_name);

Così facendo, andiamo a controllare il flag IFF_PROMISC mostrando via codice se questa è attivo o no.
Chiaro che questo non è un artificio che mette del tutto in ginocchio chi sta tentando di sniffare il traffico, ma è sempre un buon punto di partenza.

/*  Author : aspinall@oltrelinux.com
 *  License : This source file is under GPL
 *  Only for Linux kernel
 *
 *
 *  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.
 */


#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <stdio.h>
#include <net/if.h>

int sock_ioctl;
struct ifreq ifr;


int main()  {

if ((sock_ioctl = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("Errore socket()");
        exit(1);
                }

strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));

if ((ioctl(sock_ioctl, SIOCGIFFLAGS, &ifr)) < 0) {
        fprintf(stderr, "%s", "errore ioctl()");
        exit(1);
                }


if ((ifr.ifr_flags & IFF_PROMISC) != 0)
        fprintf(stdout, "%s promiscuos mode: possibile sniffing.\n", ifr.ifr_name);
else
	fprintf(stdout, "%s non è in promuscuous mode.\n", ifr.ifr_name);
        exit(1);
        return 0;
}

Di carne al fuoco ne abbiamo messa parecchia: un consiglio che voglio darvi è quello di analizzare le strutture principali utilizzate in questi sorgenti per cercare di prendere più dimestichezza possibile con esse.



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 <- Mozilla Firefox - Indice Generale - Copertina - DaDaBIK -> Destra