Ce chapitre va présenter différents outils pour développer vos propres utilitaires de sécurité. Une connaissance du language C est requise. Une solide connaissance réseau est aussi demandée.
Le système d'exploitation utilisé est Linux avec un compilateur GCC. Nous allons étudier 2 bibliothèques permettant la création d'utilitaires réseaux : libnet et libpcap.
La première, Libnet, est développée par Mike D. Schiffman. C'est une bibliothèque opensource (donc gratuite). Libnet permet de fabriquer et d'injecter facilement des paquets sur un réseau. Un grand nombre de protocoles est supporté.
Note | |
---|---|
Note importante : La version de Libnet décrite est la version 1.1. De grosses modifications faites sur le code de la version 1.1 la rend incompatible avec les versions de bibliothèques antérieures. |
La deuxième est Libpcap. Elle permet de capturer les paquets transitant sur le réseau. Cette bibliothèque est maintenue par l'équipe de développement de tcpdump (présenté au Section 5.5, « L'interception des mots de passe en réseau. »).
Libnet est téléchargeable à cette adresse : www.packetfactory.net
Libpcap est téléchargeable à cette adresse : www.tcpdump.org
Pour donner une description de ces bibliothèques, nous allons étudier le développement d'un scanner de base. Ce scanner listera les ports TCP ouverts sur une machine avec une méthode de scan en port demi-ouvert (SYN scan, voir Section 2.2, « Le Scanner »). L'injection des paquets sera réalisée grâce à Libnet, la réception des réponses grâce à Libpcap.
Le programme "scanner" sera appelé en mode console et recevra deux arguments : l'interface réseau d'utilisation et l'adresse IP de la machine à scanner. Il affichera la liste des ports ouverts et se terminera.
Exemple d'utilisation :
[root@nowhere.net /root]#./scanner -i eth0 -c 192.168.1.2 Le port 21 est ouvert Le port 22 est ouvert Le port 1111 est ouvert Le port 1024 est ouvert
Mais intéressons-nous à son développement.
Le scan en port demi-ouvert consiste à envoyer un paquet TCP avec le flag SYN armé sur un port précis. Si ce port est ouvert, on recevra en réponse un paquet TCP avec le couple SYN/ACK armé.
Le programme recevra en argument, l'adresse IP de la machine à scanner et le nom de l'interface réseau (par exemple "eth0").
ATTENTION ! Le but de ce chapitre est de présenter les librairies libnet et libpcap et certaines de leurs fonctions les plus utiles d'une manière succincte. Ce chapitre n'est en aucun cas un cours de programmation réseau.
Le principe de fonctionnement du programme est simple ; il sera constitué de deux fonctions : la première permettra d'envoyer les paquets (sur les 16000 premiers ports) et la deuxième de les recevoir. Ces deux fonctions seront activées en parallèle dans deux processus distincts (grâce à l'appel de la fonction fork()).
Je présenterai les deux fonctions utilisées pour intercepter et envoyer :
Pour recevoir (void ReceptionPaquet(unsigned int, u_char device) :
La fonction ReceptionPaquet permet de recevoir les paquets circulant sur le réseau. Pour cela, elle utilise la librairie libpcap. Nous utiliserons deux fonctions de cette librairie pour notre programme :
La première est la fonction pcap_t *pcap_t pcap_open_live(char *device, int snaplen, int promisc,int to_ms,char *errbuf).
Cette fonction initialise la carte réseau et renvoie un descripteur de type pcap_t sur cette interface réseau en écoute.
Le paramètre *device est un pointeur sur une chaîne de caractères contenant le nom de l'interface réseau utilisée (par exemple "eth0").
Le paramètre snaplen représente la taille maximale (en octet) d'un paquet intercepté (max=65535).
Le paramètre promisc contrôle le fonctionnement de la carte réseau. S'il est égal à 1, la carte est en mode transparent, c'est à dire qu'elle intercepte tous les paquets (même ceux qui ne lui sont pas destinés). Si cette valeur est différente de 1, la carte n'acceptera que les paquets lui étant destinés. Pour notre programme, la carte sera en mode transparent donc promisc sera égal à 1.
Le paramètre to_ms spécifie le délai (en millisecondes) d'interception de paquets. Lorsque ce délai est écoulé, la fonction se termine.
Le paramètre *errbuf est un pointeur sur une chaîne de caractères pouvant contenir une message d'erreur en cas de mauvaise ou non exécution du programme.
La deuxième fonction est la fonction u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h). Cette fonction utilise comme argument le descripteur "p" pour accéder à la carte réseau. Elle renvoie chaque paquet intercepté. C'est une fonction bloquante.
Ces deux fonctions sont toujours utilisées en série, la pcap_open_live initialise la carte réseau et renvoie un descripteur de fichier, ensuite ce descripteur de fichier est utilisé par la fonction *pcap_next qui intercepte les paquets.
Voici la fonction ReceptionPaquet :
void ReceptionPaquet(unsigned int IP_Cible, u_char *Device) /*Variable utilisée par les fonctions de libpcap (elle contiendra notre paquet*/ struct pcap_pkthdr Header ; /*Descripteur représentant l'interface réseau (retourné par la fonction pcap_open_live()*/ pcap_t *Descr ; /*Structure pour l'en tête ETHERNET*/ struct ethhdr *EtherHdr ; /*Structure pour l'en tête IP*/ struct ip *IpHdr ; /*Structure pour l'en tête TCP*/ struct tcphdr *TcpHdr ; /*Tampon pour retour d'erreur*/ char ErrBuf[LIBNET_ERRBUF_SIZE] ; /*Pointeur vers le paquet*/ u_char *Packet ; /*Descripteur pour libnet*/ libnet_t *l ; /* Initialisation pour les fonctions de libnet (obtention d'un descripteur)*/ l=libnet_init(LIBNET_RAW4,NULL,ErrBuf) ; /*Descripteur "Descr" sur l'interface réseau en écoute*/ Descr = pcap_open_live(Device,65535,1,0,ErrBuf) ; while(1) /*On recupére le paquet (il est pointé par la variable "Packet")*/ Packet = (u_char *) pcap_next(Descr,&Header) ; /*On convertit le paquet pour analyser l'en tête ETHERNET*/ EtherHdr = (struct ethhdr * ) (Packet) ; /*On verifie si le protocole est bien IP*/ if(ntohs(EtherHdr-¿h_proto)==ETH_P_IP) /*On convertit le paquet pour analyser l'en tête IP*/ IpHdr = (struct ip * ) (Packet +ETH_HLEN) ; /*On verifie si le protocole est bien TCP*/ if(IpHdr-¿ip_p==IPPROTO_TCP) TcpHdr = (struct tcphdr * ) ( Packet + ETH_HLEN + 4 * (IpHdr-¿ip_hl)) ; /* Cela sert à verifier que nous avons bien envoyé le paquet et qu'il provient bien de la machine "scannée". Ceci se base sur l'analyse des adresses IP */ if( (IpHdr-¿ip_src.s_addr==IP_Cible) && (IpHdr-¿ip_dst.s_addr== libnet_get_ipaddr4(l))) /*Pour verifier que le port d'envoi correspond au même que le notre*/ if(ntohs(TcpHdr-¿dest)==PORT_SOURCE) /*Si les flags SYN et ACK sont armés, le port est ouvert*/ if((TcpHdr-¿ack==1) && (TcpHdr-¿syn==1)) printf("Le port %d est ouvert n",ntohs(TcpHdr-¿source)) ; /*Destruction du descripteur*/ libnet_destroy(l) ;
Pour envoyer (void EnvoiPaquet(unsigned int,int)) :
La fonction EnvoiPaquet permet d'injecter les paquets sur le réseau. Pour cela, elle utilise la librairie Libnet.
Nous allons utiliser différentes fonctions de la librairie Libnet.
La première est la fonction libnet_t *libnet_init(int injection_type, char *device, char *err_buf).
Cette fonction permet d'initialiser une interface d'injection des paquets. Elle traite trois arguments :
-
Le premier injection_type définit le type d'interface (niveau ethernet, IP ou TCP). Pour notre programme, une injection au niveau IP suffira (valeur LIBNET_RAW4).
-
Le deuxième argument *device est un pointeur sur la chaîne de caractère contenant le nom de l'interface réseau qui sera sollicitée (ex : eth0), la valeur NULL conviendra car libnet choisira automatiquement une carte réseau.
-
Le dernier argument *err_buf est utilisé pour les messages d'erreur (comme libpcap). Elle renvoie un pointeur sur l'interface d'injection de type libnet_t.
Les fonctions libnet_ptag_t libnet_build_tcp(...) et libnet_autobuild_ipv4(...) sont utilisées pour construire les en-têtes TCP et IP. Elles prennent comme arguments les différentes valeurs constituants les en-têtes TCP et IP (numéro de port, de séquences ... pour TCP, adresses IP, types de protocoles pour IP). Elle renvoient toutes les deux un descripteur de type libnet_ptag_t, ces fonctions doivent toujours respecter l'ordre TCP puis IP (sens couche application vers couche réseau ou physique). Ces fonctions prenent en argument le pointeur sur l'interface d'injection (pointeur renvoyé par la fonction libnet_init).
La fonction int libnet_write(libnet_t) injecte le paquet. La fonction void libnet_destroy(libnet_t) détruit l'interface crée par libnet.
Voici la fonction 1,0,0EnvoiPaquet :
void EnvoiPaquet( /*IP Machine Cible*/ unsigned int IP_cible, /*Port Destination*/ int port_dest) /*Variables pour les fonction de libnet*/ char ErrBuf[LIBNET_ERRBUF_SIZE] ; libnet_t *l ; libnet_ptag_t Tag ; /*Pour initialiser et obtenir un descripteur*/ l=libnet_init(LIBNET_RAW4,NULL,ErrBuf) ; /*Pour construire l'en tête TCP*/ Tag=libnet_build_tcp( PORT_SOURCE, /*Port Source*/ port_dest, /*Port destination*/ 0, /*N° Séquence*/ 0, /*N° Acquitement*/ TH_SYN, /*Demande de connexions*/ 4096, /*Taille de fenêtre*/ 0, /*Somme de contrôle*/ 0, /*Pointeur d'urgence*/ LIBNET_TCP_H, /*Taille en tête*/ (u_char *) (NULL),/*Pointeur vers les données*/ 0, /*Taille des données*/ l, 0) ; /*Pour construire l'en tête IP*/ Tag=libnet_autobuild_ipv4( LIBNET_IPV4_H+LIBNET_TCP_H, /*Taille du paquet*/ IPPROTO_TCP, /*Protocole*/ IP_cible, /*Adresse IP de la machine Cible*/ l) ; /*Pour envoyer le paquet*/ libnet_write(l) ; /*Pour détruire le descripteur*/ libnet_destroy(l) ;
La fonction main() :
La fonction main() traite les arguments de la ligne de commande, lance un processus enfant dans lequel, elle utilisera la fonction ReceptionPaquet et attendra une seconde pour le lancement du processus et de la fonction ReceptionPaquet puis exécutera 16000 fois la boucle d'envoi de paquets (16000 ports).Elle attendra encore 5 secondes pour le traitement des réponses et terminera le programme.
Voici la fonction main :
extern char *optarg ; extern int optind ; extern int opterr ; int main(int argc,char *argv[]) /*Pour avoir les paramètres de la ligne de commande*/ static char optstring[]="i :c :" ; int optch ; char *Device ; /*Variable d'itération*/ int i ; /*Pour stocker l'adresse IP*/ u_int32_t IP_Cible ; /*Variable qui va reçevoir le PID du processus enfant*/ int Pid ; /*Pour traiter les paramètres de la ligne de commande*/ if(argc ¡ 5) printf(" nscanner -i interface -c IP Cible n") ; return 0 ; while((optch= getopt(argc,argv,optstring)) !=EOF) switch(optch) /*Pour le nom de l'interface*/ case 'i' : Device = (char *) (malloc(strlen(optarg)*sizeof(char))) ; strncpy(Device,optarg,strlen(optarg)) ; break ; /*Pour l'adresse IP de la machine cible*/ case 'c' : IP_Cible = inet_addr(optarg) ; break ; default : printf(" nscanner -i interface -c IP Cible n") ; return 0 ; /*On lançe le processus enfant (récuperation et analyse des paquets*/ Pxml:id=fork() ; if(Pxml:id==0) ReceptionPaquet(IP_Cible,Device) ; /*On attend une seconde*/ sleep(1) ; /* On envoie les paquets sur les 16000 premiers ports*/ for(i=0 ;i¡16000 ;i++) EnvoiPaquet(IP_Cible,i) ; /*On détruit le processus enfant*/ sleep(5) ; kill(Pid,SIGINT) ; return 0 ; }