Annonce

Réduire
Aucune annonce.

Reversing tutorials - level 1 - Quelques outils avant de démarrer

Réduire
X
 
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • Tutoriel Reversing tutorials - level 1 - Quelques outils avant de démarrer

    Prerequis
    Connaître Linux un minimum
    Des bases (ou plus) en programmation C
    Savoir utiliser les pages de manuel de Linux (man man)
    Savoir chercher sur google les termes que l'on ne connaît pas


    Avant propos
    Avant tout, vous devriez aller jeter un œil ICI pour avoir une bonne image globale sur le reversing.

    Ce tutoriel et les suivants sont réservés au reversing de binaires sous un environnement Linux. Tous les exemples que vous pourrez trouver sont réalisés sur une Debian testing sous Amd64, donc en 64bits. En cas de changement de version, elle sera précisée dans l'article en question ainsi que les versions des différents outils (gcc, gdb ...).

    Pour celui-ci :
    version de gcc : 4.7.3-4


    Quelques outils pratiques pour le reversing de binaires Linux
    Notre cher sakarov vous a déjà fourni quelques outils pratiques pour le reversing. Ici je ne rajouterai que les outils pour le reversing de binaires statiques/dynamiques (on y reviendra) Linux

    file - Determine file type
    Code:
    $ man objdump
    C'est certainement l'outil le plus utile lorsque l'on fait du reversing. En effet comment savoir de quel type de fichier vous allez faire l'analyse ? Je doute que GDB réussirait a lancer une image jpeg. C'est pourquoi file est indispensable. cette commande vous affiche le type de fichier que vous lui passez en paramètre parfois avec quelques informations supplémentaires. Si vous voulez plus de précisions sur les types de fichiers : http://fr.wikipedia.org/wiki/Type_MIME.

    Essayez donc de lancer ces commandes :
    Code:
    $ file /bin/ls
    $ echo bonjour > toto && file toto
    $ file /lib/*so*
    objdump, hexdump, hexedit, gcc, gdb
    Code:
    $ man objdump
    $ man hexdump
    $ man hexedit
    $ man gcc
    $ man gdb
    Vous avez déjà du lire ça dans le tutoriel de sakarov. J'ai ajoute hexedit, un éditeur hexadécimal en ligne de commande assez utile.

    ltrace - A library call tracer
    Code:
    $ man ltrace
    Comme sa description l'indique cet outil se charge de tracer les différents appels de fonctions qu'un binaire effectue au cours de son cycle de vie. Les fonctions externes que vous appelez dans vos programmes tels que printf, strcmp ... font partie d'une librairie qui est liée à votre binaire lorsque vous le compilez. C'est ces fonctions-là que ltrace va intercepter pour vous les afficher.

    Exemple avec la commande ls (qui est un programme se trouvant dans /bin/ si vous n’êtes pas au courant) :
    Code:
    $ ltrace /bin/ls
    
    __libc_start_main(0x4029e0, 1, 0x7fffb73d2588, 0x412b40, 0x412b30 <unfinished ...>
    strrchr("/bin/ls", '/')                          = "/ls"
    setlocale(6, "")                                 = "en_US.UTF-8"
    bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
    textdomain("coreutils")                          = "coreutils"
    __cxa_atexit(0x40a380, 0, 0, 0x736c6974756572, 3) = 0
    isatty(1)                                        = 0
    getenv("QUOTING_STYLE")                          = NULL
    getenv("LS_BLOCK_SIZE")                          = NULL
    getenv("BLOCK_SIZE")                             = NULL
    getenv("BLOCKSIZE")                              = NULL
    getenv("POSIXLY_CORRECT")                        = NULL
    getenv("BLOCK_SIZE")                             = NULL
    getenv("COLUMNS")                                = NULL
    ioctl(1, 21523, 0x7fffb73d2070)                  = -1
    getenv("TABSIZE")                                = NULL
    getenv("POSIXLY_CORRECT")                        = NULL
    __errno_location()                               = 0x7fdea234d6c8
    malloc(56)                                       = 0x024c7040
    memcpy(0x024c7040, "", 56)                       = 0x024c7040
    __errno_location()                               = 0x7fdea234d6c8
    malloc(56)                                       = 0x024c7080
    memcpy(0x024c7080, "", 56)                       = 0x024c7080
    malloc(19200)                                    = 0x024c70c0
    malloc(32)                                       = 0x024cbbd0
    strlen(".")                                      = 1
    malloc(2)                                        = 0x024cbc00
    memcpy(0x024cbc00, ".", 2)                       = 0x024cbc00
    __errno_location()                               = 0x7fdea234d6c8
    opendir(".")                                     = 0x024cbc20
    readdir(0x024cbc20)                              = 0x024cbc48
    readdir(0x024cbc20)                              = 0x024cbc68
    readdir(0x024cbc20)                              = 0x024cbc88
    readdir(0x024cbc20)                              = 0x024cbca8
    readdir(0x024cbc20)                              = 0x024cbcc0
    readdir(0x024cbc20)                              = 0x024cbce0
    readdir(0x024cbc20)                              = 0x024cbd00
    readdir(0x024cbc20)                              = 0x024cbd20
    readdir(0x024cbc20)                              = 0x024cbd40
    readdir(0x024cbc20)                              = 0x024cbd58
    readdir(0x024cbc20)                              = 0x024cbd78
    readdir(0x024cbc20)                              = 0x024cbda0
    readdir(0x024cbc20)                              = 0x024cbdc8
    ...
    free(0x024cbc00)                                 = <void>
    free(NULL)                                       = <void>
    free(0x024cbbd0)                                 = <void>
    exit(0 <unfinished ...>
    __fpending(0x7fdea1b10160, 0, 0x7fdea1b10cf0, 4, 0) = 75
    fileno(0x7fdea1b10160)                           = 1
    __freading(0x7fdea1b10160, 0, 0x7fdea1b10cf0, 4, 0) = 0
    __freading(0x7fdea1b10160, 0, 2052, 4, 0)        = 0
    fflush(0x7fdea1b10160BH_US_08_Shacham_Return_Oriented_Programming.pdf
    out
    perso
    pro
    school
    todo
    )                           = 0
    fclose(0x7fdea1b10160)                           = 0
    __fpending(0x7fdea1b10080, 0, 0x7fdea1b11700, 0xfbad000c, 0x7fdea1b117f0) = 0
    fileno(0x7fdea1b10080)                           = 2
    __freading(0x7fdea1b10080, 0, 0x7fdea1b11700, 0xfbad000c, 0x7fdea1b117f0) = 0
    __freading(0x7fdea1b10080, 0, 4, 0xfbad000c, 0x7fdea1b117f0) = 0
    fflush(0x7fdea1b10080)                           = 0
    fclose(0x7fdea1b10080)                           = 0
    +++ exited (status 0) +++
    Dans la partie gauche on retrouve les fonctions avec leurs paramètres, dans la partie droite ce que la fonction a renvoyé. Ainsi dans notre exemple on y retrouve des appels à readdir, malloc, memcpy, strlen ... des fonctions que vous avez sûrement dû déjà utiliser.

    Une simple lecture rapide nous montre le flux général d’exécution sans entrer dans les détails. On peut donc constater (entre autres) que le programme ouvre un dossier avec opendir (man opendir) qui renvoie une structure se trouvant à l'adresse "0x024cbc20" puis il est parcouru grâce à readdir (man readdir) qui prend l'adresse de cette structure en paramètre. Je vous laisse deviner la suite.

    Code:
    ...
    opendir(".")                                     = 0x024cbc20
    readdir(0x024cbc20)                              = 0x024cbc48
    readdir(0x024cbc20)                              = 0x024cbc68
    ...
    Essayez donc de compiler ce code et de lancer ltrace sur le binaire :
    Code:
    $ cat password.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
      if (argc > 1)
        {
          if (!strcmp("le_password", argv[1]))
    	printf("Nice job, you found the password !\n");
          else
    	printf("Try again !\n");
        }
      else
        printf("Please enter a password as second argument : %s YOUR_PASSWORD\n", argv[0]);
      return (0);
    }
    
    $ gcc -o password password.c
    
    $ ./password 
    Please enter a password as second argument : ./password YOUR_PASSWORD
    
    $ ltrace ./password test
    __libc_start_main(0x40058c, 2, 0x7ffff5fcf6e8, 0x400600, 0x400690 <unfinished ...>
    strcmp("le_password", "test")                                                                  = -8
    puts("Try again !"Try again !
    )                                                                                              = 12
    +++ exited (status 0) +++
    
    $ ltrace ./password le_password
    __libc_start_main(0x40058c, 2, 0x7ffffbf1e178, 0x400600, 0x400690 <unfinished ...>
    strcmp("le_password", "le_password")                                                           = 0
    puts("Nice job, you found the password"...Nice job, you found the password !
    )                                                                                              = 35
    +++ exited (status 0) +++
    
    $
    Comme quoi il n'y a pas forcément toujours besoin de lancer un tas d'outils.

    On peut tout de même y voir un sérieux défaut, en effet, les fonctions des librairies utilisées sont affichées avec les paramètres et valeurs de retour, mais qu'en est-il des fonctions écrites par le développeur ?
    Vous l'aurez compris ltrace ne peut pas faire le job car ces fonctions-là ne sont pas dans une librairie (sauf si vous créez votre librairie). Un développeur qui va recoder des algorithmes existant sans ce service de librairie ne sera donc pas sujet au tracing fait par ltrace.


    strace - Trace system calls and signals
    Code:
    $ man strace
    Ce second outil est presque similaire à ltrace. Lui aussi sert à tracer votre binaire seulement il ne va pas tracer les fonctions appelées depuis des librairies mais des appels système ou syscall (cf : http://fr.wikipedia.org/wiki/Appel_syst%C3%A8me). Pour faire simple ce sont des fonctions élémentaires mises en place par le kernel pour vous permettre d’interagir avec celui-ci (afficher un caractère, allouer de la mémoire ...) la plupart des fonctions de la libc font appels a ces syscall. Par exemple, printf et ses dérivés font appel à write (man 2 write) un syscall de plus bas niveau.

    Exemple avec notre binaire précédent :
    Code:
    $ strace ./password test
    execve("./password", ["./password", "test"], [/* 25 vars */])                                       = 0
    brk(0)                                                                                              = 0x20b9000
    access("/etc/ld.so.nohwcap", F_OK)                                                                  = -1 ENOENT (No such file or directory)
    mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)                            = 0x7f1ba9c08000
    access("/etc/ld.so.preload", R_OK)                                                                  = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)                                                        = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=81063, ...})                                                = 0
    mmap(NULL, 81063, PROT_READ, MAP_PRIVATE, 3, 0)                                                     = 0x7f1ba9bf4000
    close(3)                                                                                            = 0
    access("/etc/ld.so.nohwcap", F_OK)                                                                  = -1 ENOENT (No such file or directory)
    open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)                                         = 3
    read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\32\2\0\0\0\0\0"..., 832)                = 832
    fstat(3, {st_mode=S_IFREG|0755, st_size=1737136, ...})                                              = 0
    mmap(NULL, 3849280, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0)                           = 0x7f1ba963d000
    mprotect(0x7f1ba97df000, 2097152, PROT_NONE) = 0
    mmap(0x7f1ba99df000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a2000) = 0x7f1ba99df000
    mmap(0x7f1ba99e5000, 15424, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0)       = 0x7f1ba99e5000
    close(3)                                                                                            = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)                            = 0x7f1ba9bf3000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)                            = 0x7f1ba9bf2000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)                            = 0x7f1ba9bf1000
    arch_prctl(ARCH_SET_FS, 0x7f1ba9bf2700)                                                             = 0
    mprotect(0x7f1ba99df000, 16384, PROT_READ)                                                          = 0
    mprotect(0x7f1ba9c0a000, 4096, PROT_READ)                                                           = 0
    munmap(0x7f1ba9bf4000, 81063)                                                                       = 0
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...})                                      = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)                            = 0x7f1ba9c07000
    write(1, "Try again !\n", 12Try again !
    )                                                                                                   = 12
    exit_group(0)                                                                                       = ?
    $ exit
    On le constate ici, notre strcmp a disparu, forcément car strcmp n'est pas un syscall. Là où c'est intéressant, c'est pour retrouver des adresses mémoires notamment mais aussi une trace bas niveau du comportement du binaire.

    valgrind - A suite of tools for debugging and profiling programs
    Code:
    $ man valgrind
    Si vous faites du reversing, vous l'utiliserez certainement pas tous les jours, cependant cet outil est à connaître.
    Il s'agit d'un profiler notamment utilisé pour détecter les fuites de mémoire. Là où c'est interessant, c'est qu'à partir des fuites de mémoire, il est parfois possible d’injecter du code dans le binaire lors d'une exploitation de faille dans celui-ci. Autrement dit, lors de l'exploitation de failles applicatives, il sera parfois utile de se service de valgrind, et souvent il faudra faire du reversing pour trouver une faille à exploiter.

    readelf - Display information about ELF files
    Code:
    $ man readelf
    Comme sa description l'indique là encore, cet utilitaire permet d'afficher des informations sur un binaire Linux (ELF). On y reviendra plus tard.


    ldd - Print shared library dependencies
    Code:
    $ man ldd
    Cet outil permet d’afficher une liste de librairies dynamiques qui seront attaches au binaire pour son exécution (libc ...). On y reviendra également.

    nm - List symbols from object files
    Code:
    $ man nm
    Cet outil permet d’afficher une liste des symboles d'un fichier ELF. On y reviendra également.

    strings - Print the strings of printablecharacters in file
    Code:
    $ man strings
    Cette commande affiche les chaînes de caractères affichables contenues dans un fichier pas forcement binaire). Il est important de mémoriser cette commande, elle est forte utile pour retrouver des informations qui permettront de mieux comprendre le programme tel que les chaînes utilisées lors de l'a programmation de celui-ci.


    La prochaine, on verra plus en détails les fichier ELF et les outils associés a ce format de fichier.
Chargement...
X