# buffer overflow attack {{INLINETOC}} ## Description Les erreurs de Buffer overflow sont caractérisées par le fait de remplacer des fragments de mémoire du processus, qui ne devraient jamais avoir été modifiés intentionnellement ou involontairement. Le remplacement des valeurs de l'IP (Instruction Pointer), BP (Base Pointer) et d'autres causes d'exception de registres, des fautes de segmentation et d'autres erreurs se produisent. D'habitude ces erreurs mettent fin à l'exécution de l'application d'une façon inattendue. Les erreurs de Buffer overflow se produisent quand on opère sur des //buffers// de type //char//. ## Exemples ### Exemple 1 #include int main(int argc, char **argv) { char buf[8]; // buffer for eight characters gets(buf); // read from stdio (sensitive function!) printf("%s\n", buf); // print out data stored in buf return 0; // 0 as return value } Cette application très simple lit la contribution standard d'une zone de caractères et le copie dans le //buffer// de type //char//. La taille de ce //buffer// est de huit caractères. Après cela, les contenus du //buffer// sont affichés et il y a sortie d'application. Compilation de programme : rezos@spin ~/inzynieria $ gcc bo-simple.c -o bo-simple /tmp/ccECXQAX.o: In function `main': bo-simple.c:(.text+0x17): warning: the `gets' function is dangerous and should not be used. À ce stade, même le compilateur suggère que la fonction //gets()// n'est pas sûre. Exemple d 'utilisation : rezos@spin ~/inzynieria $ ./bo-simple // program start 1234 // we eneter "1234" string from the keyboard 1234 // program prints out the conent of the buffer rezos@spin ~/inzynieria $ ./bo-simple // start 123456789012 // we eneter "123456789012" 123456789012 // content of the buffer "buf" ?!?! Segmentation fault // information about memory segmenatation fault Nous nous débrouillons (mal)heureusement pour exécuter l'opération défectueuse par le programme et provoquons une sortie anormale. Analyse de problème : Le programme appelle une fonction, qui opère sur le //buffer// de type //char// et ne vérifie jamais le débordement de taille de ce //buffer//. Par conséquent, il est possible, intentionnellement ou non, de conserver plus de données dans le //buffer//, ce qui provoquera une erreur. La question suivante survient : le //buffer// conserve seulement huit caractères, donc pourquoi la fonction //printf()// en affiche douze ? La réponse vient de l'organisation du processus de la mémoire. Quatre caractères qui ont débordé du //buffer// remplacent aussi la valeur conservée dans un des registres, qui était nécessaire pour le revenir à une fonction correcte. La continuité de mémoire s'est ensuivie dans le fait d'imprimer les données conservées dans cette zone de mémoire. ### Exemple 2 #include #include void doit(void) { char buf[8]; gets(buf); printf("%s\n", buf); } int main(void) { printf("So... The End...\n"); doit(); printf("or... maybe not?\n"); return 0; } Cet exemple ressemble au premier. En plus, avant et après la fonction //doit()//, nous avons deux appels à la fonction //printf ()//. Compilation: rezos@dojo-labs ~/owasp/buffer_overflow $ gcc example02.c -o example02 -ggdb /tmp/cccbMjcN.o: In function `doit': /home/rezos/owasp/buffer_overflow/example02.c:8: warning: the `gets' function is dangerous and should not be used. Exemple d'utilisation: rezos@dojo-labs ~/owasp/buffer_overflow $ ./example02 So... The End... TEST // user data on input TEST // print out stored user data or... maybe not? Le programme entre les deux //printf ()// définis appelle l'affichage du contenu du //buffer//, qui est rempli des données entrées par l'utilisateur. rezos@dojo-labs ~/owasp/buffer_overflow $ ./example02 So... The End... TEST123456789 TEST123456789 Segmentation fault Parce que la taille du //buffer// a été définie //(char buf[8])// et qu'il a été rempli avec treize caractères de type //char//, le //buffer// déborde. Si notre application binaire est en format //ELF//, donc nous sommes en mesure d'utiliser un programme //objdump// pour analiser cela et trouver les informations nécessaires pour exploiter l'erreur de buffer overflow. Ci-dessous est la sortie produite par //objdump//. De cette sortie nous sommes en mesure de trouver les adresses, où est appellé //printf()// (0x80483d6 et 0x80483e7). rezos@dojo-labs ~/owasp/buffer_overflow $ objdump -d ./example02 080483be
: 80483be: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483c2: 83 e4 f0 and $0xfffffff0,%esp 80483c5: ff 71 fc pushl 0xfffffffc(%ecx) 80483c8: 55 push  %ebp 80483c9: 89 e5 mov  %esp,%ebp 80483cb: 51 push  %ecx 80483cc: 83 ec 04 sub $0x4,%esp 80483cf: c7 04 24 bc 84 04 08 movl $0x80484bc,(%esp) 80483d6: e8 f5 fe ff ff call 80482d0 80483db: e8 c0 ff ff ff call 80483a0 80483e0: c7 04 24 cd 84 04 08 movl $0x80484cd,(%esp) 80483e7: e8 e4 fe ff ff call 80482d0 80483ec: b8 00 00 00 00 mov $0x0,%eax 80483f1: 83 c4 04 add $0x4,%esp 80483f4: 59 pop  %ecx 80483f5: 5d pop  %ebp 80483f6: 8d 61 fc lea 0xfffffffc(%ecx),%esp 80483f9: c3 ret 80483fa: 90 nop 80483fb: 90 nop Si le deuxième appel à //printf()// informe l'administrateur sur le logout de l'utilisateur (par ex. session fermée), alors nous pouvons essayer d'omettre cette étape et finir sans appeller //printf()//. rezos@dojo-labs ~/owasp/buffer_overflow $ perl -e 'print "A"x12 ."\xf9\x83\x04\x08"' | ./example02 So... The End... AAAAAAAAAAAAu*. Segmentation fault L'application a fini son exécution avec une faute de segmentation, mais le deuxième appel à //printf()// n'avait pas de place. Quelques mots d'explication : perl -e 'print "A"x12 ."\xf9\x83\x04\x08"' - imprimera douze caractères " A " et ensuite quatre caractères, qui sont en fait l'adresse de l'instruction que nous voulons exécuter. Pourquoi douze ? 8 // size of buf (char buf[8]) + 4 // four additional bytes for overwriting stack frame pointer ---- 12 Analyse du problème : La sortie est la même comme dans le premier exemple. Il n'y a aucun contrôle sur la taille du //buffer// copié dans celui précédemment déclaré. Dans cet exemple nous remplaçons le registre d//'EIP// avec l'adresse 0x080483f9, qui est en fait un appel ret dans la dernière phase de l'exécution de programme. Comment utiliser les erreurs de buffer overflow d'une façon différente ? Généralement, l'exploitation de ces erreurs peut impacter : * application DoS * la re-exécution de fonctions * l'exécution de code (si nous sommes en mesure d'injecter le shellcode, décrit dans le document séparé) Comment les erreurs de buffer overflow errors sont faites? Ces sortes d'erreurs sont très faciles à faire. Pendant des années elles étaient le cauchemar des programmeurs. Le problème est lié aux fonctions natives de C, qui ne se soucient pas de vérifier la longueur appropriée du //buffer//. Ci-dessous est la liste de telles fonctions et, s'ils existent, leurs équivalents sûrs : * gets() -> fgets() - read characters * strcpy() -> strncpy() - copy content of the buffer * strcat() -> strncat() - buffer concatenation * sprintf() -> snprintf() - fill buffer with data of different types * (f)scanf() - read from STDIN * getwd() - return working directory * realpath() - return absolute (full) path Utilisez des fonctions équivalentes sûres, qui vérifient la longueur des buffers, chaque fois que c'est possible. À savoir : - gets() -> fgets() - strcpy() -> strncpy() - strcat() -> strncat() - sprintf() -> snprintf() Ces fonctions qui n'ont pas d'équivalents sûrs devraient être réécrites avec des vérifications sûres implémentées. Le temps passé sur ça profitera dans l'avenir. Souvenez-vous que vous devez le faire seulement une fois. Utilisez des compilateurs, qui sont en mesure d'identifier des fonctions dangereuses, des erreurs logiques et de vérifer si la mémoire est remplacée quand et où elle ne devrait pas être.