
------------
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. Il codice che trovate sotto e' estratto da
Sacket, un tool GTK per creare pacchetti TCP/IP. Lo trovate qui:
http://napolihak.da.ru
-------------------->-<---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 :) Devo dire la verita', pure io ero tra voi :D
Va bene, chiarito tutto spiego perche' non e' possibile. Non e' possibile
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 con una connessione dialup (con un modem, in
pratica) non possiamo vedere qual'e' perche' a noi arriveranno
solamente i pacchetti che hanno noi come destinazione. Ma se siamo
in una LAN, eheh, 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...
N'eM Sy (nemesy.it@tiscalinet.it)
for napolihak.da.ru
Allego la mia chiave pubblica per chi di voi non solo mi volesse
scrivere, ma addirittura con lo voglia fare con pgp.
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGP 6.5.8
mQEPAzvtfsUAAAEIAK/XtUG0jmS981V+uqgAd5xXnPQMi07mWXb++3TV5z+8mt4g
6SENRv97gLX0LbYbYOANB6+Clg0n/5ikNDxnCm6w+tHvKm9MMH5F1Tylr0HFz4Jo
LvvMLgtG3yc8QuzrtpuPZ6hdvnZ7z2HNb/kQqoHIh7xqmD+XmbLt9lVsY0I1AT6b
SeNzB5ets+wuBx10kRn2KOtu6mEPC1dXTBAVh+guF+nWF16mYZi6IHRr9kI2Z0jn
hF0H5zHUkg9hZwJz3ck7gHOkzOiJ7BU7H0K+vDK5lUc8P4N3WFYU8Jfd4g/T3TK5
8SJEwb11dsB89jfwbqd0ygO7w1SwCVa9+1y/88UAEQEAAbQgbmVtIGl0IDxuZW1l
c3kuaXRAdGlzY2FsaW5ldC5pdD6JARUDBRA77X7FCVa9+1y/88UBAWkgB/9+nIPm
tQMATzbm3sNOHXtax6nTh8HEA2+XpV1GXEuF/UTSslq6oTXEONHQ6I3BTILMKYqq
GTy/li6kRozq05P54fOYNcs3W8XcmxvsWilUQs9uiVdCuOW3kBI2goh0o9rnKx8w
N6sUlxLfRJwO90CN0Iy8TvzDlbjqqJHRqyFNR3x5LpsmaLUFC5XnEjeeIp+lgJO/
kD27FHT3dtdayGz0q2q18k1ggHaSisc+MlOFWuavP4tGdEcpFFC6kt8Z++7KV9ln
CAZwqKkaFSEXn5PyBgxbXaO9MOFoXmdE3Uknzx2lHalevZK+OTxKHUtWawe5TKDo
ES2qBzBPRsZc39BU
=i+/9
-----END PGP PUBLIC KEY BLOCK-----
"Who controls the past controls the future.
Who controls the present controls the past.
Who controls the present, now?"
|