Raw SocketPDF | Stampa | E-mail
Scritto da Administrator  
Mercoledì 30 Settembre 2009 09:51

*******************************************
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...

 

Dove sono

Location

Via L.Galvani, 36
20019 Settimo Milanese (Mi)
Partita Iva 06656160964
luke @ lsciortino.com ( e )
Fax +39 0270038898