******************************************* Breve Guida ai Raw Socket ed ai pacchetti TCP/IP *******************************************
-------------------- Introduzione -------------------- Ciao a tutti. Tanto per iniziare voglio spiegare i motivi per cui ho voluto scrivere questo tutorial sui raw socket. I raw socket permettono di gestire manualmente i pacchetti che inviamo su una rete (anche in locale, sulla loopback device). In pratica, di solito, quando ci connettiamo ad Internet il kernel impacchetta i dati che l'applicazione, diciamo un browser, vuole vengano inviati e aggiunge delle informazioni relative alla nostra macchina. Ad esempio aggiunge quale e' l'ip sorgente del pacchetto, a quale ip e' destinato il pacchetto, a quale porta questo e' destinato ed altre informazioni che vedremo in seguito. Con i raw socket (e qualche opzione particolare) tutto questo lavoro sara' fatto da noi. E' uno sporco lavoro, ma qualcuno deve pur farlo! Beh, spero che alla fine questa guida risulti utile a molti... almeno a coloro che vogliono (cominciare a) capire come funzionano le reti ,internet in particolare, e vogliono divertirsi :) Naturalmente tutte queste informazioni le ho acquisite cercando in rete documenti a riguardo, non e' che una mattina mi sono svegliato e mi sono accorto di sapere qualcosa sui socket! Fate lo stesso.
Conoscenze di base Se volete capire questa guida e i sorgenti contenuti dovete avere un minimo di conoscenza del C. Poi, se magari avete gia' usato i socket tanto di guadagnato. Dovete avere pero' un minimo di dimestichezza con i socket e su come i byte sono ordinati al loro interno (host byte order e network byte order). Comunque, credo che la maggior parte di voi capira' subito il tutto, anche perche' non e' niente di particolare. -------------------- Cosa ci serve -------------------- Visto che andremo a vedere come si costruiscono pacchetti TCP/IP abbiamo bisogno di due strutture che ci permettano di mantenere tutte le informazioni necessarie. Queste due strutture sono: struct iphdr e struct tcphdr. Le trovate negli header file ip.h e tcp.h (dovete includere e ). Andiamo a vedere cosa contengono queste due strutture.
struct iphdr { unsigned int ihl:4; // :4 indica che il membro e' composto // di 4 bits unsigned int version:4; u_int8_t tos; u_int16_t tot_len; u_int16_t id; u_int16_t frag_off; u_int8_t ttl; u_int8_t protocol; u_int16_t check; u_int32_t saddr; u_int32_t daddr; };
ihl (IpHeaderLength) Lunghezza dell'header ip in parole da 32 bit. Se ha valore 5 significa che e' lungo 5*4 byte. Il valore del campo ihl deve essere posto diverso da 5 solo nel caso in cui l'header contiene opzioni. Di solito questo e' fatto solo dai router. version Versione del protocollo. Deve essere posto sempre uguale a 4 (non serve a niente metterlo a 6 perche' la struttura dell'header IPv6 e' differente). tos (TypeOfService) Controlla la priorita' del pacchetto. I primi tre bit sono interpretati dai router, gli altri 4 indicano la priorita' relativa al tipo di servizio richiesto: il primo di questi indica il Delay, il secondo Throughput, il terzo Reliability il quarto Cost. tot_len Lunghezza dell'intero pacchetto. Questo valore deve tener conto dell'header ip, di quello tcp (o icmp o udp) e dei dati aggiuntivi accodati al pacchetto. id Viene usato per riassemblare il pacchetto IP in caso di frammentazione. frag_off Viene usato per riassemblare il datagrammi frammentati I primi tre bit sono le flag di frammentazione. Il primo di questi bit e' sempre posto a zero. Il secondo indica che non e' stato frammentato, il terzo invece il contrario. Per settare queste flag e' sufficiente fare un OR con il valore esadecimale (per la don't fragment flag frag_off |= 0x4000, per la fragment flag frag_off |= 0x2000). Il valore di ip.frag_off deve essere espresso in NetworkByteOrder, con la funzione htonl(unsigned long int). ttl (TimeToLive) Ad ogni passaggio attraverso un nodo questo valore e' decrementato di una unita'. Quindi piu' alto e' piu' lontano potra' arrivare. Il valore massimo e' 255. protocol Indica il protocollo contenuto nel pacchetto ip. TCP = 0x06, UDP = 0x11, ICMP = 0x01 check Checksum. E' un valore calcolato all'invio. Ogni volta che il contenuto all'interno dell'intero pacchetto viene modificato deve essere ricalcolato. saddr Indirizzo sorgente del pacchetto :) daddr Indirizzo destinatario del pacchetto.
Il protocollo IP di per se non garantisce che il pacchetto arrivi a destinazione. Il TCP e' il protocollo piu' utizzato e garantisce un meccanismo per stabilire una connessione affidabile e un'autenticazione.
struct tcphdr { u_int16_t source; u_int16_t dest; u_int32_t seq; u_int32_t ack_seq; u_int16_t res1:4; u_int16_t doff:4; u_int16_t fin:1; u_int16_t syn:1; u_int16_t rst:1; u_int16_t psh:1; u_int16_t ack:1; u_int16_t urg:1; u_int16_t res2:2; u_int16_t doff:4; u_int16_t res1:4; u_int16_t res2:2; u_int16_t urg:1; u_int16_t ack:1; u_int16_t psh:1; u_int16_t rst:1; u_int16_t syn:1; u_int16_t fin:1; u_int16_t window; u_int16_t check; u_int16_t urg_ptr; };
source Porta sorgente. dest Porta destinataria. seq Numero di sequenza ack_seq Acknowledgment number. doff Data offset fin Final. La connessione deve essere chiusa. La controparte deve rispondere con un altro pacchetto anch'esso con la flag fin settata. syn Synchronization. Questa flag indica che si vuole inizare una nuova connessione. rst Reset. La connessione e' stata chiusa. psh Push. Il pacchetto non sara' trattenuto dallo stack IP ma giungera' direttamente alla applicazione (usato per pacchetti contenenti dati). ack Acknowledegment. Usato per avvertire la controparte che il pacchetto precedente e' stato ricevuto correttamente. urg Urgent. il pacchetto sara' instradato piu' velocemente dai router. window Quantita' di dati che si possono inviare prima di ottenere una risposta con un ACK check Checksum. E' un valore calcolato all'invio. Ogni volta che il contenuto all'interno dell'intero pacchetto viene modificato deve essere ricalcolato. Serve al ricevente per controllare l'integrita' del pacchetto. urg_ptr Urgent pointer. Se la flag urg non e' selezionata deve essere uguale a zero, altrimenti punta alla fine dei dati che devono essere inviati con priorita'.
------------------------ Un po' di codice ------------------------ Beh... direte voi! Ora che ci facciamo con tutta sta robba. Semplice, possiamo fare un programma che invii pacchetti spoofati o con qualsiasi altra opzione vogliamo.
-------------------->-<---init_packet--->-<-----------------------------
char *init_packet (struct iphdr *ip, struct tcphdr *tcp) {
char *sacket; int spoof;
/* Allochiamo un puntatore a vettore di char che contenga il tutto */ sacket = calloc (1, sizeof (struct iphdr) + sizeof (struct tcphdr)); ip = (struct iphdr *) sacket; tcp = (struct tcphdr *) (sacket + sizeof (struct tcphdr));
ip->version = 4; /* Ip header lenght */ ip->ihl = 5; /* TypeOfService */ ip->tos = 0;
/* L'unsigned short integer frag_off e' utilizzato nel seguente modo: * (L'ordine dei bit e' dal piu' significativo a quello meno, cioe' * da sinistra a destra in pratica :) ) * 1° sempre uguale a 0 * 2° don't fragment: il pacchetto non e' stato frammentato quando e' * stato inviato. * 3° more fragments following: il pacchetto e' stato frammentato. * 4°-16° l'offset del pacchetto ossia il numero d'ordine del frammento. * * Se volete che i pacchetti abbiano tutti la flag don't fragment settata * definite DNT_FRAGMENT (#define FRAGMENT). * Se volete che i pacchetti abbiano la flag more fragments following * settata definite FRAGMENT (#define DNT_FRAGMENT). * */
#ifdef DNT_FRAGMENT /* settiamo la flag don't-fragment con 0x4000 */ ip->frag_off |= 0x4000; /* frag_off deve essere espresso in network bite order */ ip->frag_off = htons (ip->frag_off); #endif
#ifdef FRAGMENT /* settiamo la flag don't-fragment con 0x2000 */ ip->frag_off |= 0x2000; /* frag_off deve essere espresso in network bite order */ ip->frag_off = htons (ip->frag_off); #endif
#ifndef FRAGMENT #ifndef DNT_FRAGMENT ip->frag_off |= 0x0000; #endif #endif
ip->ttl |= 255; /* Il pacchetto viene inviato a noi stessi. * La funzione inet_addr non consente di usare l'ip * 255.255.255.255, visto che 255.255.255.255 convertito * in unsigned long e' -1, lo stesso valore che la funzione * restituisce per gli errori. Se sapete di dover utilizzare * l'ip 255.255.255.255 usate la funzione inet_aton */ ip->daddr = inet_addr ("127.0.0.1"); ip->protocol = IPPROTO_TCP; // TCP = 0x06 // UDP = 0x11 // ICMP = 0x01
/* Porta di destinazione */ tcp->dest = htons (8080); tcp->seq = inet_addr ("127.90.255.78"); tcp->ack_seq = inet_addr ("200.10.90.79"); tcp->urg = 0; tcp->ack = 1; tcp->psh = 1; tcp->rst = 0; tcp->syn = 0; tcp->fin = 0; tcp->window = 0;
return sacket; }
---------------------->-<---fine sorgente--->-<---------------------------
La funzione init_pack crea un pacchetto con un settaggio base. Alcune cose, tipo tcp->ack e ip->daddr le ho inserite solo per far vedere come si gestiscono i vari campi degli header. Molto semplice, vero? init_pack restituisce un puntatore a char contenente il pacchetto inizializzato. Le strutture tcp e ip sono passate per puntatore visto che dovranno essere riutilizzate in seguito.
--------------------->-<---sendsacket--->-<-----------------------------
int sendsacket (struct iphdr *ip, struct tcphdr *tcp, char *sacket) {
struct sockaddr_in to; int pack_size = sizeof (struct iphdr) + sizeof (struct tcphdr); int fd, ja = 1;
/* Lunghezza totale del pacchetto espressa in network byte order */ ip->tot_len = htons (pack_size); ip->id = getpid (); if (!spoof) ip->saddr = htonl (INADDR_ANY); ip->check = in_chksum (( u_short *)&ip, sizeof (struct iphdr));
tcp->source = getpid (); tcp->check = in_chksum ((u_short *)&ip, sizeof (struct tcphdr));
to.sin_port = 0; to.sin_family = AF_INET; to.sin_addr.s_addr = ip->daddr;
if ((fd = socket (PF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) { return -2; }
/* Con la macro IP_HDRINCL avvertiamo il kernel che il pacchetto * contiene gia' l'header IP. Se non facessimo cosi il kernel * aggiungerebbe di suo un altro header IP. */ if (setsockopt (fd, IPPROTO_IP, IP_HDRINCL, &ja, sizeof (ja)) < 0) return -1;
return (sendto (fd, sacket, pack_size, 0, (struct sockaddr*)&to, sizeof (struct sockaddr))); }
---------------------->-<---fine sorgente--->-<---------------------------
La funzione invia il pacchetto cosi come e' stato settato (da qualcun'altra funzione). La funzione ritorna un intero. Se questo e' positivo l'invio ha avuto buon esito e il numero ritornato rappresenta il numero di bytes inviati. Se la funzione ritorna -2 c'e' stato un errore nella creazione del socket, probabilmente dovuto a problemi di privilegi (bisogna essere root). Tutti gli altri valori negativi indicano un errore generico.
u_short in_chksum (u_short *addr, int len) {
int nleft = len , sum = 0; u_short *w = addr; u_short value = 0;
while (nleft > 1) { sum += *w++; nleft -=2; }
if (nleft == 1) { *(u_char *)(&value) = *(u_char *)w; sum += value; }
sum = (sum >> 16) + (sum + 0xffff); sum += (sum >> 16); answer = ~sum; return (value); }
Funzione per il calcolo del checksum.
------------------------------- Considerazioni finali ------------------------------- Dovete tenere presente che modificando l'ip sorgente del pacchetto non e' comunque possibile stabilire una connessione TCP. Lo chiarisco per quelli di voi che gia' stavano saltando dalla gioia pensando di poter finalmente invia email anonime, o giocare brutti scherzi :) Va bene, chiarito tutto spiego perche' non e' possibile. Non e' possibile in questa modalità perche' il protocollo TCP impone un handshake iniziale. In questa fase il client che richiede la connessione setta il campo seq del header tcp con un numero random, il campo ack_seq uguale a zero e la flag SYN uguale a 1. Quando il server riceve questo pacchetto risponde con il campo ack_seq contenente un valore random e con le flag ACK e SYN settate, dopo di che il client risponde con la flag ACK settata. Il campo ack_seq settato a zero impedisce di creare facilmente connessioni tcp spoofate. Dico facilmente perche' nei kernel 2.2.17 (mi pare fossero i .17) c'era un bug che permetteva di predire l'ack_seq che il kernel avrebbe utilizzato. Ricordate che tutto questo vale solo per il TCP. L'UDP e l'ICMP non hanno queste caratteristiche quindi usando l'UDP magari potete creare un modo anonimo di comunicare con altri. Basterebbe comunicarsi inizialmente gli indirizzi reali e poi comunicare tramite pacchetti UDP con sorgente spoofato. Ah, dimenticavo di dirvi una cosa che ri-rendera' felicissimi quelli di voi che stanno in una LAN. Ricordate che non e' possibile spoofare una connessione TCP completa a causa del ack_seq del server? Beh, se fosse possibile sapere qual'e' questo numero allora il gioco sarebbe fatto. Se siamo in Internet non possiamo vedere qual'e' perche' a noi arriveranno solamente i pacchetti che hanno noi come destinazione. Ma se siamo in una LAN, tutto cambia! Se mettiamo la scheda ethernet in modalita' promiscua, possiamo vedere tutto il traffico che ci attraversa. In questo modo per vedere l'ack_seq number che il server usera' in risposta ad un nostro pacchetto spoofato bastera' usare come indirizzo spoofato quello di un computer i cui pacchetti passano attraverso la nostra scheda. Pero' la risposta al pacchetto inviato dal server dovra' essere il piu' veloce possibile per impedire che il vero destinatario del pacchetto risponda con la flag RST chiudendoci cosi la connessione in faccia :(
Cos'altro dirvi? Esplorate, leggetevi i sorgenti del kernel o di qualsiasi altra cosa vi capiti tra le mani. Il "free software" e' bello proprio per questo, per la possibilita' di accrescere le nostre conoscenze che da ad ognuno di noi. Bisogna solo cercare e imparare, cercare e imparare, cercare e imparare...
|