--------------------------------------------------------------------------------------- IV. Sniffing avancé - lsniff.c Li0n7 --------------------------------------------------------------------------------------- [ Introduction ] Nous avons étudié dans l'issue précédente un sniffer de basse facture se contentant d'afficher les différents champs de l'header principale de la trame Ethernet ( les adresses MAC src et dest). Cette fois-ci, nous n'allons plus nous contenter de sniffer une couche, mais quatre couches! Comme préambule à l'OS fingerprinting, nous implémenterons une fonction de prise d'empreinte de la pile TCP/IP à distance. [ Sommaire ] I. Description II. Programmation III. Code source IV. Conclusion I. Description : ________________ Lors de la programmation du sniffer, nous manipulerons des paquets de manière très fine ; il est donc obligatoire de connaitre la structure d'un paquet dans son intégralité. Ici le snaplen est de 68 octets, mais il varie énormement selon les protocoles et les options des en-têtes. Cette structure est générique, elle commence par la couche liaison avec l'en-tête principale de la trame, ou en-tête encapsulateur, contenant les adresses MAC sources et destination. Ensuite l'header IP suit (proto mode non-connecté), sa taille peut varier, mais elle est au minimum de 20 octets sans les options. Puis l'header du protocole de communication utilisé (TCP, UDP, ICMP, protos semi-connectés). Là encore la taille en octet varie selon le protocole et les options définies (de 8 à 20 octets). Je vous renvoie étudier les en-têtes des différents protocoles présentés dans l'issue précédente. Le dernier en-tête contient les données transmises à travers le réseau, elles peuvent renfermer, en l'occurence des informations censés êtres protégés, qu'elles soient cryptés ou non. [ Structure d'un paquet ] 14 20 20 14 +---------+--------------+--------------+------>>------+ | | | | | | EN-TETE | EN-TETE | EN-TETE | DONNEES | | TRAME | IP | PROTOCOLE | PROTOCOLE | | | | | | +---------+--------------+--------------+------<<------+ <------------------------------------------------------> Trame Ethernet <--------------------------------------------> Datagramme IP <-----------------------------> Paquet TCP, ICMP, UDP [ Filtres ] lsniff capture tout type de traffic, mais il peut se contenter, selon la demande de l'utilisateur, de n'afficher qu'un type précis de paquet, en limitant l'affichage lourd des différents headers. Lors de chaque initialisation d'une session de capture lsniff, vous devez préciser le protocole à sniffer avec l'option -p. Attention, le protocole est identifié via son numéro propre (voir etc/protocols). Par exemple, pour sniffer des paquets tcp vous lancerez lsniff comme ceci ./lsniff -ieth0 -p6. Eth0 étant l'interface utilisée par la plupart des systèmes. Pour afficher tout type de paquet, il suffit de rentrer en paramètre -p0. Il est également possible de récupérer le numéro d'un protocole en utilisant son nom et vice-versa. Pour cela, ajouter l'argument -z suivit du nom ou du numéro. $ ./protos tcp 1 1: tcp ( TCP ) numéro: 6 1: icmp ( ICMP ) numéro: 1 [ Interfaces ] Il a été implémenté deux types d'interface avec lsniff : une interface simple (TCPdump-like), et une autre complexe et entièrement configurable, structurée autour de la gestion des différents header de la trame réseau. Pour utiliser l'interface simple par défaut, n'entrer aucun argument, sinon -e (pour afficher l'en-tête encapsulateur dans sa totalité), -t(pour en-tête IP complet), -p (affichage de tous les champs de l'entête de $proto) et enfin -d pour capturer les données du protocole. Si par hasard toutes ses options doivent êtres combinées, lancer lsniff avec l'argument -x. => interface simple ./lsniff -ieth0 -p0 -s 1.4.6 99.236.109.178:5000 > 125.2.2.5:80 S win 65535 674719801>674719801(0) => interface complexe ./lsniff -ieth0 -p0 -x ---------->>> Trame réseau numéro: 1 --[ETHERNET HEADER] Host Source Ethernet: 01:40:06:00:00:00 Host Distant Ethernet: d6:10:00:40:14:54 Type: 61884 --[IP HEADER] Adresse IP source: 99.236.109.178 Adresse IP destination: 125.2.2.5 IHL:5, VERSION:4, TOS:0, TOT_LEN:10240, ID:30384, TTL:255, PROTOCOL: 6 --[TCP HEADER] Port source: 5000 Port destination: 80 SEQ: 674719801, ACK_SEQ: 0, D_OFF: 1, RES1: 0, FIN:0, SYN: 1 RST: 0, PSH:0, ACK:0, URG:0, WINDOW: 65535, URG_PTR:0 Une petite description de l'interface simple s'impose : 1.4.6 99.236.109.178:5000 > 125.2.2.5:80 S win 65535 674719801>674719801(0) "1.4.6" : 1: trame réseau numéro 1, 4: version du protocole IP, 6: numéro d'identification du protocole (ici TCP) "99.236.109.178:5000 > 125.2.2.5:80" : host source(99.236.109.178) envoyant un paquet du port 5000 au port 80 de 125.2.2.5 "S win 65535" : S signifie que le flag SYN pointe sur 1, chaque flag est représenté par la première lettre de son nom. 674719801>674719801(0): numéro_de_séquence_entrant numéro_de_séquence_sortant (taille du champ données du protocole) [ Manipulation des protcoles ] Depuis la création du Réseau des réseaux, les protocoles font partie-intégrante de son fonctionnement. Ils régissent le réseau et évitent ainsi toute anarchie. Ils instorent un système de communication universel et obéissent à un modèle : le modèle OSI définie en 1984. Lors de la création d'un paquet, celui-ci se voit attribuer différentes couche ; ce procédès répond plus commnunément à l'appelation d'encapsulation (ou tunneling). Voici le modèle actuel : (7) Application => gère le transport des informations entre les programmes (6) Présentation => s'occupe de la mise en forme du texte et des conventions d'affichage (5) Session => s'occupe de l'établissement de la gestion des coordinations de communication (4) Transport => gère la remise correcte des informations (3) Réseau => détermine les routes de transport et du transfert de messages (2) Liaison de données => s'occupe du codage, de l'adressage, de la transmission des informations (1) Physique => gère les connections matérielles Ainsi, nous savons que chaque datagramme subit une encapsulation rigoureuse avant son envoi sur le réseau. A l'arrivée des données sur l'ordinateur distant, il y a décaspulation, soit le processus inverse, c'est ainsi que l'on peut dire que les couches dialogues virtuellement entres elles même s'ils elles n'ont de contacts qu'avec les couches inférieurs et supérieurs qui les entourent. Il est utile de noter que contrairement aux idées reçues, les protocoles les plus répandues TCP/IP et UDP/IP, pour les nommer, n'utilisent que 5 des 7 couches du modèle OSI, la communication est alors modifiée : *encapsulation* *processus inverse* --------------- --------------- | couche | | couche | | application | <------------------> | application | | | | | --------------- --------------- | couche | | couche | | transport | <------------------> | transport | | TCP | | TCP | --------------- --------------- | couche | | couche | | réseau IP | <------------------> | réseau IP | | | | | --------------- --------------- | couche | | couche | | liaison | <------------------> | liaison | | Ethernet | | Ethernet | --------------- --------------- || || ><----------------------------------------------------------------------------------------->< Couche physique (support matériel) Ci-dessous une liste non exhaustive de protocoles, consultables via etc/protocoles. # /etc/protocols: # $Id: protocols,v 1.1.1.1 1999/12/27 21:11:58 chmouel Exp $ # # Internet (IP) protocols # # from: @(#)protocols 5.1 (Berkeley) 4/17/89 # # Updated for NetBSD based on RFC 1340, Assigned Numbers (July 1992). ip 0 IP # internet protocol, pseudo protocol number icmp 1 ICMP # internet control message protocol igmp 2 IGMP # Internet Group Management ggp 3 GGP # gateway-gateway protocol ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'') st 5 ST # ST datagram mode tcp 6 TCP # transmission control protocol egp 8 EGP # exterior gateway protocol pup 12 PUP # PARC universal packet protocol udp 17 UDP # user datagram protocol hmp 20 HMP # host monitoring protocol xns-idp 22 XNS-IDP # Xerox NS IDP rdp 27 RDP # "reliable datagram" protocol iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4 xtp 36 XTP # Xpress Tranfer Protocol ddp 37 DDP # Datagram Delivery Protocol idpr-cmtp 39 IDPR-CMTP # IDPR Control Message Transport ipv6 41 IPv6 # IPv6 ipv6-route 43 IPv6-Route # Routing Header for IPv6 ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6 ipv6-crypt 50 IPv6-Crypt # Encryption Header for IPv6 ipv6-auth 51 IPv6-Auth # Authentication Header for IPv6 ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6 ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6 ipv6-opts 60 IPv6-Opts # Destination Options for IPv6 rspf 73 RSPF #Radio Shortest Path First. vmtp 81 VMTP # Versatile Message Transport ospf 89 OSPFIGP # Open Shortest Path First IGP ipip 94 IPIP # Yet Another IP encapsulation encap 98 ENCAP # Yet Another IP encapsulation [ OS FINGERPRINTING ] - [ TCP FIN PROBE] J'ai implémenté une petite fonction de prise d'empreinte de la pile à distance. Bien sûr, ce test est basique, l'OS ne peut pas être déterminé de façon précise, mais le but était d'imposer un préambule à cette notion. La technique utilisée fut très en vogue, mais se démode, pour être efficace il aurait fallut la coupler à un TCP SYN SAMBLING et un hypothétique ICMP MSG ERROR QUENCHING. Ici, nous allons nous contenter de faire un SYN PROBE. Le principe est simple : certains OS, conformement à la RFC 793, sont censés ne PAS répondre à un paquet envoyé contenant un flag FIN, mais certaines implémentations des stacks tcp/ip d'OS comme MS Windows, BSDI, CISCO, HP/UX, MVS et IRIX ont été grandement modifiées et répondent par un flag RST, ce qui implique un défaut de sécurité. Pour éxécuter une prise d'empreinte de la pile TCP/IP d'un host distant, il suffit d'utiliser l'option -o . [ Introduction à la détection du sniffing ] Bien que la sécurité d'un réseau contre le sniffing ne soit pas le but de cette article, il ne va pas sans dire que ce sujet est extrémement intéréssant, les techniques étant nombreuses. La majorité des administrateurs de réseaux volumineux (plusieurs centaines de machines) n'hésitent pas à les commuter, bien qu'en général les raisons soient plus souvents techniques et dans une optique de gain de performance. Un réseau étant commuté, les paquets envoyés par les machines atteignent directement la distination désirée, mais ce n'est pas pour autant que le pirate ne pourra pas renifler ce réseau. Une parade connue est l'hijacking ARP (avec les attaques de type Man In the Middle) qui fera surement l'objet d'un article prochainement ; ceci consiste à altérer les tables de correspondances IP/MAC ADDRESSES de manière à se glisser litté- -ralement entre deux hôtes réseaux communiquants, ainsi le sniffing est possible. La détection d'un pirate sniffant est aussi très ludique, nous distinguons trois techniques majeures : ¤ La première consiste à générer une fausse connection réseau sur un LAN puis surveiller les requêtes DNS qui tentent de résoudre l'adresse falsifiée. ¤ La seconde réside dans un bug de pilote Ethernet Linux. En effet, une machine tournant en mode promiscuous n'arrive pas à vérifier l'adresse Ethernet des paquets qu'elle reçoit, cette validation est effectuée au niveau IP et doit être normalement abandonnée si cette adresse ne correspond pas à celle de l'hôte, au niveau matériel. Ainsi, si un paquet est reçu avec une adresse Ethernet falsifiée mais une IP correspondant à celle de l'hôte qui le reçoit, alors une réponse sera émise. Il suffit donc d'envoyer un quelconque paquet construit avec une adresse Ethernet altérée à tous les hôtes d'un réseau pour identifier les sniffers qui répondent. ¤ Enfin une technique très peu utilisée permettant de dénicher un hôte surveillant le réseau, est le calcul de la latence de ce dernier, c'est à dire le nombres de commandes pings qu'il émet. Plus ce nombre est éle- -vé, plus il répondra lentement aux pings envoyés. Cette méthode est donc simple : évaluer dans un premier temps la latence d'un hôte en lui envoyant un ping puis en calcuant le temps de réponse, puis, générer ensuite un traffic réseau extrémement important et, dans un dernier temps envoyer une seconde requête ping en mesurant le temps de réponse. Si les deux temps diffèrent (de manière importante) alors l'hôte surveillait le réseau. Notez que les variations de latence d'un hôte peut dépendre de nombreux facteurs. II. Programmation : ___________________ Autant le dire tout de suite, lsniff n'a pas un code facile à étudier, pour la bonne et simple raison que le publier n'était à l'origine pas mon but. Nous allons étuider successivement chacune de ses fonctions, de l'ouverture de l'interface au point d'entrée, en passant par le stack fingerprint. Aussi, je vous demanderais d'être très attentifs aux nombreuses variables int (faisant office de bools ici) que j'ai utilisées pour manipuler les différentes interfaces. [ Structures ] Les structures utilisées sont nombreuses et volumineuses. Chaque protocole IP, TCP, UDP, ICMP dispose d'une structure qui lui est propre, destinée à faciliter l'inter- -activité avec les différents champs de chaque header. L'header ethernet présente aussi une structure générique : struct pseudohdr { // pseudo header utilisé pour le calcul du checksum unsigned long saddr; unsigned long daddr; char useless; unsigned char protocol; unsigned short length; } pseudo; struct ether_header *hdr; struct iphdr *ip; struct tcphdr *tcp; struct icmphdr *icmp; struct udphdr *udp; struct sockaddr_in rhost; // hôte distant pour le stack fingerp struct hostent *source; // pointeur sur adresse source struct hostent *cible; // pointeur sur adresse destination struct recvpaquet // manipulation des différents headers d'un paquet { struct ethhdr eth; // header ethernet (adresses MAC) struct iphdr ip; // header IP struct tcphdr tcp; // header TCP struct icmphdr icmp; // header ICMP struct udphdr udp; // header UDP char data[8000]; // header données du protocole du champ précédent } buffer; int size; // snaplen 1. Overture interface & fingerprint : _____________________________________ [ Ouvre_interface ] Je ne vais pas m'étendre, cette fonction permet d'ouvrir l'interface réseau propre au système utilisé (eth0 par exemple). Cette fonction prend pour argument le nom de l'interface, passé en paramètre, entrée par l'utilisateur. Puis on met la carte réseau en mode promiscuous. ------8<--------------------------------------------------------------------- int ouvre_interface(char *name) { struct sockaddr addr; /* déclaration de notre structure générique d'adressage */ struct ifreq ifr; /* structure de manipulation des trames */ int sockfd; /* déclaration de notre socket */ sockfd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); /* définition de notre socket*/ if(sockfd<0) return -1; memset(&addr, 0, sizeof(addr)); /* on remplit de 0 le ptr addr */ addr.sa_family=AF_INET; strncpy(addr.sa_data, name, sizeof(addr.sa_data)); /* on bind via notre socket sockfd, et ainsi on "écoute" */ /* les trames réseaux circulant */ if(bind(sockfd, &addr, sizeof(addr)) !=0 ){ close(sockfd); return -1; } memset(&ifr,0,sizeof(ifr)); strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); /* le point d'entrée ioctl permet l'éxécutions d'opérations particulières */ /* sur des périphériques autres que lectures/ écriture, ici nous accèdons */ /* aux fonctions de notre socket */ if(ioctl(sockfd, SIOCGIFHWADDR, &ifr)<0){ close(sockfd); return -1; } /* si l'interface n'est pas ethernet alors on quitte */ if(ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER){ close(sockfd); return -1; } /* accès aux options des sockets */ memset(&ifr,0,sizeof(ifr)); strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) <0) { close(sockfd); return -1; } ifr.ifr_flags |= IFF_PROMISC; if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) { close(sockfd); return -1; } return sockfd; // retourne la socket } ------8<--------------------------------------------------------------------- [ os_fingerprint ] Cette fonction va éxécuter une prise d'empreinte de tcp/ip stack en remote. Pour cela, elle va forger un paquet tcp avec un quelconque bit de contrôle (différent du SYN/ACK) initialisé à 1, puis va attendre une hypothétique réponse d'un OS contenant un flag RST. ------8<--------------------------------------------------------------------- int os_fingerp(unsigned long ssource, unsigned long scible) { char *paquet, *fip, *packet, *buffer; int sock; // Allocation dynamique de mémoire pour le paquet et le buffer packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr)); buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr)); ip = (struct iphdr *) packet; tcp = (struct tcphdr *) (packet + sizeof(struct iphdr)); // remplissage des champs de l'header IP ip->ihl = 5; // nombre de DWORDS constituant le datagramme IP ip->version = 4; // Version 4 ip->tos = 0; // Type Of Service ip->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr); ip->id = (random()); // ID aléatoire ip->ttl = 255; // Time To Live ip->protocol = IPPROTO_TCP; // Protocole de communication ip->saddr = ssource; // Adresse source (la vraie ;-)) ip->daddr = scible; // Adresse destination // remplissage des champs du pseudo header destiné à calculer la somme de contrôle pseudo.saddr = ip->saddr; pseudo.daddr = ip->daddr; pseudo.useless = htons(0); pseudo.protocol = IPPROTO_TCP; pseudo.length = sizeof(struct tcphdr); // remplissage des champs de l'header TCP tcp->source = htons(5000); // port source tcp->dest = htons(80); // port destination tcp->seq = htonl(SEQ); // 0x28376839 tcp->ack_seq = htonl(0); tcp->doff=sizeof(tcp)/4; tcp->res1 = 0; tcp->fin = 1; // bit de contrôle fin activé tcp->syn = 0; tcp->rst = 0; tcp->psh = 0; tcp->ack = 0; tcp->urg = 0; tcp->window = htons(65535); // taille assez volumineuse tcp->urg_ptr = htons(0); // calcul des sommes de contrôle tcp->check = in_cksum((unsigned short *)&pseudo,sizeof(struct tcphdr) + sizeof(struct pseudohdr)) ; ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); // initialisation de notre socket if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW))<0){ // en cas d'erreur on quitte perror("Erreur lors de la creation du socket"); return -1; } else { // sinon on définie l'hôte distant rhost.sin_family = AF_INET; // domaine rhost.sin_port = tcp->dest; // port distant rhost.sin_addr.s_addr = ip->daddr; // adresse distante // on envoie le paquet if((sendto(sock,packet,ip->tot_len,0,(struct sockaddr *)&rhost, sizeof(struct sockaddr)))<0){ // en cas d'erreur on quitte perror("Erreur lors de l'envoie du paquet FIN"); }else{ // sinon on lance une boucle de lecture des paquets passant sur le réseau printf("Paquet FIN envoye sur port: %d!\n", ntohs(tcp->dest)); size = read(sock, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); read_loop(sock, 0, 0, 0, 0, 0, 1); } } // on ferme la socket close(sock); // statistiques printf("\n>-=+=+=+=+=+=- Statistiques -=+=+=+=+=+=-<\n\n"); return 0; } ------8<--------------------------------------------------------------------- 2. Lecture des paquets : ________________________ [ read_loop ] Voici l'artère principale du sniffer. Elle s'articule autour d'une boucle, qui à chaque paquet reçu sur la carte Ethernet passée en mode promiscuous, affiche selon les désirs de l'utilisateur et les paramêtres, l'interface et les différents champs du paquet capté. Notez que pour proposer une programmation plus structurée donc plus facile d'accès et de manipulation, une structure, recvpaquet, est utilisée pour l'affichage des paquets ; en effet elle contient tous les headers du paquet reçu. ------8<--------------------------------------------------------------------- int read_loop(int sockfd, int protos, int x, int sinter, int sip, int sdat, int os) { struct hostent *hote; // pointeur sur l'adresse distante char buf[1792], *donnees, *proto, *flag[5], *itype, *ios; int fromlen, c, i=0, j, datal, stype; struct protoent * protocole; unsigned char *s, *d, *ftype; // Allocation dynamique de mémoire ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2); // header IP tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2); // header TCP icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2); // header ICMP udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2); // header UDP proto = (char *)malloc(1024); // allocation de mémoire pour le ptr proto for (j=0; j <= 5; j++){ flag[j] = (char *)malloc(1024); // allocation de mémoire pour les pointeurs sur les flags (utilisé pour différencié les flags contenus dans l'header tcp lors de l'affichage en mode simple) } itype = (char *)malloc(1024); // allocation de mémoire pour icmp champs type ios = (char *) malloc(1024); // allocation de mémoire pour le char *ios qui stockera l'adresse de tcp->rst (pour l'os fingerp) s = (unsigned char *)&(ip->saddr); // adresse source d = (unsigned char *)&(ip->daddr); // adresse destination while(1){ i++; size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); // on lit les paquets passant sur notre carte réseau if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; if (os == 0){ // Si l'utilisateur ne désire pas éxécuter de prise d'empreinte de stack // alors on stocke les adresses des différents champs tcp/ip pour leur affichage ultérieure sprintf(proto, "%d", ip->protocol); // protocole au dessus de l'en-tête IP sprintf(flag[0], "%d", tcp->fin); // bit de contrôle: fin sprintf(flag[1], "%d", tcp->syn); // bit de contrôle: syn sprintf(flag[2], "%d", tcp->rst); // bit de contrôle: rst sprintf(flag[3], "%d", tcp->psh); // bit de contrôle: psh sprintf(flag[4], "%d", tcp->ack); // bit de contrôle: ack sprintf(flag[5], "%d", tcp->urg); // bit de contrôle: urg if(sinter==1){ // si l'interface complexe a été choisie printf("---------->>> Trame réseau numéro: %i\n", i); hdr=(struct ether_header *)buf; if(x==1){ // si l'utilisateur a demandé l'affichage de l'header Ethernet printf("--[ETHERNET HEADER]\n"); printf("Host source ethernet: "); // on affiche l'adresse MAC source for(c=0; c < ETH_ALEN; c++) printf("%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]); printf("\nHost distant ethernet: "); for(c=0; c < ETH_ALEN; c++) // on affiche l'adresse MAC destination printf("%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]); // on affiche le type printf("\nType: %i\n",hdr->ether_type); } if (sip==1){ // si l'utilisateur a demandé l'affichage de l'header IP printf("--[IP HEADER]\nAdresse IP source: %u.%u.%u.%u\nAdresse IP destination: %u.%u.%u.%u\n", s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]); // affichage de l'ip Source et destination printf("IHL: %d, VERSION: %d, TOS: %d, TOT_LEN: %d, ID: %d, TTL: %d, PROTOCOLE: %d\n", ip->ihl, ip->version, ip->tos, ip->tot_len, ip->id, ip->ttl, ip->protocol); // affichage des différents champs composant l'header IP } // si l'utilisateur a demandé la capture de paquets TCP ou de tout type de paquet if(((atoi(proto))==6)&&(protos==6 || protos==0)){ printf("--[TCP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(tcp->source), ntohs(tcp->dest)); printf("SEQ: %d, ACK_SEQ: %d, D_OFF: %d, RES1: %d, FIN: %d, SYN: %d, RST: %d\n", ntohl(tcp->seq), ntohl(tcp->ack_seq), tcp->doff, tcp->res1, tcp->fin, tcp->syn, tcp->rst); printf("PSH: %d, ACK: %d, URG: %d, WINDOW: %d, URG_PTR: %d\n", tcp->psh, tcp->ack, tcp->urg, ntohs(tcp->window), ntohs(tcp->urg_ptr)); // longueur du champ en-tête données protocole datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); donnees = (char *)(((unsigned long)buffer.data)-2); // header data if(sdat==1){ // on affiche les données si cela a été rentré en argument printf("--[DONNES]\n"); for(j=0; j <= datal; j++) printf("%c", donnees[j]); } // si l'utilisateur a demandé la capture de paquets ICMP ou de tout type de paquet } else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) { printf("--[ICMP HEADER]\nType: %d\nCode: %d\n", icmp->type, icmp->code); printf("echo.id: %d\nEcho.seq: %d\nChecksum: %d\n", icmp->un.echo.id, icmp->un.echo.sequence, icmp->checksum); // si l'utilisateur a demandé la capture de paquets UDP ou de tout type de paquet } else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)){ printf("--[UDP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(udp->source), ntohs(udp->dest)); printf("Len: %d\nChecksum: %d\n", ntohs(udp->len), udp->check); donnees = (char *)(((unsigned long)buffer.data)-2); // header data // longueur du champ en-tête données protocole datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); if(sdat==1){ // on affiche les données si cela a été rentré en argument printf("--[DONNEES]\n"); for(j=0; j <= datal; j++) printf("%c", donnees[j]); } } } else { // affichage interface simple, il y a plus de boulot au niveau de la gestion des champs // si l'utilisateur a demandé la capture de paquets TCP ou de tout type de paquet if(((atoi(proto))==6)&&(protos==6 || protos==0)){ datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); if(datal < 0) datal = 0; // affichage interface simple printf("%i.%d.6(tcp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d", i, ip->version, s[0],s[1],s[2],s[3], ntohs(tcp->source), d[0],d[1],d[2],d[3], ntohs(tcp->dest)); if((atoi(flag[0]))==1) printf(" F"); // si flag FIN=1 alors on affiche "F" if((atoi(flag[1]))==1) printf(" S"); // si flag SYN=1 alors on affiche "S" if((atoi(flag[2]))==1) printf(" R"); // si flag RST=1 alors on affiche "R" if((atoi(flag[3]))==1) printf(" P"); // si flag PSH=1 alors on affiche "P" if((atoi(flag[4]))==1) printf(" A"); // si flag ACK=1 alors on affiche "A" if((atoi(flag[5]))==1) printf(" U"); // si flag URG=1 alors on affiche "U" printf(" win %d %d>%d(%i)", ntohs(tcp->window), ntohl(tcp->seq), ntohl(tcp->seq)+datal, datal); // si l'utilisateur a demandé la capture de paquets ICMP ou de tout type de paquet } else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) { stype = icmp->type; // on stock icmp->type dans l'int stype ftype = icmp_type(stype); // puis on identifie le champ type de l'header ICMP // affichage interface simple printf("%i.%d.1(icmp) %u.%u.%u.%u > %u.%u.%u.%u icmp(%d): %s", i, ip->version, s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], stype, ftype); // si l'utilisateur a demandé la capture de paquets UDP ou de tout type de paquet } else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)) { datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); if (datal<0) datal = 0; // affichage interface simple printf("%i.%d.17(udp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d udp(%i) %d:%d", i, ip->version, s[0],s[1],s[2],s[3], ntohs(udp->source), d[0],d[1],d[2],d[3], ntohs(udp->dest), datal, ntohs(udp->len), udp-> check); // Si le protocole est différent d'ICMP, TCP ou UDP, alors on effectue un affichage limité } else if ((atoi(proto))==protos || protos==0) { // affichage du numéro de la trame, de la version d'IP, du numéro du protocole, de son nom, de l'adresse source et de l'adresse destination. printf("%i.%d.%i(%s) %u.%u.%u.%u > %u.%u.%u.%u", i, ip->version, ip->protocol, getprotobynumber(ip->protocol), s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]); } } printf("\n"); } else { // Sinon en cas d'OS Fingerprinting on affiche adresse source, adresse destination, le flag syn, fin, rst et ack printf("%u.%u.%u.%u > %u.%u.%u.%u > %d %d %d", s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], tcp->syn, tcp->fin, tcp->rst, tcp->ack); sprintf(ios, "%d", tcp->rst); // notre ptr ios pointe sur l'adresse tcp->rst if((atoi(ios))==1){ // si le flag RST était sur 1 alors on affiche les statistiques printf("Système distant probable: WINDOWS, BSDI, CISCO, HP/UX, MVS, IRIX\n"); } } } // Nombre de paquets sniffés printf("Nombre de paquets sniffés: %d\n", i); } ------8<--------------------------------------------------------------------- [ icmp_type ] Cette fonction va nous servir à identifier le champ type de l'header icmp en donnant une courte description. (pour un tableau exhaustif des différents types icmp, référez-vous à mon article sur le smurfing icmp). Cette fonction prend un argument de type int (icmp->type) et renvoit un pointeur sur la description donnée. ------8<--------------------------------------------------------------------- char *icmp_type(int type){ char *rtype; switch (type){ // on switch le type // puis selon la valeur de l'int type, on affiche la description correspondante case 0: rtype = "echo reply"; break; case 3: rtype = "unreachable host"; break; case 4: rtype = "Source quench"; break; case 5: rtype = "Redirection"; break; case 6: rtype = "Alternative host adress"; break; case 8: rtype = "Echo request"; break; case 9: rtype = "Router advertissement"; break; case 10: rtype = "Router solicitation"; break; case 11: rtype = "Time exceded"; break; case 12: rtype = "Parameter problem"; break; case 13: rtype = "Timestamp request"; break; case 14: rtype = "TImestamp reply"; break; case 15: rtype = "Information request"; break; case 16: rtype = "Information reply"; break; case 17: rtype = "Adress mask request"; break; case 18: rtype = "Adress mask reply"; break; case 30: rtype = "Traceroute"; break; case 31: rtype = "Conversion error"; break; case 32: rtype = "Dynamic redirection"; break; case 35: rtype = "Mobile registration request"; break; case 36: rtype = "Mobile registration reply"; break; case 39: rtype = "SADP"; break; case 40: rtype = "Photuris"; break; } return rtype; // on renvoit un pointeur sur le type } ------8<--------------------------------------------------------------------- [ getaddr ] Cette fonction ne prend qu'un seul argument de type char représentant le nom de la machine, et permet de vérifier la présence de cet hôte sur le réseaux. Elle retourne l'adresse réseau de ce dernier au format u_long. ------8<--------------------------------------------------------------------- unsigned long getaddr(char *sname){ struct hostent * hip; // notre structure hostent (voir issue précédente) hip = gethostbyname(sname); // présence de l'hôte if (!hip){ perror("Adresse invalide"); // en c exit(1); } return *(unsigned long *)hip -> h_addr; // pointeur sur l'adresse au format // réseau de la structure hostent } ------8<--------------------------------------------------------------------- 3. Point d'entrée : ___________________ Pour finir, la fonction principale qui récupère l'interface réseau passée en argument, l'interface à utiliser, le(s) protocole(s) à sniffer, la quelconque cible à la prise d'empreinte de pile, le protocole à identifier ou encore les headers à afficher ! J'ai également ajouté la possiblité d'afficher les aliases/nom/numéro de chaque protocole pour permettre une interactivité plus facile avec les différentes options du sniffer. Il faut savoir qu'à chaque protocole correspond un numéro comme vous auriez pu le voir plus haut. Ils sont employés pour la communication sur le réseau. La bibliothèque C met à notre disposition deux fonctions potentielles permettant de récupérer le protocole. Tout d'abord la fonction getprotobyname() définissant ledit protocole par le biais d'une chaine string représentant son nom et enfin gethostbynumber() indiquant cette fois-ci le protocole en lui faisant correspondre un integer (voir le tableau ci-dessus). Ces fonctions sont déclarés dans l'header : struct protoent * getprotobyname(cont chat * nom); struct protoent * getprotobynumber(int numero); Il faut savoir que la structure protoent contient les membres suivants : p_proto: type int, numéro officiel du protocole (fourni dans l'odre des octets de la machine). p_name: type char *, nom officiel du protocole. p_aliases: type char **, table de chaines de caractères correspondant à d'éventuels alias, terminée par un pointeur NULL. L'ensemble des protocoles supportés par le système courant est affiché via l'appel des fonctions setprotoent(), getprotoent(), endprotoent(). La première ouvre le fichier, le seconde lis l'enregistrement et la dernière ferme le fichier. Ne pas oublier de définir l'argument seprotoent() nul, dans le cas contraire le fichier ne sera pas refermé par endprotoent() : void setprotoent(int ouvert); sruct protoent * getprotoent(void); void endprotoent(void); Après avoir déclaré l'header netdb et notre point d'entrée, nous définirons une fonction permettant l'affichage de tous les protocoles présents sur la machine. Tout ceci via le triplé des fonctions décrites ci-dessus: ------8<--------------------------------------------------------------------- for(i=0;ip_name); // Afficher le nom de chaque protocole endprotoent(); // Fermet le fichier, cette ligne est indispensable fprintf(stdout, "\n"); } } ------8<--------------------------------------------------------------------- Il s'agit désormais d'afficher les caractéristiques d'un protocole (alias, numéro, nom), via son nom ou son numéro d'identification (contenu dans etc/protocols). Notre programme devra alors être capable d'analyser le type d'arg (int ou char) et d'afficher le résultat correspondant. ------8<--------------------------------------------------------------------- if(sscanf(argv[i],"%d",&numero)==1) // Si l'argument i est un integer protocole=getprotobynumber(numero); // On cherche le protocole par son numéro else protocole=getprotobyname(argv[i]); // Sinon on s'empare du protocole via son nom fprintf(stdout,"%s :",argv[i]); if(protocole=NULL) { fprintf(stdout,"inconnu\n"); // En cas d'erreur, on continue continue; } fprintf(stdout, "%s ( ",protocole->p_name); // On affiche alors le nom du protocole for(j=0;protocole->p_aliases[j]!=NULL;j++) fprintf(stdout, "% s",protocole->p_aliases[j]); // On affiche les aliases du protocoles fprintf(stdout, ") numéro = %d \n", protocole->p_proto); // Enfin, le numéro du protocole } return (0); } ------8<--------------------------------------------------------------------- Et le voici notre fameux point d'entrée ! ------8<--------------------------------------------------------------------- int main(int argc, char **argv) { // déclaration des variables int sockfd; int proto=0, x=0, e=0, sinter=0, fip=0, sdat=0, i, j, numero; char *name, *pp, *source, *cible; unsigned long ssource, scible; struct protoent * protocole; // si le nombre d'arguments entré est insuffisant à lsniff on affiche une aide et on quitte if (argc < 3) { printf(" Ssniff - Li0n7 \n\n"); printf(" .: Presentation des arguments :. \n\n"); printf(" -i: interface carte reseau \n"); printf(" -p: type de paquets a intercepter (0 pour tout type de protocole, protos par numeros)\n"); printf(" -e: afficher ethernet header \n"); printf(" -t: afficher ip header \n"); printf(" -d: afficher donnees \n"); printf(" -x: tout afficher (ethernet header, ip header, $proto header, donnees) \n"); printf(" -s: interface simple \n"); printf(" -o : OS fingerprinting \n"); printf(" -z: description et numéro du protocole (nom ou numéro) \n\n"); exit(0); } else { // sinon on récupère nos arguments while((argc>1)&&(argv[1][0]=='-')) { switch(argv[1][1]) { case 'i': name = &argv[1][2]; // nom de l'interface break; case 'p': proto = atoi(&argv[1][2]); // protocole à sniffer break; case 'e': e = 1; // sniffer ethernet header break; case 's': sinter = 1; // affichage de tous les champs du paquet break; case 't': fip = 1; // sniffer ip header break; case 'd': sdat = 1; // sniffer data header break; case 'x': // interface complexe e = 1; sinter = 1; fip = 1; sdat = 1; break; case 'z': pp = &argv[1][2]; // récupération du nom ou numéro du protocole if(sscanf(pp,"%d", & numero) == 1) protocole = getprotobynumber(numero); // identification du protocole via son numéro else protocole = getprotobyname(pp); // identification du protocole via son nom fprintf(stdout,"%s :", pp); if(protocole == NULL) { fprintf(stdout,"inconnu\n"); // si le protocole est inconnue, on continue continue; } fprintf(stdout, "%s ( ",protocole->p_name); // affiche nom du protocole for(j=0;protocole->p_aliases[j] != NULL;j++) fprintf(stdout, "% s", protocole->p_aliases[j]); // affiche tous ses alias fprintf(stdout, ") numéro = %d \n", protocole->p_proto); // affiche numéro du protocole return -1; break; case 'o': cible = &argv[1][2]; // cible os_fingerp source = &argv[2][0]; // adresse source (la notre) scible = getaddr(cible); // résolution de l'adresse ciblée ssource = getaddr(source); // résolution de notre adresse os_fingerp(ssource, scible); // on lance l'os fingerprinting } --argc; ++argv; } } if((sockfd = ouvre_interface(name))<0){ // ouverture de l'interface fprintf(stderr, "Erreur lors de l'ouverture de l'interface\n"); return -1; } if(read_loop(sockfd, proto, e, sinter, fip, sdat, 0) < 0){ // lecture en boucles des paquets fprintf(stderr, "Erreur lors de la lecture des paquets\n"); return -1; } return 0; } ------8<--------------------------------------------------------------------- III. Code Source : __________________ ------------8<----------------------------------------------------------------------- /******************************************/ /* lsniff By Li0n7 */ /* contactez-moi: Li0n7@voila.fr */ /* L7L.FR.ST */ /* SNIFFER ETHER/IP/TCP/UDP/ICMP */ /* Copyright Li0n7 - Tous droits réservés */ /******************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SEQ 0x28376839 struct pseudohdr { unsigned long saddr; unsigned long daddr; char useless; unsigned char protocol; unsigned short length; }pseudo; struct ether_header *hdr; struct iphdr *ip; struct tcphdr *tcp; struct icmphdr *icmp; struct udphdr *udp; struct sockaddr_in rhost; struct hostent *source; struct hostent *cible; struct recvpaquet { struct ethhdr eth; struct iphdr ip; struct tcphdr tcp; struct icmphdr icmp; struct udphdr udp; char data[8000]; } buffer; int size; int ouvre_interface(char *name) { struct sockaddr addr; struct ifreq ifr; int sockfd; sockfd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); if(sockfd<0) return -1; memset(&addr, 0, sizeof(addr)); addr.sa_family=AF_INET; strncpy(addr.sa_data, name, sizeof(addr.sa_data)); if(bind(sockfd, &addr, sizeof(addr)) !=0 ){ close(sockfd); return -1; } memset(&ifr,0,sizeof(ifr)); strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); if(ioctl(sockfd, SIOCGIFHWADDR, &ifr)<0){ close(sockfd); return -1; } if(ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) { close(sockfd); return -1; } memset(&ifr,0,sizeof(ifr)); strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) <0) { close(sockfd); return -1; } ifr.ifr_flags |= IFF_PROMISC; if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) { close(sockfd); return -1; } return sockfd; } unsigned short in_cksum(unsigned short *addr, int len) { register int sum = 0; u_short answer = 0; register u_short *w = addr; register int nleft = len; while (nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *(u_char *) (&answer) = *(u_char *) w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return (answer); } int os_fingerp(unsigned long ssource, unsigned long scible) { char *paquet, *fip, *packet, *buffer; int sock; packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr)); buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr)); ip = (struct iphdr *) packet; tcp = (struct tcphdr *) (packet + sizeof(struct iphdr)); ip->ihl = 5; ip->version = 4; ip->tos = 0; ip->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr); ip->id = (random()); ip->ttl = 255; ip->protocol = IPPROTO_TCP; ip->saddr = ssource; ip->daddr = scible; pseudo.saddr = ip->saddr; pseudo.daddr = ip->daddr; pseudo.useless = htons(0); pseudo.protocol = IPPROTO_TCP; pseudo.length = sizeof(struct tcphdr); tcp->source = htons(5000); tcp->dest = htons(80); tcp->seq = htonl(SEQ); tcp->ack_seq = htonl(0); tcp->doff=sizeof(tcp)/4; tcp->res1=0; tcp->fin = 1; tcp->syn = 0; tcp->rst = 0; tcp->psh = 0; tcp->ack = 0; tcp->urg = 0; tcp->window = htons(65535); tcp->urg_ptr = htons(0); tcp->check = in_cksum((unsigned short *)&pseudo,sizeof(struct tcphdr) + sizeof(struct pseudohdr)) ; ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW))<0){ perror("Erreur lors de la creation du socket"); return -1; } else { rhost.sin_family = AF_INET; rhost.sin_port = tcp->dest; rhost.sin_addr.s_addr = ip->daddr; if((sendto(sock,packet,ip->tot_len,0,(struct sockaddr *)&rhost, sizeof(struct sockaddr)))<0){ perror("Erreur lors de l'envoie du paquet FIN"); }else{ printf("Paquet FIN envoye sur port: %d!\n", ntohs(tcp->dest)); size = read(sock, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); read_loop(sock, 0, 0, 0, 0, 0, 1); } } close(sock); printf("\n>-=+=+=+=+=+=- Statistiques -=+=+=+=+=+=-<\n\n"); return 0; } unsigned long getaddr(char *sname){ struct hostent * hip; hip = gethostbyname(sname); if (!hip){ perror("Adresse invalide"); exit(1); } return *(unsigned long *)hip -> h_addr; } char *icmp_type(int type){ char *rtype; switch (type){ case 0: rtype = "echo reply"; break; case 3: rtype = "unreachable host"; break; case 4: rtype = "Source quench"; break; case 5: rtype = "Redirection"; break; case 6: rtype = "Alternative host adress"; break; case 8: rtype = "Echo request"; break; case 9: rtype = "Router advertissement"; break; case 10: rtype = "Router solicitation"; break; case 11: rtype = "Time exceded"; break; case 12: rtype = "Parameter problem"; break; case 13: rtype = "Timestamp request"; break; case 14: rtype = "TImestamp reply"; break; case 15: rtype = "Information request"; break; case 16: rtype = "Information reply"; break; case 17: rtype = "Adress mask request"; break; case 18: rtype = "Adress mask reply"; break; case 30: rtype = "Traceroute"; break; case 31: rtype = "Conversion error"; break; case 32: rtype = "Dynamic redirection"; break; case 35: rtype = "Mobile registration request"; break; case 36: rtype = "Mobile registration reply"; break; case 39: rtype = "SADP"; break; case 40: rtype = "Photuris"; break; } return rtype; } int read_loop(int sockfd, int protos, int x, int sinter, int sip, int sdat, int os) { struct hostent *hote; char buf[1792], *donnees, *proto, *flag[5], *itype, *ios; int fromlen, c, i=0, j, datal, stype; struct protoent * protocole; unsigned char *s, *d, *ftype; ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2); tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2); icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2); udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2); proto = (char *)malloc(1024); for (j=0; j <= 5; j++){ flag[j] = (char *)malloc(1024); } itype = (char *)malloc(1024); ios = (char *) malloc(1024); s = (unsigned char *)&(ip->saddr); d = (unsigned char *)&(ip->daddr); while(1){ i++; size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; if (os == 0){ sprintf(proto, "%d", ip->protocol); sprintf(flag[0], "%d", tcp->fin); sprintf(flag[1], "%d", tcp->syn); sprintf(flag[2], "%d", tcp->rst); sprintf(flag[3], "%d", tcp->psh); sprintf(flag[4], "%d", tcp->ack); sprintf(flag[5], "%d", tcp->urg); if(sinter==1){ printf("---------->>> Trame réseau numéro: %i\n", i); hdr=(struct ether_header *)buf; if(x==1){ printf("--[ETHERNET HEADER]\n"); printf("Host source ethernet: "); for(c=0; c < ETH_ALEN; c++) printf("%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]); printf("\nHost distant ethernet: "); for(c=0; c < ETH_ALEN; c++) printf("%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]); printf("\nType: %i\n",hdr->ether_type); } if (sip==1){ printf("--[IP HEADER]\nAdresse IP source: %u.%u.%u.%u\nAdresse IP destination: %u.%u.%u.%u\n", s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]); printf("IHL: %d, VERSION: %d, TOS: %d, TOT_LEN: %d, ID: %d, TTL: %d, PROTOCOLE: %d\n", ip->ihl, ip->version, ip->tos, ip->tot_len, ip->id, ip->ttl, ip->protocol); } if(((atoi(proto))==6)&&(protos==6 || protos==0)){ printf("--[TCP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(tcp->source), ntohs(tcp->dest)); printf("SEQ: %d, ACK_SEQ: %d, D_OFF: %d, RES1: %d, FIN: %d, SYN: %d, RST: %d\n", ntohl(tcp->seq), ntohl(tcp->ack_seq), tcp->doff, tcp->res1, tcp->fin, tcp->syn, tcp->rst); printf("PSH: %d, ACK: %d, URG: %d, WINDOW: %d, URG_PTR: %d\n", tcp->psh, tcp->ack, tcp->urg, ntohs(tcp->window), ntohs(tcp->urg_ptr)); datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); donnees = (char *)(((unsigned long)buffer.data)-2); if(sdat==1){ printf("--[DONNES]\n"); for(j=0; j <= datal; j++) printf("%c", donnees[j]); } } else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) { printf("--[ICMP HEADER]\nType: %d\nCode: %d\n", icmp->type, icmp->code); printf("echo.id: %d\nEcho.seq: %d\nChecksum: %d\n", icmp->un.echo.id, icmp->un.echo.sequence, icmp->checksum); } else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)){ printf("--[UDP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(udp->source), ntohs(udp->dest)); printf("Len: %d\nChecksum: %d\n", ntohs(udp->len), udp->check); donnees = (char *)(((unsigned long)buffer.data)-2); datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); if(sdat==1){ printf("--[DONNEES]\n"); for(j=0; j <= datal; j++) printf("%c", donnees[j]); } } } else { if(((atoi(proto))==6)&&(protos==6 || protos==0)){ datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); if(datal < 0) datal = 0; printf("%i.%d.6(tcp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d", i, ip->version, s[0],s[1],s[2],s[3], ntohs(tcp->source), d[0],d[1],d[2],d[3], ntohs(tcp->dest)); if((atoi(flag[0]))==1) printf(" F"); if((atoi(flag[1]))==1) printf(" S"); if((atoi(flag[2]))==1) printf(" R"); if((atoi(flag[3]))==1) printf(" P"); if((atoi(flag[4]))==1) printf(" A"); if((atoi(flag[5]))==1) printf(" U"); printf(" win %d %d>%d(%i)", ntohs(tcp->window), ntohl(tcp->seq), ntohl(tcp->seq)+datal, datal); } else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) { stype = icmp->type; ftype = icmp_type(stype); printf("%i.%d.1(icmp) %u.%u.%u.%u > %u.%u.%u.%u icmp(%d): %s", i, ip->version, s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], stype, ftype); } else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)) { datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); if (datal<0) datal = 0; printf("%i.%d.17(udp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d udp(%i) %d:%d", i, ip->version, s[0],s[1],s[2],s[3], ntohs(udp->source), d[0],d[1],d[2],d[3], ntohs(udp->dest), datal, ntohs(udp->len), udp-> check); } else if ((atoi(proto))==protos || protos==0) { printf("%i.%d.%i(%s) %u.%u.%u.%u > %u.%u.%u.%u", i, ip->version, ip->protocol, getprotobynumber(ip->protocol), s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]); } } printf("\n"); } else { printf("%u.%u.%u.%u > %u.%u.%u.%u > %d %d %d", s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], tcp->syn, tcp->fin, tcp->rst, tcp->ack); sprintf(ios, "%d", tcp->rst); if((atoi(ios))==1){ printf("Système distant probable: WINDOWS, BSDI, CISCO, HP/UX, MVS, IRIX\n"); } } } printf("Nombre de paquets sniffés: %d\n", c); } int main(int argc, char **argv) { int sockfd; int proto=0, x=0, e=0, sinter=0, fip=0, sdat=0, i, j, numero; char *name, *pp, *source, *cible; unsigned long ssource, scible; struct protoent * protocole; if (argc < 3) { printf(" Ssniff - Li0n7 \n\n"); printf(" .: Presentation des arguments :. \n\n"); printf(" -i: interface carte reseau \n"); printf(" -p: type de paquets a intercepter (0 pour tout type de protocole, protos par numeros)\n"); printf(" -e: afficher ethernet header \n"); printf(" -t: afficher ip header \n"); printf(" -d: afficher donnees \n"); printf(" -x: tout afficher (ethernet header, ip header, $proto header, donnees) \n"); printf(" -s: interface simple \n"); printf(" -o : OS fingerprinting \n"); printf(" -z: description et numéro du protocole (nom ou numéro) \n\n"); exit(0); } else { while((argc>1)&&(argv[1][0]=='-')) { switch(argv[1][1]) { case 'i': name = &argv[1][2]; break; case 'p': proto = atoi(&argv[1][2]); break; case 'e': e = 1; break; case 's': sinter = 1; break; case 't': fip = 1; break; case 'd': sdat = 1; break; case 'x': e = 1; sinter = 1; fip = 1; sdat = 1; break; case 'z': pp = &argv[1][2]; if(sscanf(pp,"%d", & numero) == 1) protocole = getprotobynumber(numero); else protocole = getprotobyname(pp); fprintf(stdout,"%s :", pp); if(protocole == NULL) { fprintf(stdout,"inconnu\n"); continue; } fprintf(stdout, "%s ( ",protocole->p_name); for(j=0;protocole->p_aliases[j] != NULL;j++) fprintf(stdout, "% s", protocole->p_aliases[j]); fprintf(stdout, ") numéro = %d \n", protocole->p_proto); return -1; break; case 'o': cible = &argv[1][2]; source = &argv[2][0]; scible = getaddr(cible); ssource = getaddr(source); os_fingerp(ssource, scible); } --argc; ++argv; } } if((sockfd = ouvre_interface(name))<0){ fprintf(stderr, "Erreur lors de l'ouverture de l'interface\n"); return -1; } if(read_loop(sockfd, proto, e, sinter, fip, sdat, 0) < 0){ fprintf(stderr, "Erreur lors de la lecture des paquets\n"); return -1; } return 0; } ------------8<----------------------------------------------------------------------- [ Conclusion ] Voila tout, vous pouvez modifier ce programme à votre guise, dès l'instant que le nom du concepteur initial figure dans les sources. Ce sniffer est encore basique, une utilisation plus poussée de l'OS fingerprinting sera probablement ultérieurement implémentée avec peut être quelques fonctions de scanning avancées (half-scan, xmas scanning, SYN fin, NULL scanning). Restons-en ici pour le moment, l'interfafe a été beaucoup travaillée, le programme est lui même intuitif. Mais il n'est pas dénué de défaut, loin de là. Il ne capture pas les paquets envoyés avec comme IP source et destination localhost@localhost. Une fonction intéréssante serait d'enregistrer toutes les sorties de lsniff dans un fichier pour visualisation ultérieure. Et gardez en tête que c'est en programmant par plaisir que vous repousserez vos limites dans des retranchements insoupsonés. Enfin, pour compiler : $ gcc -o protos protos.c Help? Commentaires? Insultes? -> Li0n7@voila.fr Li0n7