Raw socket

Da Hacknowledge.

I socket standard usati in C sono relativamente comodi da usare in quanto automatizzano tutti i meccanismi implementati dal protocollo TCP/IP, lasciando allo sviluppatore solo la responsabilità del livello applicativo. Tuttavia in alcuni contesti si vuole avere il controllo completo di ciò che viene inviato sulla rete. Applicazioni tipiche sono l'IP spoofing (invio di un pacchetto ad un host con un altro IP) e i conseguenti attacchi Smurf. In questi casi può risultare comodo costruirsi il pacchetto inviato sull'interfaccia di rete pezzo per pezzo. Per fare ciò il C mette a disposizione i raw socket, dei socket su cui è possibile inviare pacchetti grezzi creati dallo sviluppatore (ovviamente delle profonde conoscenze dei protocolli di rete e di trasporto sono richieste). Vediamo subito un esempio pratico con un'applicazione che crea un pacchetto da zero che pinga localhost e lo invia su raw socket:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/ip.h>
 
#define ICMP_ECHO               8
#define IPLEN           sizeof(struct iphdr)
#define ICMPLEN         sizeof(struct icmphdr)
 
typedef unsigned char  u8;
typedef unsigned short u16;
typedef unsigned long  u32;
 
struct icmphdr {
        u8              type;
        u8              code;
        u16             checksum;
        u16             id;
        u16             sequence;
};
 
unsigned short csum (u16 *buf, int nwords)  {
        unsigned long sum;
 
        for (sum = 0; nwords > 0; nwords--)
                sum += *buf++;
                sum = (sum >> 16) + (sum & 0xffff);
                sum += (sum >> 16);
        return ~sum;
}
 
main()  {
        int i,sd,one,len;
        unsigned char buff[BUFSIZ],in[BUFSIZ];
        char data[56];
        char *tmp;
 
        struct sockaddr_in sin;
        struct iphdr *ip = (struct iphdr*) malloc(IPLEN);
        struct icmphdr *icmp = (struct icmphdr*) malloc(ICMPLEN);
 
        srand ((unsigned) time(NULL));
        sd=socket (PF_INET, SOCK_RAW, IPPROTO_ICMP);
 
        sin.sin_family=AF_INET;
        sin.sin_port=0;
        sin.sin_addr.s_addr=inet_addr("127.0.0.1");
 
        memset (buff,0,sizeof(buff));
 
        for (i=0; i<56; i++)
                data[i]=i;
 
        ip->version=4;
        ip->ihl=5;
        ip->tos=0;
        ip->tot_len=IPLEN+ICMPLEN+sizeof(data);
        ip->id=0;
        ip->frag_off=0;
        ip->ttl=64;
        ip->protocol=IPPROTO_ICMP;
        ip->check=0;
        ip->saddr=inet_addr("127.0.0.1");
        ip->daddr=inet_addr("127.0.0.1");
        ip->check = csum ((u16*) buff, ip->tot_len >> 1);
 
        icmp->type=ICMP_ECHO;
        icmp->code=0;
        icmp->checksum=0;
        icmp->id=1;
        icmp->sequence=1;
 
        tmp = (char*) malloc(ICMPLEN+sizeof(data));
        memcpy (tmp, icmp, ICMPLEN);
        memcpy (tmp+ICMPLEN, data, sizeof(data));
        icmp->checksum=csum((u16*) tmp, ICMPLEN+sizeof(data) >> 1);
 
        memcpy (buff, ip, IPLEN);
        memcpy (buff+IPLEN, icmp, ICMPLEN);
        memcpy (buff+IPLEN+ICMPLEN, data, sizeof(data));
 
        one=1;
 
        if (setsockopt (sd, IPPROTO_IP, IP_HDRINCL, &one, sizeof (one)) < 0)
                printf ("Warning: Cannot set HDRINCL!\n");
 
        if (sendto (sd,
                          buff,
                          ip->tot_len,
                          0,
                          (struct sockaddr *) &sin,
                          sizeof (sin)) < 0)  {
                printf ("Error in send\n");
                exit(1);
        } else
                printf ("Send OK\n");
}

Vediamo i componenti notevoli:

#include <linux/ip.h>

Questa dichiarazione è necessaria per poter usare la struttura iphdr, contenente tutti i campi di un header IP, che semplifica notevolmente il lavoro. Successivamente dichiaro la struttura di un header ICMP (icmphdr).

unsigned short csum (u16 *buf, int nwords)  {
        unsigned long sum;
 
        for (sum = 0; nwords > 0; nwords--)
                sum += *buf++;
                sum = (sum >> 16) + (sum & 0xffff);
                sum += (sum >> 16);
        return ~sum;
}

Questa è la funzione per il calcolo del checksum di un header (complemento a 1 della somma dei complementi a 1 dell'header diviso in word da 16 bit). In seguito inizializzo il socket come socket raw

sd=socket (PF_INET, SOCK_RAW, IPPROTO_ICMP);

riempio gli ultimi 56 byte del pacchetto con byte casuali (struttura classica di un pacchetto ping)

for (i=0; i<56; i++)
  data[i]=i;

quindi riempio gli header IP e ICMP:

ip->version=4;
        ip->ihl=5;
        ip->tos=0;
        ip->tot_len=IPLEN+ICMPLEN+sizeof(data);
        ip->id=0;
        ip->frag_off=0;
        ip->ttl=64;
        ip->protocol=IPPROTO_ICMP;
        ip->check=0;
        ip->saddr=inet_addr("127.0.0.1");
        ip->daddr=inet_addr("127.0.0.1");
        ip->check = csum ((u16*) buff, ip->tot_len >> 1);
 
        icmp->type=ICMP_ECHO;
        icmp->code=0;
        icmp->checksum=0;
        icmp->id=1;
        icmp->sequence=1;

A questo punto effettuo il calcolo del checksum ICMP

tmp = (char*) malloc(ICMPLEN+sizeof(data));
        memcpy (tmp, icmp, ICMPLEN);
        memcpy (tmp+ICMPLEN, data, sizeof(data));
        icmp->checksum=csum((u16*) tmp, ICMPLEN+sizeof(data) >> 1);

in quanto dovrò calcolare il checksum sull'header ICMP e sulla parte di dati. Ora copio le strutture così riempite in un buffer

memcpy (buff, ip, IPLEN);
        memcpy (buff+IPLEN, icmp, ICMPLEN);
        memcpy (buff+IPLEN+ICMPLEN, data, sizeof(data));

setto l'opzione IP_HDRINCL sul socket (necessaria per iniettare pacchetti raw, richiede i privilegi di root)

one=1;
 
        if (setsockopt (sd, IPPROTO_IP, IP_HDRINCL, &one, sizeof (one)) < 0)
                printf ("Warning: Cannot set HDRINCL!\n");

quindi effettuo l'invio tramite sendto:

if (sendto (sd,
                          buff,
                          ip->tot_len,
                          0,
                          (struct sockaddr *) &sin,
                          sizeof (sin)) < 0)  {
                printf ("Error in send\n");
                exit(1);
        } else
                printf ("Send OK\n");
Strumenti personali