---------------------------------------------------------------------------------------------- IX. Programmation d'un sniffer Li0n7 ---------------------------------------------------------------------------------------------- [ Introduction ] Qu'est-ce qu'un sniffer? Ils tirent leur nom du premier outil de capture du trafic réseau Sniffer Pro de Network Associates. En effet, un sniffer se contente d'analyser les différentes trames traversant le réseau pour les afficher en clair sur l'écran de l'attaquant. Pour cela, il va exploiter le mode promiscuous de la carte réseau, et ainsi visualiser le trafic réseau. Il faut savoir que tout hôte reçoit des trames et les analyse celon leur adresse MAC destinataire, ainsi il peut les ignorer ou non. L'inferface promiscuous contourne la pile TCP/IP de l'OS en laissant alors passer tous les paquets de la couche liason à l'application. La plus part des attaquants qui utilisent ce type de programme cherchent à récupérer des informations liées à l'authentification (tout protocole compris), ou d'autres types d'informations sensibles (qu'elles soient cryptés ou non). Analyse d'un analyseur ... ++++ | Partie 1 : Programmation | ++++ => Ovrir l'interface réseau => Lire les paquets => Point d'entrée Il y a plusieurs façons de programmer un sniffer. Une librairie, la lippcap est disponible à cet effet, elle inclue de nombreuses fonctions utiles destinés à simplifier la capture et la gestion des trames réseaux et interfaces réseaux. Deuxième méthode, utilisation des raw sockets, programmation socket brute, et récupération brute des trames. Nous n'allons pas exploiter la libraire pcap, du fait qu'elle est trop connue, trop utilisée, et cela ne présenterait donc aucun intérêt. Nous allons ainsi utiliser les librairies que linux nous propose par défaut, ce dernier se contente de nous fournir une interface avec la couche de liaison via son interface socket. I. Ouvrir l'interface réseau : ______________________________ Commençons par ouvrir notre interface réseau (eth0 par exemple). Cette fonction prend pour argument le nom de l'interface, passé en paramètre, entrée par l'utilisateur. 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; } 2. Lire les paquets : _____________________ On va donc créer une boucle de lecture des paquets reçus. A chaque trame analysée, l'adresse MAC source et destinataire est affichée, ainsi que le type du paquet. Cette fonction prend donc en argument notre socket crée antérieurement. Nous allons nous servir de la structure ether_header: struct ether_header { u_char ether_dhost[6]; //adresse destination u_char ether_shost[6]; //adresse source u_short ether_type; //type de trame } int read_loop(sockfd) { /* structure d'adressage */ struct sockaddr_in from; /* déclaration des variables */ char buf[1792], *ptr; /* déclaration des integers */ int size, fromlen, c; /* déclaration de notre structure permettant */ /* de lire les entêtes ethernet */ struct ether_header *hdr; while(1){ /* la variable size contiendra les paquets reçus */ size = recv(sockfd, buf, sizeof(buf),0); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; hdr=(struct ether_header *)buf; /* pour chaque paquet on affiche l'adresse ** MAC source et l'adresse Mac de destination **/ for(c=0; c < ETH_ALEN; c++){ printf("%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]); printf(" > "); for(c=0; c < ETH_ALEN; c++) printf("%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]); /* puis enfin le type de paquet */ printf(" type: %i\n",hdr->ether_type); } printf("Nombre de paquets sniffés: %d\n", c); } 3. Point d'entrée : ___________________ Pour finir, la fonction principale qui récupère l'interface réseau passée en argument : int main(int argc, char **argv) { /* notre socket */ int sockfd; /* variable contenant l'interface */ /* passé en argument */ char *name=argv[1]; if(!argv[1]){ fprintf(stderr, "Veuillez entrer une interface valide\n"); return -1; } /* on lance la fonction d'initialisation de l'interface, */ /* en cas d'erreur on quitte */ if((sockfd = ouvre_interface(name))<0){ fprintf(stderr, "Erreur lors de l'ouverture de l'interface\n"); return -1; } /* on lit les paquets, en cas d'erreur on quitte */ if(read_loop(sockfd) < 0){ fprintf(stderr, "Erreur lors de la lecture des paquets\n"); return -1; } return 0; } ++++ | Partie 2 : Code source | ++++ ------------8<----------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include 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; } int read_loop(sockfd) { struct sockaddr_in from; char buf[1792], *ptr; int size, fromlen, c; struct ether_header *hdr; while(1){ size = recv(sockfd, buf, sizeof(buf),0); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; hdr=(struct ether_header *)buf; for(c=0; c < ETH_ALEN; c++){ printf("%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]); printf(" > "); for(c=0; c < ETH_ALEN; c++) printf("%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]); printf(" type: %i\n",hdr->ether_type); } printf("Nombre de paquets sniffés: %d\n", c); } int main(int argc, char **argv) { int sockfd; char *name=argv[1]; if(!argv[1]){ fprintf(stderr, "Veuillez entrer une interface valider\n"); return -1; } if((sockfd = ouvre_interface(name))<0){ fprintf(stderr, "Erreur lors de l'ouverture de l'interface\n"); return -1; } if(read_loop(sockfd) < 0){ fprintf(stderr, "Erreur lors de la lecture des paquets\n"); return -1; } return 0; } ------------8<----------------------------------------------------------------------- [ Conclusion ] Une notion aussi simple que le sniffing ne nécéssite pas d'élargissemet inutile, mis à part le fait que je consacrerai peut être un article sur la programmation d'un sniffer via la lip pcap ultérieurement même si cela ne m'attire pas vraiment, ce qui pourrait être relativement intéréssant bien que simple et rapide. Les parades au sniffing sont simple ; le cryptage des données des trames circulant sur le réseau, bien que pouvant se montrer dans certains cas superficiel et inutile, complique la tache à un attaquant désireux de s'approprier quelqu'information sensible. Ainsi des systèmes cryptage, tel que SSH, ont vu le jour, et sont perpétuellement remis en cause en raison du manque de sécurité et de fiabilité qu'ils sont censés proposer. Les exploits fleurissent, les utilisateurs désabusés se multiplient, les sk n'en sont que plus ravis.