----------------- Programmation d'un Sniffer ---------------- / ETHERNET #1 \ ___________________________ By RaPass | mailto:RaPass@linuxstart.com La comprehension de ce texte implique la conaissance du language C, et de la programmation reseau sous Unix/Linux. Si vous ne possedez rien de cela, je vous conseille d'aller lire d'autres textes sur le C, les sockets, les raw sockets et le TCP/IP. Ceci etant dis, on peut commencer... 1 - Description ---------------- Un sniffer est un programme qui chope tout ce qui passe sur le reseau, on peut "intercepter" tout les trames ethernet qui transitent sur le reseau et meme (Et surtout!) ceux qui ne nous sont pas adresse (Je parle pour l'instant de reseau ethernet). Un sniffer, ca sert a quoi? he ben ca peut servir a detecter des problemes et a maintenir le reseau ou a tester des programmes. Mais sa principal utilite pour nous et de choper des pass (sniffer les ports 21(FTP), 23(Telnet), 110(POP3)..) ou des infos(sniffer le port 25(SMTP)..) 2 - Ze "promiscuous" mode -------------------------- En tant "normal" lorsque l'on creer une socket et que l'on lit dessus, on ne recoit que les paquets qui nous sont adresse grace a notre adresse IP. Pour dire a la carte reseau de taiter tout les paquets qu'elle recoit(et donc meme ceux qui ne sont pas pour nous) il faut la mettre dans un mode special, le mode "promiscuous". On peut faire cela manuellement : $ id uid=0(root), gid=0(root) group=0(root) /* Bien sur, il faut etre root !*/ $ ifconfig eth0 promisc Ou on peut utiliser cette fonction (que je n'ai pas faites donc je n'ai aucun merite la-dessus) : ----------------------------Cut here----------------------------------------- #include /* Y a pas besoins de tout ca mais */ #include /* J 'ai la flemme de trier :( */ #include #include #include #include #include #include #include #include #include #include #include #include #include int setup_interface(char *device) { int fd; struct ifreq ifr; int s; //open up our magic SOCK_PACKET fd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); if(fd < 0) { perror("cant get SOCK_PACKET socket"); exit(0); } //set our device into promiscuous mode strcpy(ifr.ifr_name, device); s=ioctl(fd, SIOCGIFFLAGS, &ifr); if(s < 0) { close(fd); perror("cant get flags"); exit(0); } ifr.ifr_flags |= IFF_PROMISC; s=ioctl(fd, SIOCSIFFLAGS, &ifr); if(s < 0) perror("cant set promiscuous mode"); return fd; } --------------Cut here-------------------------------------------------------- Cette fonction prend comme argument le peripherique que l'on veut placer en mode "promiscuous" (Par exemple : eth0) et retourne une socket sur laquelle on pourra lire et donc voir ce qui transite sur le reseau :) Si vous ne voulez pas utilisez cette fonction et prefere utiliser la methode manuel, vous devrez creer une socket, regarder la source de la fonction l.26 (man socket pour plus d'info). 3 - Ze reception of the packets -------------------------------- Pour l'instant, nous allons essayer de coder un sniffer TCP, lorsque nous allons faire un read() sur la socket, nous allons recevoir un datagramme TCP/IP encapsuler dans une trame ethernet, en fait nous allons recevoir un pacquet pouvant etre coder par la structure suivante : struct recvpacquet { struct ethhdr eth; /* l'entete d'une trame ethernet voir struct iphdr ip; /* l'entete d'un datagramme ip voir struct tcphdr tcp; /* l'entete d'un pacquet tcp voir struct data[8000]; /* emplacement des donnees */ } A) structure ethhdr #################### La structure ethhdr (en-tete ethernet) ne nous interesse pas ici, elle est definie dans B) structure iphdr ################### La structure iphdr (en-tete ip) est definie dans : struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 ihl:4, version:4; #elif defined (__BIG_ENDIAN_BITFIELD) __u8 version:4, ihl:4; #else #error "Please fix " #endif __u8 tos; __u16 tot_len; __u16 id; __u16 frag_off; __u8 ttl; __u8 protocol; __u16 check; __u32 saddr; __u32 daddr; }; Si on fait un plan du header ip, ca donne cela(20 octets) : ------------------------------------------------------------------- |version| ihl | tos | tot_len | | 4 | 4 | 8 | 16 | |_______|_______|_______________|_________________________________| | id | frag_off | | 16 | 16 | |_______________________________|_________________________________| | ttl | protocole | check | | 8 | 8 | 16 | |_______________|_______________|_________________________________| | saddr | | 32 | |_________________________________________________________________| | daddr | | 32 | |_________________________________________________________________| PS:le mot correspond au champ dans la structure iphdr et le chiffre a la taille de ce champ en bits. Pour savoir ce que veut dire chaque champ, lisez un livre sur le TCP/IP ou de la doc sur le ouaib. C) structure tcphdr #################### La structure tcphdr (en-tete tcp) est definit dans : struct tcphdr { __u16 source; __u16 dest; __u32 seq; __u32 ack_seq; #if defined(__LITTLE_ENDIAN_BITFIELD) __u16 res1:4, doff:4, fin:1, syn:1, rst:1, psh:1, ack:1, urg:1, res2:2; #elif defined(__BIG_ENDIAN_BITFIELD) __u16 doff:4, res1:4, res2:2, urg:1, ack:1, psh:1, rst:1, syn:1, fin:1; #else #error "Adjust your defines" #endif __u16 window; __u16 check; __u16 urg_ptr; }; Le plan de l'entete tcp (20 octets): ________________________________________________________________________ | source | dest | | 16 | 16 | |__________________________________|___________________________________| | seq | | 32 | |______________________________________________________________________| | ack_seq | | 32 | |______________________________________________________________________| | doff | RESERVE | FLAGS | window | | 5 | 5 | 6 | 16 | |_________|_________|______________|___________________________________| | check | urg_ptr | | 16 | 16 | |__________________________________|___________________________________| Les champs en MAJUSCULES ne sont pas des champs de la structure. D)Ceux que nous allons recevoir: ############################### Voici le schema de la structure recvpacquet commente plus haut: . \ . . . header ethernet(Je detaille pas parce que on en . . . a pas besoin) . | ethhdr . . / 14 octets |_______________________________________________________________|/ |version| ihl | tos | tot_len |\ | 4 | 4 | 8 | 16 | \ |_______|_______|_______________|_______________________________| | | id | frag_off | | | 16 | 16 | | iphdr |_______________________________|_______________________________| | 20 octets | ttl | protocole | check | | | 8 | 8 | 16 | | |_______________|_______________|_______________________________| | | saddr | | | 32 | | |_______________________________________________________________| | | daddr | | | 32 | / |_______________________________________________________________|/ | source | dest |\ | 16 | 16 | \ |_______________________________|_______________________________| | | seq | | | 32 | | |_______________________________________________________________| | | ack_seq | | tcphdr | 32 | | 20 octets |_______________________________________________________________| | | doff | RESERVE | FLAGS | window | | | 5 | 5 | 6 | 16 | | |_________|_________|__________|________________________________| | | check | urg_ptr | | | 16 | 16 | / |______________________________|________________________________|/ | |\ | | \ | Donnees ( 8000 octets de donnees, ca devrait | | | suffire ) | | data . . .(8000 . . . octets) . . / . ./ Donc pour recevoir les trames ethernet, nous allons faire un truc du genre : struct recvpacquet buffer; int octets_recus; . . /* mise en "promiscuous" mode de la carte reseau */ . /* et creation de la socket (sock) */ . octets_recus = read(sock,(struct recvpacquet *)&buffer,sizeof(struct recvpacquet)); La valeur de octets_recus sera le nombre d'octets que la fonction read() a lu. La structure recvpacquet(trame ethernet) sera rempli. ATTENTION: Lors de la reception, nous allons recevoir 2 octets en plus que la structure recvpacquet (Je ne sais pas pourquoi mais je vais essayer de le decouvrir), ce qu'il fait qu'il faudra creer des pointeurs sur les structures tcphdr et iphdr pour pouvoir les utiliser. Ca va donner un truc un peu comme ca: struct recvpacquet buffer; int octets_recus; struct tcphdr *tcp; /* Pointeur sur une structure tcphdr */ struct iphdr *ip; /* Pointeur sur une structure iphdr */ tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2); /*decalage 2 octets */ ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2); /*" " " "*/ . . /* mise en "promiscuous" mode de la carte reseau */ . /* et creation de la socket (sock) */ . octets_recus = read(sock,(struct recvpacquet *)&buffer,sizeof(struct recvpacquet)); printf("Ip version %d\n", ip->version); /*Utilisation les pointeurs qu'on a creer */ 3 - Traiter les pacquets recus ------------------------------- Bon alors maintenant, on arrive a la partie qui necessite le plus de lignes de codes : filtrer et traiter les trames ethernet que l'on a recu. Des le debut, on verifie que c'est bien un datagramme TCP/IP, en testant la valeur du champ protocole du paquet IP : /*** Exemple ***/ if (ip->protocole != 6) { /*** protocole 6 = TCP (voir /etc/protocols) ***/ fprintf(stderr, "Datagramme non TCP/IP :( \n"); return -1; } Apres, on regarde l'adresse source et l'adresse de destination, le port de destination et le port source(Pas tres interressant). Ensuite, on regarde les flags pour voir si c'est un debut de connection flags : SYN Une fin de connection flags : FIN ou RST. ...etc... Et enfin, on regarde les donnees pour les formater et les inscrires dans un fichier par exemple. ... Y a d'autres choses mais je ne vais pas tout vous dire qunad meme ...:) 4 - Exemple ------------ Voici l'exemple d'un sniffer TRRRREEESSSS basique : ---------------------------Cut here ------------------------------------------ /* Exemple de sniffer Basic coder par RaPass pour Mezza. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include int setup_interface(char *device) /*met la carte reseau en mode promiscuous */ { int fd; struct ifreq ifr; int s; //open up our magic SOCK_PACKET fd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); if(fd < 0) { perror("cant get SOCK_PACKET socket"); exit(0); } //set our device into promiscuous mode strcpy(ifr.ifr_name, device); s=ioctl(fd, SIOCGIFFLAGS, &ifr); if(s < 0) { close(fd); perror("cant get flags"); exit(0); } ifr.ifr_flags |= IFF_PROMISC; s=ioctl(fd, SIOCSIFFLAGS, &ifr); if(s < 0) perror("cant set promiscuous mode"); return fd; } int main() { int sock, octets_recus, lendata, j, i=0; unsigned char *so, *dest; struct recvpacquet /* Les trames seront stockes la avant d'etre traiter */ { struct ethhdr eth; struct iphdr ip; struct tcphdr tcp; char data[8000]; } buffer; struct iphdr *ip; /* Utiliser pour le decalage de 2 octets */ struct tcphdr *tcp; /* " " " " */ char *data; /* " " " " */ ip=(struct iphdr *)(((unsigned long)&buffer.ip)-2); /* On pointe 2 octets AVANT */ tcp=(struct tcphdr *)(((unsigned long)&buffer.tcp)-2); /* " " " */ so = (unsigned char *)&(ip->saddr); /* Utiliser pour affciher les adresses ip */ dest = (unsigned char *)&(ip->daddr); /* " " " " */ sock = setup_interface("eth0");/* mise en mode promiscuous */ while(1) /*Il aurait fallut gerer les signaux pour fermer la socket en partant :( */ { octets_recus = read(sock, (struct recvpacquet *)&buffer, sizeof(struct recvpacquet)); i++; printf("\n-------Packet %d---------\n", i); printf("Adress ::: %u.%u.%u.%u -----> %u.%u.%u.%u\n", /* On affcihe les adresses ips */ so[0],so[1],so[2],so[3], dest[0],dest[1],dest[2],dest[3]); printf("Port ::: %d -------> %d\n",ntohs(tcp->source), ntohs(tcp->dest)); printf("TTL ::: %d\n", ip->ttl); printf("Flags ::: SYN=%d | ACK=%d | RST=%d | FIN=%d\n",tcp->syn, tcp->ack, tcp->rst, tcp->fin); data=(char *)(((unsigned long)buffer.data)-2); /*La gestion des donnees est foireuse */ lendata = octets_recus - 2 - (sizeof(struct ethhdr)+sizeof(struct iphdr)+sizeof(struct tcphdr)); printf("DATA\n"); /* C juste un exemple */ for (j=0 ; j <= lendata ; j++) printf("%c", data[j]); } } -----------------Cut Here---------------------------------------------------- Ze end. -------- Et ben voila...J'espere que j'aurais apris quelque chose a quelqu'un. Je vais me mettre a coder un sniffer(un de plus!) sous X(j'me suis pas acheter un livre sur GTK+ pour rien..). Peut-etre qu'il y aura les sources dans le prochain numero de Mezza. Si vous avez des remarques, des questions... mailto : RaPass@linuxstart.com ou Venez sur #mezza (Undernet) Greetz : A VlaLarleF(pseudo a la con!) A tout Mezza(Longue vie a nous!) Et a vi qui m'aura cramer les yeux pendant toutes ces nuits :) RaPass