==Phrack Inc.== Volume 0x0c, Édition 0x40, Article #0x0d sur 0x11 |=-----------------------------------------------------------------------=| |=-------------------=[ L'Art de l'Exploitation : ]=---------------------=| |=-------------------=[ Come back sur un exploit ]=---------------------=| |=-----------------------------------------------------------------------=| |=-----------------------------------------------------------------------=| |=------------------=[ Par vl4d1m1r de Ac1dB1tch3z ]=--------------------=| |=-----------------------------------------------------------------------=| |=---------------=[ Traduit par TboWan pour arsouyes.org ]=--------------=| |=-----------------------------------------------------------------------=| Cher Underground, à partir de cette édition, The Circle of Lost Hackers a décidé de publier, dans chaque édition, un come back sur un exploit public connu depuis assez longtemps. Cette section pourrait s'appeler "autopsie d'un exploit". L'idée est d'expliquer la partie technique d'un exploit célèbre autant que son histoire, post-mortem. Nous commençons ici par l'exploit CVS "Is-modified" qui fuit en 2004. ------------------------- PRÉLUDE L'Exploitation est un Art Coder un exploit peut être un art à part entière. Pour coder un vrai exploit, vous devez avoir un contrôle total du système. Pour arriver à cette fin, nous avons souvent besoin de comprendre, analuser et de maitriser chaque partie du puzzle. Rien n'est laissé au hasard. L'art de l'exploitation est de créer un exploit générique, finalement "en un coup". Pour aller plus loins que l'exploitation pragmatique. Qu'il soit plus qu'une bête preuve de concept. Y mettre toutes vos tripes. Et tenter de contourner les techniques de protections existantes. Un bon exploit est belle oeuvre d'art, mais confinée à rester dans l'ombre. Le travail intérieur n'est connu que de ses auteurs et les rares lecteurs du code qui tenteraient de percer ses mystères. C'est pour ces derniers que cette section a été créée. Pour ceux qui ont faim des informations qui se cachent derrière le code source. C'est la seule raison derrière le "l1s3z l3 c0d3 s0urc3 l3s m3cs" de la fonction usage() dans cet exploit : forcer les gens à lire le code, d'apprécier ce qu'ils ont en main. Pas de leur donner un nouvel outil ou une nouvelle arme mais leur faire comprendre ses différents aspects techniques. Chaque exploit est construit en suivant une méthodologie particulière. Nous devons analyser profondément les possibilités des allocations mémoire avant de pouvoir en maitriser tous les paramètres, souvent à un point où même les programmeurs originels ignoraient ces aspects techniques. C'est une histoire de vous avanturer dans les méandres, la complexité de la situation, et de finalement découvrir les diverses opportunités qui s'offrent à vous. Pour voir ce que le destin peut nous offrir, les divers potentiels dont on dispose. Pour en faire quelque chose. Essayer de tirer le meilleur de la situation. Quand vous passerez cette ligne invisible, celle qui sépare la simple preuve de concept au meilleur exploit possible, celui qui vous garantit un shell à chaque fois, vous pourrez dire que la création d'une forme d'art à commencé. La joie d'admirer votre propre bout de travail qui transforme un simple écrasement de mémoie en un exploit complètement fonctionnel. C'est un joyau technique de créativité et de détermination de rendre un simple bug informatique vers son potentiel complet. Qui n'a jamais rooté un server avec l'exploit "x" ? Qui n'a jamais attendu derrière son écran, regardant les différentes étapes, attendant qu'il fasse le travail génial pour lequel il est conçu ? Mais combien de gens comprennent vraiment la dichotomie de "x2" et comment il fonctionne ? Ce qu'il se passe vraiment derrière ce qu'il se passe à l'écran, dans sa version non finale qui a été dévoilée et abusée ? Au delà du kiddie pragmatique qui veut seulement récupérer un accès, cette section aimerait être l'antre de ceux qui sont motivés par la curiosité, par une sensibilité artistique de ce genre d'exploits. Cette section n'est pas un moyen d'apprendre aux autre comment owner un serveur, mais plutôt de leur enseigner comment l'exploit fonctionne. C'est une démystification des rares exploit qui ont fuit dans le passé de l'Underground pour arriver dans le domaine publique. C'est à propos d'exploit sur-utilisé par une masse d'incompétents. Cette section est pour ceux qui peuvent voir, pas pour les gens qui ne sont capable que d'aller chercher ce qui a de la valeur. En fait, cette section est pour rendre justice aux exploits originaux. C'est un retour vers ce qui mérite vraiment l'attention. À un certain point du temps, le niveau requis pour comprendre et mener une exploitation réussie atteignat la bare de la démence. L'esprit s'est fondu dans la folie, nous avons momentanément perdu toute sorte de rationnalité et sommes entré dans un état d'illumination C'est le fanatisme des passionnés qui le mène à toute son étendue, à son extrème, démontrant qu'il est possible de transcender le bien connu, pour prouver qu'on peut toujours faire plus, c'est une histoire de repousser ses limites. C'est alors que nous entrons dans la création artistique. Non, nous ne nous en allons pas, nous restont en fait plus proche de la réalité qui se cache derrière l'exploit. Il n'y a que très peu d'exploit qui ont été rendu publics. Leurs auteurs sont généralement assez intelligents pour se les garder en privé. Malgré ça, des fuites ont lieu pour diverses raisons, mais généralement, c'est une faute de débutant. Le vrai exploit n'est pas celui qui a 34 cibles, mais une seule, à savoir toute dans une seule. Un exploit qui démare d'un simple débordement dans le tas et fonctionne contre GRsec, à distance, et avec ET_DYN sur le binaire. Vous n'utiliserez probablement cet exploit qu'une fois dans votre vie, mais la partie la plus importante est le travail accompli par les auteur pour le créer. La partie la plus importante est l'amour qu'il y ont mis. Peut-être n'apprendrez-vous rien de nouveau avec cet exploit. En fait, le vrai but n'est pas de vous donner une nouvelle technique d'exploitation. Vous êtes assez grand pour lire les manuels, trouver vos propres techniques, créer quelque chose à partir des possibilités qui s'offrent à vous, le but est simplement de rendre les éloges à ce code obscur et mystérieux délaissé par la plupart des gens, ce morceau de code qui a été dévoilé mais reste malcompris. Une rubrique avec l'esprit Underground, le vrai, pour l'expert et l'amoureux de l'art. Pour celui qui peut voir. ----------------------------------- L'exploit CVS "Is_Modified" vl4d1m1r of ac1db1tch3z vd@phrack.org 1 - Vue d'ensemble 2 - Histoire de l'exploit 3 - L'exploitation Linux : utiliser le vaudou malloc  4 - Quelques mots sur l'exploitation sous BSD 5 - Conclusion --[ 1 - Vue d'ensemble Nous allons, à travers cet article, vous montrer comment l'exploitations sous Linux a été rendue possible, et ensuite, étudier le cas de BSD. Les deux techniques d'exploitation sont différentes et elles mènent toutes deux à un scénario générique et "en un coup". Souvenez-vous que le code à trois ans. Je sais que depuis, avec la glibc 2.3.2, le flag MAIN_ARENA est apparu, la macro FRONTLINK a été retirée et on vit l'ajout d'une nouvelle liste chainée, la "fast_chunks". Donc, depuis la version 2.3.5, la macro UNLINK() a été patchée pour éviter des primitives "écriture de 4 octets n'importe où". Dernier mais pas des moindres, sur la majorité des systèmes, le tas est randomisé par défaut, en même temps que la pile. Mais ce n'était pas le cas au moment de cet exploit. Le but de cet article, comme il a déjà été expliqué plus haut, n'est pas de vous enseigner une nouvelle technique mais plutôt de vous expliquer les techniques de l'époque pour exploiter ce bug. --[ 2 - Histoire de l'exploit Ce bug a été trouvé initialement par [CENSURÉ]. Une première preuve de concept a été codée par kujikiri de ac1db1tch3z en 2003. L'exploit était fonctionnel mais uniquement pour des cibles particulières. Il n'était pas fiable parce que de tous les paramètres du contexte de l'exploitation n'avaient pas été pris en compte. Le principal avantage du code était qu'il pouvait s'identifier lui-même au serveur CVS et déclancher le bug, ce qui représente une partie importante dans le développement d'un exploit. Le bug a ensuite été présenté à d'autres membres de ac1db1tch3z. C'est à ce moment qu'on décida finalement de coder un exploit vraiment fiable pour être utilisé dans la nature. Une première version a été codée pour Linux. Elle était générique mais elle avait besoin d'une trentaine de connections pour réussir. Cette première version de l'exploit soumettait quelques adresses au serveur CVS pour déterminer si elles étaient valides ou non en regardant si le serveur avait craché ou pas. Alors, un autre membre porta l'exploit pour les plateformes *BSD. Il naquit alors un exploit générique et "en un coup". Comme challenge, j'essaya d'arriver au même résultat pour la version Linux, et ma perséverance fut gagnante. Pendant ce temps, un troisième membre trouva une fonctionnalité intéressante dans le CVS, qui ne sera pas présentée ici, qui nous permettait de bruteforcer les trois paramètres obligatoires nécessaire à une exploitation réussie : le cvsroot, le login et le mot de passe. Ça m'a pris une nuit de passion (rien de sexuel) pour réunir les trois morceaux de code en un seul, et le résultat fut cvs_freebsd_linux.c, qui fut dévoilé plus tard. Un autre membre de l'Underground coda plus tard une version solaris, mais sans la généricité ni "en un coup". Cet exploit ne sera pas présenté ici. Ce bug, en fait, a été "découvert" plus tard par Stefan Esser et dévoilé par e-matters. Nous avons des doutes que Stefan Esser ai trouvé lui-même exactement le même bug qui était déjà connu dans l'Underground. Même s'il ne l'a pas fait, il s'est racheté plus tard en auditant le code source de CVS avec un de ses collègues et en trouvant un certain nombre d'autres bugs. Ça prouve qu'il est capable de trouver des bugs, peu importe. Ce code a finallement été rendu public par [CENSURÉ] qui l'a signé par "The Axis of Elitness", et s'est vanté d'avoir déjà rooté toutes les cibles intéressante disponibles. Ce n'était pas une grande perte, même si ça a fait un pincement au coeur de voir publiquement que des serveurs CVS opensource avaient été compromis. --[ 3 - L'exploitation Linux : utiliser le vaudou malloc La faille originelle était un simple débordement dans le tas. En fait, il était possible d'écraser le tas avec des données sous notre contrôle, et même d'insérer des caractères non-alphanumériques sans restriction de longueur du buffer. C'était un scénario typique. En plus, et c'est ce qui est merveilleux avec les serveurs CVS, en analysant les différentes possibilités, nous avons découvert qu'il était assez facile de forcer quelques appels à malloc() avec des tailles arbitraires et de choisir ceux sur lesquels appeller free(), avec peu de restrictions. Le truc marrant est que, quand j'ai d'abord codé la version Linux de l'exploit, je ne savais pas qu'il était possible d'écraser l'espace mémoire avec des données complètement arbitraires. Je pensait que les seuls caractères qui pouvaient écraser la mémoire étaient 'M' et 0x4d. Je n'avais pas assez analysé le bug parce que j'essayais de trouver rapidement un vecteur d'exploitation intéressant avec les informations que j'avais déjà en main. Donc, la version Linux exploitait le bug comme un simple débordement avec des caractères 0x4d. La première difficulté qu'on rencontre avec le tas est qu'il est assez instable pour diverses raisons. Beaucoup de paramètres changent l'agencement de la mémoire, comme le nombre d'allocations mémoires déjà effectuées, l'adresse IP du serveur et d'autres paramètres internes du serveur CVS. En conséquent, la première étape du processsu est d'essayer de normaliser le tas et de le placer dans un état sur lequel nous avons un contrôle total. Nous devons savoir exactement ce qu'il se passe sur la machine distante : pour être sûr de l'état du tas. Une petite analyse des possibilités que le tas nous offre nous révèle les choses suivantes : J'ai du analyser diverses possibilités d'allocations mémoires offertes par le serveur CVS. Heureusement, le code était assez simple. J'ai rapidement découvert, en analysant les appels à malloc() et free() que je pouvais allouer des buffers avec la commande "Entry". La fonction qui accomplis ça est serve_entry, le code est assez direct : static void serve_entry (arg) char *arg; { struct an_entry *p; char *cp; [...] cp = arg; [...] p = xmalloc (sizeof (struct an_entry)); cp = xmalloc (strlen (arg) + 2); strcpy (cp, arg); p->next = entries; [1] p->entry = cp; entries = p; } Dans cette fonction, qui prend comme paramètre un pointeur vers une chaine que nous contrôlons, il y a une allocation mémoire de la structure suivante : struct an_entry { struct an_entry *next; char *entry; } ; Ensuite, on va allouer de la mémoire pour le paramètre et l'assigner au champ "entry" de la structure "an_entry" qu'on a déjà définie, comme vous pouvez le voir en [1]. Cette structure est alors ajoutée à la liste chainée des entrées, suivie par la variable globale "struct an_entry * entries". Donc, si nous sommes d'accord que des petites structures "an_entry" seront allouées entre nos buffers contrôlés, nous pouvons utiliser ce vecteur pour alouer de la mémoire quand on veut. Maintenant, si nous voulons faire des appels à free(), nous pouvons utiliser la commande CVS "noop" qui appelle la fonction "server_write_entries()". Voici un extrait du code de cette fonction : static void server_write_entries () { struct an_entry *p; struct an_entry *q; [...] for (p = entries; p != NULL;) { [...] free (p->entry); q = p->next; free (p); p = q; } entries = NULL; } Comme vous pouvez le voir, toutes les entrées allouées précéement sont maintenant libérées. Notez que quand nous parlons ici d'"entrées", nous nous référons aux paires de structures an_entry avec son champ ->entry que nous contrôlons. Au vu du fait que tous les buffers que nous avons alloués seront libérés, cette technique nous va bien. Notez qu'il y a d'autres possibilités moins restrictives, mais celle-ci nous convient suffisement. Donc, nous savons maintenant comment allouer des buffers contenant des données arbitraires, même des caractères non-alphanumériques, et aussi comment les libérer. Revenons maintenant à la faille initialle que nous n'avons pas encore décrite. La commande vulnérable est "Is_Modified" et la fonction est la suivante : static void serve_is_modified (arg) char *arg; { struct an_entry *p; char *name; char *cp; char *timefield; for (p = entries; p != NULL; p = p->next) { [1] name = p->entry + 1; cp = strchr (name, '/'); if (cp != NULL && strlen (arg) == cp - name && strncmp (arg, name, cp - name) == 0) { if (*timefield == '/') { [...] cp = timefield + strlen (timefield); cp[1] = '\0'; while (cp > timefield) { [2] *cp = cp[-1]; --cp; } } *timefield = 'M'; break; } } } Comme vous pouvez le voir, en [2], après avoir ajouté une entrée avec la commande "Entry", il est possible d'ajouter des caractères 'M' à la fin des entrées précédement insérées dans la liste chainée. C'était possible pour les entrées de notre choix. Le code est assez explicite, je ne rentrerai donc pas plus dans les détails. Nous avons maintenant toutes les informations nécessaire pour coder un exploit fonctionnel. Immédiatement après avoir établis une connection, la méthode pour normaliser le tas et le mettre dans un état connu consiste à utiliser la commande "Entry". Avec cette commande particulière, nous pouvons ajouter des buffers de tailles arbitraires. La fonction fill_heap() fait celà. La macro MAX_FILL_HEAP donne le nombre maximum de trous que nous pouvons trouver dans le tas. Elle est initialisée à une grosse vleur, pour anticiper toute surprise. Nous commençons par allouer beaucoup de gros buffers pour remplir la majorité des troucs. Ensuite, nous continuons en allouant beaucoup de petits buffer pour remplir les plus petit trous qu'il resterait. A ce moment, nous n'avons plus de trous dans notre tas. Maintenant, si nous nous asseyons et réfléchissons un peut, nous nous rendons compte que le schéma du tas ressemble un peu à ce qui suit : [...][an_entry][buf1][an_entry][buf2][an_entry][bufn][top_chunk] Note : Pendant le développement de l'exploit, j'ai modifier le code de malloc pour ajouter mes propres fonctions que j'ai préchargé avec LD_PRELOAD. Cette version modifiée me génère différents plans du tas pour m'aider à le déboguer. Notez que quelques hackers utilisent des simulateurs de tas pour en connaitre son état pendant le processus de développement. Ces simulateurs de tas peuvent être de simples scripts gdb ou quelque chose utilisant libncurses. Tout outil qui peut représenter l'état du tas est utile. Une fois que la connection est établie et la fonction fill_heap() appelée, nous connessons l'agencement exact du tas. Le challenge était alors de corrompre un chunk de malloc, insérer un faux chunk et faire un appel à free() pour lancer la macro UNKINK() avec fd et bk sous notre contrôle. Ça nous permettrait d'écrire 4 octets arbitraires n'importe où en mémoire. C'est assez facile à faire quand vous avez le tas dans un état prévisible. Nous savons qu'on peut faire déborder le buffer "an_entry->entry" qu'on veut. Nous allons inévitablement écraser ce qu'il y a après ce buffer, soit le top chunk ou la prochaine structure "an_entry" si nous en avons alloué une précédement avec "Entry". Nous allons tenter d'utiliser la deuxième car nous ne voulons pas corrompre le top chunk. Remarque : À partir de maintenant, puisque la macro UNLINK contient des vérifications de sécurité, nous utiliseriont plutôt un débordement du top chunk et appelerions set_head() pour exploiter le programme, comme expliqué dans un autre article de cette édition. Pratiquement, nous savons que les en-tête des chunks se trouvent juste avant l'espace mémoire alloué. Concentrons nous sur la partie intéressante de la mémoire au moment du débordement : [struct malloc_chunk][an_entry][struct malloc_chunk][buf][...][top_chunk] En appellant la fonction "Is_Modified" avec le nom de l'entrée que nous pouvons corrompre, nous allons écraser la structure an_entry qui se trouve juste après le buffer courant. Donc, l'idée est d'écraser le champ "size" d'une structure an_entry, pour qu'il semble plus gros et quand free va calculer l'offset vers le prochain chunk, il va directement tomber dans la partie contrôlée du champ ->entry de la struc an_entry. Donc, nous n'avons besoin que d'ajouter un "Entry" avec un faut chunk malloc au bon offset. #define NUM_OFF7 (sizeof("Entry ")) #define MSIZE 0x4c #define MALLOC_CHUNKSZ 8 #define AN_ENTRYSZ 8 #define MAGICSZ ((MALLOC_CHUNKSZ * 2) + AN_ENTRYSZ) #define FAKECHUNK MSIZE - MAGICSZ + (NUM_OFF7 - 1) L'offset est FAKECHUNK. Résumons le processus au point où nous en sommes : 1. La fonction fill_heap() remplis tous les trous dans le tas en envoyant beaucoup d'entrées grâce à la commande Entry... 2. On ajoute 2 entrées : la première appelée "ABC" et l'autre nommée "dummy". le champ ->entry de "ABC" sera débordé pour modifier le malloc_chunk correpondant à dummy. 3. Nous appelons la fonction "Is_Modified" avec "ABC" en paramètre, beaucoup de fois d'affilée jusqu'à toucher le champ taille du malloc_chunk. Ça a pour effet d'ajouter "M" à la fin du buffer, en dehors de ses bornes. Dans le champs ->entry de l'entrée dummy nous avons un faux malloc_chunk à l'offset FAKECHUNK. 4. Si nous appellons maintenant la fonction "noop", elle va avoir pour effet de lancer free() sur les "entrées" de la liste chainée. En partant de la fin, l'entrée "dummy", et sa structure an_entry associée, l'entrée "ABC" et sa structure associée. Finalement, toutes les structures "an_entry" que nous avons utilisé pour remplir les trous dans le tas seront libérées. Donc, toute la magie à lieu pendant la libération du an_entry de dummy. Le vaudou malloc exact est comme ceci : Nous avons écrasés le champ "size" du chunk malloc de la structure "an_entry" suivant notre "ABC" avec des caractères 'M'. À partir d'ici, si nous faisont un free() sur la structure "an_entry" qui a son champ "size" corrompu, free() va tenter de récupérer le prochain chunk mémoire à l'adresse de chunk + 'M'. Ça nous amènera directement dans un buffer que nous contrôlons, c'est à dire dummy. En conséquent, si nous inséront un faux chunk au bon offset, nous sommes capables d'écrire 4 octets n'importe où en mémoire. À partir d'ici, 90% du travail est déjà fait ! Remarque : En pratique, ce n'est pas assez de ne créer qu'un seul faux prochain chunk. Vous devez être sur qu'un deuxième prochain chunk est disponible aussi. EN fait, DLmalloc fait des vérifications sur l'octet PREV_INUSE du prochain chunk pour vérifier que le prochain chunk est libre ou utilisé. Le problème est que nous ne pouvons pas mettre de '\0' dans le faux chunk, nous devons donc mettre une taille négative, pour être sur que le prochain chunk après le prochain chunk est avant le premier chunk. En pratique, ça marche et j'ai utilisé cette technique beaucoup de fois pour coder des débordements dans le tas. Allez voir la macro SIZE_VALUE dans le code de l'exploit pour plus d'information. Sa valeur est -8. Maintenant, nous allons creuser un peu plus profondément dans l'exploit. Regardons la fonction detect_remote_os(). Voici son code : int detect_remote_os(void) { info("Guessing if remote is a cvs on a linux/x86...\t"); if(range_crashed(0xbfffffd0, 0xbfffffd0 + 4) || !range_crashed(0x42424242, 0x42424242 + 4)) { printf(VERT"NO"NORM", assuming it's *BSD\n"); isbsd = 1; return (0); } printf(VERT"Yes"NORM" !\n"); return (1); } Dans cette technique, nous lancons une opération d'écrasement sur une adresse toujours valide. Cet endroit sera une haute adresse dans la pile, par example 0xbfffffd0. Si le serveur répond correctement, ça veut dire qu'il n'a pas crashé. S'il n'a pas craché malgré l'overflow, ça veut dire que soit l'appel à UNLINK a fonctionné (c'est à dire que nous sommes sous Linux avec une pile mappée en dessous de 0xc0000000) ou que l'appel à UNLINK n'a pas eu lieu (= pas Linux). Pour le vérifier, nous alons essayer d'écrire dans une adresse invalide ou non mappée, comme 0x42424242. Si le serveur crashe, alors, nous sommes sur que l'exploit fonctionne correctement et que nous sommes sous linux. Si ce n'est pas le cas, nous passons à l'exploitation sous FreeBSD. À partir de maintenant, la seule chose que nous sommes capables de faire est d'effectuer un appel à UNLINK de manière fiable et d'être sûr que tout fonctionne correctement. Nous devons maintenant prendre ceci au sérieux, et nous pencher sur le processus d'exploitation. Généralement, pour que l'exploitation réussisse avec ce genre de vulnérabilité, nous devons connaître l'adresse du shellcode et l'adresse d'un pointeur de fonction à écraser. En creusant plus dans le problème, il est toujours possible de rendre un exploit fonctionnel avec une seule adresse au lieu de deux. Il peut même être possible de le rendre fonctionnel sans lui donner aucune adresse mémoire ! Voici la technique pour réaliser cette prouesse. En fait, nous sommes capables d'allouer une infinité de buffers à la suites les uns des autres, pour corrompre leur en-têtes de chunks et de les passer ensuite à free() grâce à server_write_entries(). Être capable de le faire signifie que nous pouvons exécuter plus d'un appel à UNLINK, et c'est ce qui va faire la différence. Être capable d'écraser plus d'une adresse mémoire est une technique utilisée fréquement dans les exploits de débordement dans le tas et rend généralement l'exploit générique. Dans les lignes suivantes, je vais expliquer comment ce comportement peut nous mener à la création d'une fonction memcpy_remote(), qui prend les mêmes arguments que la célèbre fonction memcpy() avec l'exception qu'elle écrit dans l'espace mémoire d'un processus exploité. Quand nous sommes capables de lancer autant de UNLINK que nous voulons, nous verrons qu'il est possible de changer le scénario en une primitive "écrire n'importe quoi n'importe où". Quel est le bénéfice de pouvoir faire ceci ? Si nous pouvons écrire ce que nous voulons à l'adresse que nous voulons, sans contrainte de taille, on peut copier le shellcode en mémoire. Nous allons l'écrire à une adresse vraiment basse de la pile, et je vous expliquerai pourquoi plus tard. Pour savoir quelles adresses écraser, nous allons écraser la majorité de la pile avec une adresse pointant au début de notre shellcode. De cette manière, nous écraseront le pointeur d'instruction sauvegardé lors de l'appel à free() et nous obtiendrons le contrôle de %eip. Tout l'art de cette exploitation réside dans l'utilisation avancée de la macro UNLINK. Nous allons rentrer dans les détails, mais avant, souvenez-vous de l'utilité de la macro UNLINK. La macro UNLINK retire une entrée de la double liste chainée. En fait, le pointeur "prev" du chunk suivant celui qu'on veut retirer est remplacé par le pointeur "prev" du chunk qu'on retire. Le pointeur "next" du chunk précédent celui qu'on retire est aussi remplacé par le pointeur "next" du chunk qu'on retire. Souvenez vous du fait que seul les chunks malloc libérés sont dans la double liste chainée, qui sont ensuite groupé dans des binlists. Le champ "prev" est appellé BK et se trouve à l'offset 12 d'un chunk malloc. Le champ "next" s'appelle FD et se trouve à l'offset 8 du chunk malloc. On obtient alors les macros suivantes : #define CHUNK_FD 8 #define CHUNK_BK 12 #define SET_BK(x) (x - CHUNK_FD) #define SET_FD(x) (x - CHUNK_BK) Si nous voulons écrire 0x41424344 à l'adresse 0x42424242, nous devons appeller UNLINK de la manière suivante : UNLINK (SET_FD(0x41424344), SET_BK(0x42424242)). Le truc est qu'on veut écrire "ABCD" à l'adresse 0x42424242, mais UNLINK écrira à la fois à 0x42424242 et à 0x41424344. "ABCD" n'est pas une adresse valide. La solution pour mitiger ce problème est d'écrire un caractère à la fois. Nous écrirons donc "A", puis "B", puis "C" et ensuite "D" jsuqu'à ce qu'il ne reste rien à écrire. Pour le faire, nous avons besoin d'une plage de 0xFF caractères que nous sommes disposés à jeter. Elle est facile à obtenir. En fait, si nous prenons des adresses vraiment hautes dans la pile, nous nous retrouverons en train d'écraser des variables d'environnement qui ont été stockées les premières dans la pile. À l'époque, nous avions écrit cet exploit pour des piles mappées en dessous de l'espace noyau/ utilisateur, qui était à 0xc0000000. L'adresse exacte que j'avais choisie était 0xc0000000 - 0xFF. En gros, si nous voulons écrire "ABCD" à l'adresse 0xbfffd000, nous allons devoir exécuter les appels à UNLINK suivants : UNLINK (UNSET_FD(0xbfffd000), UNSET_BK(0xbfffff41)) (0x41 étant l'équivalent en hexa de 'A'). UNLINK (UNSET_FD(0xbfffd001), UNSET_BK(0xbfffff42)) (0x42 étant l'équivalent en hexa de 'B'). Et ainsi de suite... Donc, si nous sommes capables d'exécuter autant de UNLINK que nous voulons, et si nous avons une plage d'adresse de 0xFF qui peut être modifiée sans conséquences sur l'exécution du programme, alors, nous pouvons faire un "memcpy" distant. Pour résumer : 1. Nous avons normalisé le tas pour le mettre dans un état prévisible. 2. Nous avons écrasé le champ de taille d'un chunk alloué précédement pour une structure "an_entri". Quand cette "an_entry" sera libérée (par free()), l'allocateur mémoire va croire que le chunk suivant se trouve dans les données sous notre contrôle. Ce prochain faux chunk sera alors marqué comme libre et les deux blocs mémoires seront réunifiés en un seul. Malloc va alors retirer le prochain chunk de la double liste chainée des blocs libres, et il va alors lancer UNLINK, avec un FD et BK sous notre contrôle. 3. Puisque nous pouvons allouer autant d'entrées "an_entry" que nous voulons, et les livérer toutes au même moment grace à server_write_entries(), nous pouvons lancer autant de UNLINK que nous voulons. Ceci nous mène, comme on l'a dit, à la création d'une fonction memcpy_remote(), qui nous permet d'écrire ce qu'on veut ou on veut. 4. Nous utilisons la fonction memcpy_remote() pour écrire le shellcode vers une adresse très basse de la pile. 5. On écrase alors toutes les adresses dans la pile, en commencant par le haut, jusqu'à ce qu'on touche le pointeur d'un instruction sauvegardée. 6. Quand les fonctions internet qui libèrent les chunks termineront, notre shellcode sera alors exécuté. Et voilà ! Remarque : Nous avons choisi des adresses vraiment basses dans la pile, parce que même si nous touchons une adresse qui n'est pas encore mappée, ça génèrera un pagefault() et au lieu que le programme termine avec un signal 11, il va étirer la pile avec la fonction noyau expand_stack(). Cette méthode est générique aux OS. Merci bbp. --[ 4 - Quelques mots sur l'exploitation sous BSD Comme promis, voici les explications de la technique utilisée pour exploiter la version FreeBSD. Considérez qu'avec de petits changements, cet exploit va fonctionner sur d'autres systèmes d'exploitation. En fait, en échangeant le shellcode et en modifiant les hautes adresses du tas codées en dur, l'exploit est complètement fonctionnel sur tous les systèmes utilisant le PHK malloc. Cet exploit n'est pas restreint qu'à FreeBSD, un truc que les script kiddies ne savaient pas. J'aime voir ce genre de trucs dans les exploits. Ça les rends puissants pour les experts et quasiment inutiles pour les kiddies. Cette technique expliquée ici est une excellente façon de prendre le contrôle d'un processus cible, et elle aurait pu être utilisée facilement pour la version linux. L'avantage principal est que cette méthode n'utilise pas la magie vaudou, elle peut donc vous aider à contourner les vérifications de sécurité faites par le code de malloc. D'abord, la pile doit être remplie pour être dans un état prévisible, comme pour tous les exploits dans le tas. Deuxièmement, ce qu'on veut faire, en gros, est de mettre une structure contenant des pointeurs de fonctions juste derrière le buffer qu'on peut déborder, pour pouvoir écraser les pointeurs de fonction. Dans ce cas, nous écrasons complètement, et pas partielement, les pointeurs de fonction. Une fois que c'est fait, la seule chose qu'il reste à faire est d'envoyer régulièrement des gros buffers contenant le shellcode, pour être sur qu'il soit disponible dans des hautes adresses du tas. Ensuite, nous devons écraser le pointeur de fonction et de lancer un appel à cette fonction. Par conséquent, le shellcode sera lancé. En pratique, nous utilisons la commande "Gzip-stream" pour allouer un tableau de pointeurs de fonctions, à l'intérieur d'une sous-fonction de la fonction serve_gzip_stream(). Récapitulons : 1. Nous remplissons les trous de l'allocateur malloc de PHK pour que le buffer que nous allons écraser soit avant un trou dans le tas. 2. Nous allouons un buffer contenant 4 pointeurs vers le shellcode au bon endroit. 3. Nous appellons la fonction "Gzip-stream" qui alloue un tableau de pointeurs de fonctions juste dans notre trou mémoire. Ce tableau sera localisé juste après le buffer que nous allons déborder. 4. Nous lançons le débordement et écrasons les pointeurs de fonctions avec l'adresse de notre shellcode (la macro HEAPBASE dans l'exploit). Voir la variable OFFSET pour savoir de combien d'octets nous avons besoin pour déborder. 5. Avec la commande "Entry", nous ajoutons plein d'entrée contenant des nops et notre shellcode pour remplir les hautes adresses du tas avec notre shellcode. 6. Nous appelons la fonction zflush(1) qui clos le strem-gzippé et lançon un pointeur de fonction écrasé (le zfree de la structure z_stream). Et finalement, nous récupérons un shell. Si nous ne sommes pas encore root, nous regardons si un fichier de mot de passes CVS est en écriture sur l'arbre cvs complet, ce qui était le cas sur la plupart des serveurs, nous le modifions pour obtenir un compte root. Nous réexploitons le serveur avec ce compte et - oui, c'est ça - nous sommes r00t à distance. :-) --[ 5 - Conclusion Nous pensions qu'il vallait la peine de présenter l'exploit comme on l'a fait ici, pour laisser le lecteur apprendre lui-même les détails du code de l'exploitation, qui est maintenant disponible sur le domaine public, même si les auteurs ne le voulaient pas. À partir de maintenant, cette section sera inclue dans les prochaines éditions de phrack. Dans chaque édition, nous présenterons les détails d'un exploit intéressant. L'exploit sera choisi parce que son développement fut intéressant et que ses auteurs avaient une forte détermination de réussir à le créer. Ce genre d'exploit peut être compté sur les doigts de vos mains (je parle des exploits dévoilés). Avec l'espoir que vous vous amusiez à lire tout ceci... --[ 6 - Remerciements À MaXX pour son super papier sur le DL malloc.